Full Code of samm-git/btest-opensource for AI

master 5040a01267c3 cached
20 files
79.2 KB
26.6k tokens
90 symbols
1 requests
Download .txt
Repository: samm-git/btest-opensource
Branch: master
Commit: 5040a01267c3
Files: 20
Total size: 79.2 KB

Directory structure:
gitextract_hqjlj7ey/

├── .dockerignore
├── .gitignore
├── Dockerfile
├── LICENSE
├── Makefile.am
├── README.md
├── bclient.pl
├── bootstrap
├── bserver.pl
├── btest.c
├── build.sh
├── configure.ac
├── docker-compose.yml
├── install-sh
├── md5.c
├── md5.h
├── timing_mach.c
├── timing_mach.h
├── utils.c
└── utils.h

================================================
FILE CONTENTS
================================================

================================================
FILE: .dockerignore
================================================


================================================
FILE: .gitignore
================================================
btest
rust_btest-server/**
.vscode/**

================================================
FILE: Dockerfile
================================================
# Use a minimal Alpine Linux base image with build tools
FROM alpine:latest

# Set the working directory inside the container
WORKDIR /app
# Install necessary build tools
RUN apk --no-cache add build-base

# Copy the build.sh script and btest source code into the container
COPY build.sh .
COPY . /app/
# Run the build.sh script to compile btest
RUN chmod +x build.sh && ./build.sh
COPY --from=docker.io/tailscale/tailscale:stable /usr/local/bin/tailscaled /usr/local/bin/tailscaled
COPY --from=docker.io/tailscale/tailscale:stable /usr/local/bin/tailscale /usr/local/bin/tailscale
# Define environment variables
ENV USERNAME admin
ENV PASSWORD admin
RUN [ -n "$AUTHKEY" ] && [ ! -z "$AUTHKEY" ] && tailscale up --authkey $AUTHKEY || { echo "AuthKey is not set or empty. Exiting."; exit 1; }

# Set the entry point for the container
ENTRYPOINT ["./btest"]

# Specify the default command to run when the container starts
CMD ["-a", "${USERNAME}", "-p", "${PASSWORD}", "-s"]


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2016 Alex Samorukov

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: Makefile.am
================================================
bin_PROGRAMS = btest
btest_SOURCES = btest.c md5.c timing_mach.c
AM_LDFLAGS = -lpthread


================================================
FILE: README.md
================================================
# Mikrotik bandwith test protocol description
## about
This is attempt to create opensource version of the btest tool. Currently it is possible to run bandwith-test only from another mikrotik devices or from the windows closed source client. 

Protocol itsef seems to be not complicated, so it should not be easy to create 3rd party client for it.

## Protocol description
There is no official protocol description, so everything was obtained using WireShark tool and RouterOS 6 running in the virtualbox, which was connecting to the real device. For now i am inspecting only TCP, later will try to do UDP if TCP works. Please keep in mind that this data is guessed on the captures, so possibly is not accurate or wrong. 

```
> bserver: hello
01:00:00:00 
> client: Start test cmd, depending on the client settings:
01:01:01:00:00:80:00:00:00:00:00:00:00:00:00:00 (transmit)
01:01:00:00:00:80:00:00:00:00:00:00:00:00:00:00 (transmit, random data)
00:01:01:00:dc:05:00:00:00:00:00:00:00:00:00:00 (transmit, UDP)
01:02:01:00:00:80:00:00:00:00:00:00:00:00:00:00 (receive)
01:02:00:00:00:80:00:00:00:00:00:00:00:00:00:00 (receive, random data)
00:02:01:00:dc:05:00:00:00:00:00:00:00:00:00:00 (receive, UDP, remote-udp-tx-size: default (1500))
00:02:01:00:d0:07:00:00:00:00:00:00:00:00:00:00 (receive, UDP, remote-udp-tx-size: 2000)
00:02:01:00:00:fa:00:00:00:00:00:00:00:00:00:00 (receive, UDP, remote-udp-tx-size: 64000)
01:02:01:00:00:80:00:00:01:00:00:00:00:00:00:00 (receive, remote-tx-speed=1)
01:02:01:00:00:80:00:00:ff:ff:ff:ff:00:00:00:00 (receive, remote-tx-speed=4294967295)
01:02:01:00:00:80:00:00:00:00:00:00:01:00:00:00 (receive, local-tx-speed=1)
01:02:01:00:00:80:00:00:00:00:00:00:ff:ff:ff:ff (receive, local-tx-speed=4294967295)
01:02:01:0a:00:80:00:00:00:00:00:00:00:00:00:00 (receive, tcp-connection-count=10)
01:03:01:00:00:80:00:00:00:00:00:00:00:00:00:00 (both)
01:03:00:00:00:80:00:00:00:00:00:00:00:00:00:00 (both, random data)
00:03:01:00:dc:05:00:00:00:00:00:00:00:00:00:00 (both, UDP)
> bserver: Start test confirm (auth is disabled on server):
01:00:00:00 (tcp-connection-count=1 on a client)
01:bc:04:00 (tcp-connection-count>1 on a client)
> bserver: auth requested, with 16 challenge bytes (3 random packets provided)
02:00:00:00:90:67:3f:0f:5c:c7:4e:17:a0:e0:9e:1c:b9:ee:3b:0c
02:00:00:00:17:e7:ee:84:83:cc:15:53:e8:fa:9c:0d:ad:ac:b8:e1
02:00:00:00:ee:d1:19:b9:d3:f2:df:6d:04:46:da:25:55:44:49:81
> client: auth reply (3 random packets, userid test)
90:75:1f:b0:2a:f7:25:51:46:25:71:c3:16:ce:cc:2b:74:65:73:74:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
a0:1b:fa:27:78:55:08:71:93:09:70:86:15:30:84:ac:74:65:73:74:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
b4:f2:9e:06:5e:74:da:89:65:c9:be:94:4d:bf:8f:20:74:65:73:74:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
> bserver: packet data follows
00:00:00:00........
```
1. Server always starts with "hello" (01:00:00:00) command after establishing TCP connection to port 2000. If UDP protocl is specified in the client TCP connection is still established.
2. Client sends 16 bytes command started with 01 (TCP) or 00 (UDP) command to the server. Some of the bytes guess:
 - **cmd[0]**: protocol, 01: TCP, 00: UDP
 - **cmd[1]**: direction, 01 - transmit, 02 - receive, 03 - both
 - **cmd[2]**: use random data, 00 - use random, 01: use \00 character
 - **cmd[3]**: tcp-connection-count, 0 if tcp-connection-count=1, number if more
 - **cmd[4:5]**: remote-udp-tx-size (dc:05) on UDP, 00:80 on TCP, UINT16 - Little Endian
 - **cmd[6:7]**: client buffer size. Maximum value for TCP is 65535 (FF:FF).
 - **cmd[8:11]**: remote-tx-speed, 0: unlimimted, 1-4294967295: value, UINT32 - Little Endian
 - **cmd[12:15]**: local-tx-speed, 0: unlimimted, 1-4294967295: value, UINT32 - Little Endian
3. If server authentication is disabled it sends 01:00:00:00 and starts to transmit/recieve data. 
If auth is enabled and **btest server versions is < 6.43** server sends 20bytes reply started with 02:00:00:00 in the beginning and random bytes (challenge) in the [4:15] range.
Customer sends back 48 bytes reply containing user name (unencrypted) and 16 bytes hash of the password with challenge. Hashing alghoritm is double md5, hashed by challenge, see "authentication" section. 
If **btest server versions is >= 6.43** server sends **03:00:00:00** reply and new authentication method is used, based on the EC-SRP5. Exact implementation details are not yet known and method is not yet supported in the OSS software. See https://github.com/haakonnessjoen/MAC-Telnet/issues/42 for the related discussion. 
4. If auth failed server sends back `00000000` (client shows "authentication failed"), if succeed - `01000000` packet and test is starting.
5. If tcp-connection-count > 1 server should reply with `01:xx:xx:00` where xx seems to be some kind of authentification data to start other connections. This number is used in other threads. 
6. From time to time (~1 per second) server or client sends 12 bytes messages started with `07`, e.g. `07:00:00:00:01:00:00:00:36:6e:03:00`. Btest client relies on this information to show "transmit" speed.  It is server-level statistic, where values are:
  - **stat[0]** is 07 (message id)
  - **stat[4-7]**  number or time from start in seconds, sends one per second, UINT32 - Little Endian
  - **stat[8-11]** number of bytes transferred per sequence, UINT32 - Little Endian

## Old authentication methoud
Sample of the challenge-response for the "test" password:
```
ad32d6f94d28161625f2f390bb895637
3c968565bc0314f281a6da1571cf7255
```

Hashing alghoritm was found with implementation of the testing server and help from the **Chupaka** on a Mikrotik forum. It is 
md5('password' + md5('password' + hash)). This is an example on the Perl
language, which using challenge from the example and returns same hash:

```perl
use Digest::MD5 qw(md5 md5_hex);

my $salt='ad32d6f94d28161625f2f390bb895637';
my $pass='test';
print md5_hex($pass.md5($pass.pack( 'H*', $salt)))."\n";
```
Script should return `3c968565bc0314f281a6da1571cf7255` and its matching captured traffic. 


================================================
FILE: bclient.pl
================================================
#!/usr/bin/env perl

# this is PoC for the Mikrotik bandwith server. On mikorotik use
# /tool bandwidth-test <ip> duration=10s protocol=tcp tcp-connection-count=1
# to connect to it

use IO::Socket::INET;
use Digest::MD5 qw(md5 md5_hex md5_base64);
use Data::Dumper;

# auto-flush on socket
$| = 1;
 
my $udp_port_offset=256;
my $require_auth=0;

# creating a listening socket
my $socket = new IO::Socket::INET (
    PeerHost => '192.168.10.1',
    PeerPort => '2000',
    Proto => 'tcp',
);
die "cannot create socket $!\n" unless $socket;
print "connecting to server port 2000\n";
 
$socket->recv($data, 1024);
print "client hello data is ".unpack('H*', "$data")."\n";
$data=pack('CCCCvvNN',
	1, # UDP
	2, # TX
	1, # Not random
	0, # TCP Count
	1500, # TX Size
	0, # Unknown
	0, # Unlimited
	0, # Unlimited
);
print "client command is ".unpack('H*', "$data")."\n";

$socket->send($data);

while(defined($socket->recv($data, 32768))) {
	print "client recv data is ".unpack('H*', "$data")."\n";
}
exit(0);

while(1)
{
    # waiting for a new client connection
    my $client_socket = $socket->accept();
 
    # get information about a newly connected client
    my $client_address = $client_socket->peerhost();
    my $client_port = $client_socket->peerport();
    print "connection from $client_address:$client_port\n";
    print "sending hello\n";
    # write response data to the connected client
    $data = pack 'H*', '01000000';
    $client_socket->send($data);
    print "reading reply\n";
    $client_socket->recv($data, 1024);
    my $action=unpackCmd($data);
    print(Dumper($action));
    # send auth requested command
    if ($require_auth) {
        $client_socket->send(pack 'H*', '02000000');
        $digest=pack 'H*', '00000000000000000000000000000000';
        # print "digest md5=".md5_hex($digest)." expected reply is: ".md5_hex(md5($digest))."\n";
        $client_socket->send($digest);
        $client_socket->recv($data, 1024);
        print "client auth data is ".unpack('H*', "$data")."\n";
    #    $client_socket->send(pack 'H*', '00000000'); # auth failed
        $client_socket->send(pack 'H*', '01000000'); # auth accepted
    }
    if ($action->{proto} eq 'TCP') {
    	# send data
    	while (1) {
	    $client_socket->send(pack 'H*', '00' x $action->{tx_size});
	    print ".";
        }
    	# notify client that response has been sent
        shutdown($client_socket, 1);
    } else {
	my $tx_socket = new IO::Socket::INET (
		PeerAddr => "$client_address:2000",
		Proto => 'udp',
	);
	die "cannot create socket $!\n" unless $tx_socket;
	sleep(10);
    	#while (1) {
	#    $tx_socket->send(pack 'H*', '00' x $action->{tx_size});
	#    print ".";
        #}
	$tx_socket->close();
    }
}
$socket->close();

sub unpackCmd {
	my $data=shift;
	my @cmdlist=unpack('CCCCvvNN', $data);
	my $cmd={
		proto => $cmdlist[0] ? 'TCP' : 'UDP',
		direction => ($cmdlist[1]==1) ? 'RX' : (($cmdlist[1]==2) ? 'TX' : 'TXRX'),
		random => ($cmdlist[2]==0),
		tcp_conn_count => ($cmdlist[3]==0) ? 1 : $cmdlist[3],
		tx_size => $cmdlist[4],
		unknown => $cmdlist[5],
		remote_tx_speed => $cmdlist[6],
		local_tx_speed => $cmdlist[7],
	};
	#print "data is ".unpack('H*', "$data")."\n";
	return($cmd);
}


================================================
FILE: bootstrap
================================================
#! /bin/sh

# bootstrap — generic bootstrap/autogen.sh script for autotools projects
#
# Copyright © 2002—2015 Sam Hocevar <sam@hocevar.net>
#
# This program is free software. It comes without any warranty, to
# the extent permitted by applicable law. You can redistribute it
# and/or modify it under the terms of the Do What the Fuck You Want
# to Public License, Version 2, as published by the WTFPL Task Force.
# See http://www.wtfpl.net/ for more details.
#
# The latest version of this script can be found at the following place:
#    http://caca.zoy.org/wiki/build

# Die if an error occurs
set -e

# Guess whether we are using configure.ac or configure.in
if test -f configure.ac; then
  conffile="configure.ac"
elif test -f configure.in; then
  conffile="configure.in"
else
  echo "$0: could not find configure.ac or configure.in"
  exit 1
fi

# Check for needed features
auxdir="`sed -ne 's/^[ \t]*A._CONFIG_AUX_DIR *([[ ]*\([^] )]*\).*/\1/p' $conffile`"
pkgconfig="`grep '^[ \t]*PKG_PROG_PKG_CONFIG' $conffile >/dev/null 2>&1 && echo yes || echo no`"
libtool="`grep '^[ \t]*A._PROG_LIBTOOL' $conffile >/dev/null 2>&1 && echo yes || echo no`"
header="`grep '^[ \t]*A._CONFIG_HEADER' $conffile >/dev/null 2>&1 && echo yes || echo no`"
makefile="`[ -f Makefile.am ] && echo yes || echo no`"
aclocalflags="`sed -ne 's/^[ \t]*ACLOCAL_AMFLAGS[ \t]*=//p' Makefile.am 2>/dev/null || :`"

# Check for automake
amvers="no"
for v in "" "-1.15" "-1.14" "-1.13" "-1.12" "-1.11"; do
  if automake${v} --version > /dev/null 2>&1; then
    amvers=${v}
    break
  fi
done

if test "$amvers" = "no"; then
  echo "$0: automake not found"
  exit 1
fi

# Check for autoconf
acvers="no"
for v in "" "259" "253"; do
  if autoconf${v} --version >/dev/null 2>&1; then
    acvers="${v}"
    break
  fi
done

if test "$acvers" = "no"; then
  echo "$0: autoconf not found"
  exit 1
fi

# Check for libtool
if test "$libtool" = "yes"; then
  libtoolize="no"
  if glibtoolize --version >/dev/null 2>&1; then
    libtoolize="glibtoolize"
  else
    for v in "16" "15" "" "14"; do
      if libtoolize${v} --version >/dev/null 2>&1; then
        libtoolize="libtoolize${v}"
        break
      fi
    done
  fi

  if test "$libtoolize" = "no"; then
    echo "$0: libtool not found"
    exit 1
  fi
fi

# Check for pkg-config
if test "$pkgconfig" = "yes"; then
  if ! pkg-config --version >/dev/null 2>&1; then
    echo "$0: pkg-config not found"
    exit 1
  fi
fi

# Remove old cruft
for x in aclocal.m4 configure config.guess config.log config.sub config.cache config.h.in config.h compile libtool.m4 ltoptions.m4 ltsugar.m4 ltversion.m4 ltmain.sh libtool ltconfig missing mkinstalldirs depcomp install-sh; do rm -f $x autotools/$x; if test -n "$auxdir"; then rm -f "$auxdir/$x"; fi; done
rm -Rf autom4te.cache
if test -n "$auxdir"; then
  if test ! -d "$auxdir"; then
    mkdir "$auxdir"
  fi
  aclocalflags="-I $auxdir -I . ${aclocalflags}"
fi

# Honour M4PATH because sometimes M4 doesn't
save_IFS=$IFS
IFS=:
tmp="$M4PATH"
for x in $tmp; do
  if test -n "$x"; then
    aclocalflags="-I $x ${aclocalflags}"
  fi
done
IFS=$save_IFS

# Explain what we are doing from now
set -x

# Bootstrap package
if test "$libtool" = "yes"; then
  ${libtoolize} --copy --force
  if test -n "$auxdir" -a ! "$auxdir" = "." -a -f "ltmain.sh"; then
    echo "$0: working around a minor libtool issue"
    mv ltmain.sh "$auxdir/"
  fi
fi

aclocal${amvers} ${aclocalflags}
autoconf${acvers}
if test "$header" = "yes"; then
  autoheader${acvers}
fi
if test "$makefile" = "yes"; then
  #add --include-deps if you want to bootstrap with any other compiler than gcc
  #automake${amvers} --add-missing --copy --include-deps
  automake${amvers} --foreign --add-missing --copy
fi

# Remove cruft that we no longer want
rm -Rf autom4te.cache



================================================
FILE: bserver.pl
================================================
#!/usr/bin/env perl

# this is PoC for the Mikrotik bandwith server. On mikorotik use
# /tool bandwidth-test <ip> duration=10s protocol=tcp tcp-connection-count=1
# to connect to it

use IO::Socket::INET;
use Digest::MD5 qw(md5 md5_hex md5_base64);
use Data::Dumper;

# auto-flush on socket
$| = 1;
 
my $require_auth=0;

my $udp_port_offset=256;

# creating a listening socket
my $socket = new IO::Socket::INET (
    LocalHost => '0.0.0.0',
    LocalPort => '2000',
    Proto => 'tcp',
    Listen => 5,
    Reuse => 1
);
die "cannot create socket $!\n" unless $socket;
print "server waiting for client connection on port 2000\n";
 
while(1)
{
    # waiting for a new client connection
    my $client_socket = $socket->accept();
 
    # get information about a newly connected client
    my $client_address = $client_socket->peerhost();
    my $client_port = $client_socket->peerport();
    print "connection from $client_address:$client_port\n";
    print "sending hello\n";
    # write response data to the connected client
    $data = pack 'H*', '01000000';
    $client_socket->send($data);
    print "reading reply\n";
    $client_socket->recv($data, 1024);
    print "data is ".unpack('H*', "$data")."\n";
    my $action=unpackCmd($data);
    print(Dumper($action));
    # send auth requested command
    if ($require_auth) {
        $client_socket->send(pack 'H*', '02000000');
        $digest=pack 'H*', '00000000000000000000000000000000';
        # print "digest md5=".md5_hex($digest)." expected reply is: ".md5_hex(md5($digest))."\n";
        $client_socket->send($digest);
        $client_socket->recv($data, 1024);
        print "client auth data is ".unpack('H*', "$data")."\n";
    #    $client_socket->send(pack 'H*', '00000000'); # auth failed
        $client_socket->send(pack 'H*', '01000000'); # auth accepted
    }
    if ($action->{proto} eq 'TCP') {
    	# send data
    	while (1) {
	    $client_socket->send(pack 'H*', '00' x $action->{tx_size});
	    print ".";
        }
    	# notify client that response has been sent
        shutdown($client_socket, 1);
    } else {
    	print "sending hello\n";
    	# write response data to the connected client
    	$data = pack 'H*', '01000000';
    	$client_socket->send($data);
	my $locudpport=2001;
	$data= pack('n', $locudpport);
    	print "sending local port: " . unpack('H*', $data) . "\n";
    	$client_socket->send($data);
	#while(defined($client_socket->recv($data, 32768))) {
	#	print "client recv data is ".unpack('H*', "$data")."\n";
	#}
	#exit(0);

	my $tx_socket = new IO::Socket::INET (
		PeerHost => "$client_address",
		PeerPort => $locudpport+$udp_port_offset,
    		LocalHost => '0.0.0.0',
    		LocalPort => $locudpport,
		Proto => 'udp',
	);
	die "cannot create socket $!\n" unless $tx_socket;
	#sleep(10);
	my $seq=1;
    	while (1) {
	    $tx_socket->send(pack 'NNH*', 0, $seq, '00' x ($action->{tx_size}-8-28));
	    $seq++;
	    print ".";
        }
	$tx_socket->close();
    }
}
$socket->close();

sub unpackCmd {
	my $data=shift;
	my @cmdlist=unpack('CCCCvvNN', $data);
	my $cmd={
		proto => $cmdlist[0] ? 'TCP' : 'UDP',
		direction => ($cmdlist[1]==1) ? 'RX' : (($cmdlist[1]==2) ? 'TX' : 'TXRX'),
		random => ($cmdlist[2]==0),
		tcp_conn_count => ($cmdlist[3]==0) ? 1 : $cmdlist[3],
		tx_size => $cmdlist[4],
		unknown => $cmdlist[5],
		remote_tx_speed => $cmdlist[6],
		local_tx_speed => $cmdlist[7],
	};
	#print "data is ".unpack('H*', "$data")."\n";
	return($cmd);
}


================================================
FILE: btest.c
================================================
/* Program to emulate the Mikrotik Bandwidth test protocol */
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <signal.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <arpa/inet.h>
#include <netinet/tcp.h>
#include <sys/select.h>
#include <netdb.h>
#include <errno.h>
#include <time.h>
#include <getopt.h>
#include "timing_mach.h"
#include "utils.h"
#include "md5.h"
#define BTEST_PORT 2000
#define BTEST_PORT_CLIENT_OFFSET 256
#define CMD_PROTO_UDP 0
#define CMD_PROTO_TCP 1
#define CMD_DIR_RX 0x01
#define CMD_DIR_TX 0x02
#define CMD_RANDOM 0

unsigned char helloStr[] = {0x01, 0x00, 0x00, 0x00};
unsigned char noAuthResp[] = {0x01, 0x00, 0x00, 0x00};
unsigned char needAuthResp[] = {0x02, 0x00, 0x00, 0x00};
unsigned char failedAuthResp[] = {0x00, 0x00, 0x00, 0x00};
// unsigned char cmdStr[16];
unsigned int udpport = BTEST_PORT;

struct cmdStruct
{
	int proto;
	int direction;
	int random;
	int tcp_conn_count;
	unsigned int tx_size;
	unsigned int client_buf_size;
	unsigned long remote_tx_speed;
	unsigned long local_tx_speed;
};

struct statStruct
{
	unsigned long seq;
	unsigned char unknown[3];
	unsigned long recvBytes;
	unsigned long maxInterval; // In us, Not sent over the wire
	unsigned long minInterval; // In us, Not sent over the wire
	signed long lostPackets;   // Not sent over the wire
};

void usage();
void usage_long();
int server();
int client();
int server_conn(int cmdsock, char *);
struct cmdStruct unpackCmdStr(unsigned char *);
void packCmdStr(struct cmdStruct *, unsigned char *);
struct statStruct unpackStatStr(unsigned char *);
void packStatStr(struct statStruct *, unsigned char *);
void printStatStruct(char *, struct statStruct *);
int test_udp(struct cmdStruct, int, char *);
int test_tcp(struct cmdStruct, int);
void timespec_diff(struct timespec *, struct timespec *,
				   struct timespec *);
void timespec_add(struct timespec *, struct timespec *);
int timespec_cmp(struct timespec *t1, struct timespec *t2);
void timespec_dump(char *, struct timespec *);
void dumpBuffer(const char *msg, unsigned char *buffer, int len);
void packShortLE(unsigned char *, unsigned int);
void packLongLE(unsigned char *, unsigned long);
void packLongBE(unsigned char *, unsigned long);
void unpackShortLE(unsigned char *, unsigned int *);
void unpackLongLE(unsigned char *, unsigned long *);
void unpackLongBE(unsigned char *, unsigned long *);
void calc_interval(struct timespec *, unsigned long, unsigned int);
unsigned char *calc_md5auth(unsigned char *nonce, char *passwd);

char *opt_bandwidth = NULL;
int opt_udpmode = 0;
int opt_server = 0;
int opt_interval = 0;
int opt_nat = 0;
int opt_transmit = 0;
int opt_receive = 0;
int opt_display = 0;
char *opt_connect = NULL;
char *opt_authuser = NULL;
char *opt_authpass = NULL;

double new_tx_speed; // Used to command the sending thread to change speed
int tx_speed_changed = 0;

int main(int argc, char **argv)
{
	int opt;
	static struct option long_options[] =
		{
			{"udp", no_argument, &opt_udpmode, 1},
			{"transmit", no_argument, &opt_transmit, 1},
			{"receive", no_argument, &opt_receive, 1},
			{"server", no_argument, &opt_server, 1},
			{"nat", no_argument, &opt_nat, 1},
			{"display", no_argument, &opt_display, 1},
			{"help", no_argument, 0, 'h'},
			{"client", required_argument, 0, 'c'},
			{"interval", required_argument, 0, 'i'},
			{"bandwidth", required_argument, 0, 'b'},
			{"authuser", required_argument, 0, 'a'},
			{"authpass", required_argument, 0, 'p'},
			{0, 0, 0, 0}};
	int option_index = 0;

	if (argc < 2)
	{
		usage();
		exit(1);
	}

	while ((opt = getopt_long(argc, argv, "utrsnhdc:i:b:a:p:", long_options, &option_index)) != -1)
	{
		switch (opt)
		{
		case 'u':
			opt_udpmode = 1;
			break;
		case 't':
			opt_transmit = 1;
			break;
		case 'r':
			opt_receive = 1;
			break;
		case 's':
			opt_server = 1;
			break;
		case 'n':
			opt_nat = 1;
			break;
		case 'd':
			opt_display = 1;
			break;
		case 'c':
			opt_connect = strdup(optarg);
			break;
		case 'i':
			opt_interval = atoi(optarg);
			break;
		case 'b':
			opt_bandwidth = strdup(optarg);
			break;
		case 'a':
			opt_authuser = strdup(optarg);
			break;
		case 'p':
			opt_authpass = strdup(optarg);
			break;
		case 'h':
			usage_long();
			exit(1);
		default:
			usage();
			exit(EXIT_FAILURE);
		}
	}

	if (opt_server)
	{
		server();
	}
	else
	{
		if (!opt_transmit && !opt_receive)
		{
			printf("You must specify transmit(-t) or receive(-r)\n");
			exit(EXIT_FAILURE);
		}
		client();
	}
}

void usage()
{
	const char usage_shortstr[] = "Usage: btest [-s|-r -c host] [options]\n"
								  "Try `btest --help' for more information.\n";
	printf(usage_shortstr);
}

void usage_long()
{
	const char usage_longstr[] = "Usage: btest [-s|-c host] [options]\n"
								 "       btest [-h|--help]\n\n"
								 "Server or Client:\n"
								 "  -i, --interval  #         seconds between periodic bandwidth reports\n"
								 "  -h, --help                show this message and quit\n"
								 "  -a, --authuser <user>     provide username for authentication\n"
								 "  -p, --authpass <password> provide password for authentication\n"
								 "Server specific:\n"
								 "  -s, --server              run in server mode\n"
								 "Client specific:\n"
								 "  -c, --client    <host>    run in client mode, connecting to <host>\n"
								 "  -t, --transmit 			transmit data\n"
								 "  -r, --receive 			receive data\n"
								 "  -u, --udp                 use UDP\n"
								 "  -b, --bandwidth #[KMG][/#] target bandwidth in bits/sec (0 for unlimited)\n"
								 "                            (default %d Mbit/sec for UDP, unlimited for TCP)\n"
								 "                            (optional slash and packet count for burst mode)\n"

		;
	printf("%s", usage_longstr);
}

int client()
{
	int cmdsock;
	struct cmdStruct cmd;
	unsigned char cmdStr[16];
	struct sockaddr_in serverAddr;
	struct hostent *he;
	char *remoteIP;
	char bwmult;
	int ret;

	cmd.proto = CMD_PROTO_UDP;
	cmd.direction = opt_transmit ? CMD_DIR_RX : 0;
	cmd.direction += opt_receive ? CMD_DIR_TX : 0;
	cmd.random = 0;
	cmd.tcp_conn_count = 0;
	cmd.tx_size = 1500;
	cmd.client_buf_size = 0;

	if (opt_bandwidth)
	{
		if ((ret = sscanf(opt_bandwidth, "%lu%c", &cmd.remote_tx_speed, &bwmult)) < 1)
		{
			fprintf(stderr, "Cannot parse bandwidth string\n");
			return (-1);
		}
		if (ret == 2)
		{
			/* Apply multiplier */
			if (bwmult == 'k' || bwmult == 'K')
			{
				cmd.remote_tx_speed *= 1000;
			}
			else if (bwmult == 'm' || bwmult == 'M')
			{
				cmd.remote_tx_speed *= 1000000;
			}
			else
			{
				fprintf(stderr, "Cannot parse bandwidth string\n");
			}
		}
	}
	else
	{
		cmd.remote_tx_speed = 0; // Unlimited
	}
	cmd.local_tx_speed = cmd.remote_tx_speed;

	packCmdStr(&cmd, cmdStr);

	if ((he = gethostbyname(opt_connect)) == NULL)
	{
		// get the host info
		perror("gethostbyname");
		exit(1);
	}

	// Return the first one;
	bcopy((char *)he->h_addr, (char *)&serverAddr.sin_addr.s_addr, he->h_length);

	remoteIP = strdup(inet_ntoa(serverAddr.sin_addr));

	cmdsock = socket(PF_INET, SOCK_STREAM, 0);
	int enable = 1;
	if (setsockopt(cmdsock, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0)
	{
		perror("setsockopt(SO_REUSEADDR) failed");
	}

	serverAddr.sin_family = AF_INET;
	serverAddr.sin_port = htons(BTEST_PORT);
	memset(serverAddr.sin_zero, '\0', sizeof serverAddr.sin_zero);

	if (connect(cmdsock, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0)
	{
		perror("connect");
		exit(1);
	}

	/* Look for hello string */
	if (recv(cmdsock, helloStr, sizeof(helloStr), 0) < sizeof(helloStr))
	{
		fprintf(stderr, "Remote did not return any hello response\n");
		return (-1);
	}

	if (memcmp(helloStr, noAuthResp, sizeof(noAuthResp)) != 0)
	{
		fprintf(stderr, "Remote did not return correct hello response\n");
		dumpBuffer("Response: ", helloStr, sizeof(helloStr));
		return (-1);
	}

	send(cmdsock, cmdStr, sizeof(cmdStr), 0);

	/* Look for hello string */
	if (recv(cmdsock, helloStr, sizeof(helloStr), 0) < sizeof(helloStr))
	{
		fprintf(stderr, "Remote did not return any auth response\n");
		return (-1);
	}

	if (memcmp(helloStr, noAuthResp, sizeof(noAuthResp)) != 0)
	{
		if (memcmp(helloStr, needAuthResp, sizeof(needAuthResp)) == 0)
		{
			/* Fetch the 16 random bytes */
			unsigned char nonce[16];
			char user[32];
			unsigned char *md5hash;
			if (recv(cmdsock, nonce, sizeof(nonce), 0) < sizeof(nonce))
			{
				fprintf(stderr, "Remote did not send auth nonce\n");
				return (-1);
			}
			// dumpBuffer("Nonce: ", nonce, sizeof(nonce));
			memset(user, '\0', sizeof(user));
			strncpy(user, opt_authuser, sizeof(user));
			md5hash = calc_md5auth(nonce, opt_authpass);
			// dumpBuffer("MD5: ", md5hash, 16);
			send(cmdsock, md5hash, 16, 0);
			send(cmdsock, user, sizeof(user), 0);

			/* Look for hello string */
			if (recv(cmdsock, helloStr, sizeof(helloStr), 0) < sizeof(helloStr))
			{
				fprintf(stderr, "Remote did not return auth response\n");
				return (-1);
			}

			if (memcmp(helloStr, failedAuthResp, sizeof(failedAuthResp)) == 0)
			{
				fprintf(stderr, "Remote authentication failed\n");
				// dumpBuffer("Response: ", helloStr,sizeof(helloStr));
				return (-1);
			}

			if (memcmp(helloStr, noAuthResp, sizeof(noAuthResp)) != 0)
			{
				fprintf(stderr, "Remote did not return correct hello response\n");
				dumpBuffer("Response: ", helloStr, sizeof(helloStr));
				return (-1);
			}
		}
		else
		{
			fprintf(stderr, "Remote did not return correct hello response\n");
			dumpBuffer("Response: ", helloStr, sizeof(helloStr));
			return (-1);
		}
	}

	if (cmd.proto == CMD_PROTO_UDP)
	{
		test_udp(cmd, cmdsock, remoteIP);
	}
	else
	{
		test_tcp(cmd, cmdsock);
	}
	return 0;
}

int server()
{
	int controlSocket;
	struct sockaddr_in serverAddr;
	struct sockaddr_in clientAddr;
	socklen_t addr_size;
	int newSocket;

	printf("Running in server mode\n");
	controlSocket = socket(PF_INET, SOCK_STREAM, 0);
	int enable = 1;
	if (setsockopt(controlSocket, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0)
	{
		perror("setsockopt(SO_REUSEADDR) failed");
	}

	serverAddr.sin_family = AF_INET;
	serverAddr.sin_port = htons(BTEST_PORT);
	serverAddr.sin_addr.s_addr = INADDR_ANY;
	memset(serverAddr.sin_zero, '\0', sizeof serverAddr.sin_zero);

	bind(controlSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr));

	if (listen(controlSocket, 5) == 0)
		printf("Listening\n");
	else
		printf("Error\n");

	addr_size = sizeof(clientAddr);
	signal(SIGCHLD, SIG_IGN);
	while (1)
	{
		newSocket = accept(controlSocket, (struct sockaddr *)&clientAddr, &addr_size);
		/* fork a child process to handle the new connection */
		if (!fork())
		{
			server_conn(newSocket, strdup(inet_ntoa(clientAddr.sin_addr)));
			close(newSocket);
			printf("Complete\n");
			exit(0);
		}
		else
		{
			/*if parent, close the socket and go back to listening new requests*/
			close(newSocket);
		}
	}
	return 0;
}

int server_conn(int cmdsock, char *remoteIP)
{
	int nBytes = 1;
	struct cmdStruct cmd;
	unsigned char cmdStr[16];
	uint32_t hexValue;
	int flag = 1;
	setsockopt(cmdsock, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(int));

	/* Send hello message */
	send(cmdsock, helloStr, sizeof(helloStr), 0);

	/* Recieve command */
	nBytes = recv(cmdsock, cmdStr, sizeof(cmdStr), 0);
	if (nBytes < sizeof(cmdStr))
	{
		return (-1);
	}

	cmd = unpackCmdStr(cmdStr);
	printf("proto=%d\n", cmd.proto);
	printf("direction=%d\n", cmd.direction);
	printf("random=%d\n", cmd.random);
	printf("tcp_conn_count=%d\n", cmd.tcp_conn_count);
	printf("tx_size=%d\n", cmd.tx_size);
	printf("client_buf_size=%d\n", cmd.client_buf_size);
	printf("remote_tx_speed=%lu\n", cmd.remote_tx_speed);
	printf("local_tx_speed=%lu\n", cmd.local_tx_speed);
	printf("remoteIP=%s\n", remoteIP);
	/* auth logic here */
	int isauthrequired = isStringNotEmpty(opt_authuser) || isStringNotEmpty(opt_authpass);
	printf("is auth required: %d\n", isauthrequired);
	if (isauthrequired)
	{
		/* Send auth request */
		hexValue = htonl(0x02000000);
		send(cmdsock, &hexValue, sizeof(hexValue), 0);
		unsigned char nonce[16];
		/* Generate random nonce */
		generateRandomNonce(nonce);
		char recuser[32];
		char recdigest[32];
		unsigned char *md5hash;
		send(cmdsock, nonce, sizeof(nonce), 0);
		md5hash = calc_md5auth(nonce, opt_authpass);
		unsigned char authstr[32];
		nBytes = recv(cmdsock, authstr, sizeof(authstr), 0);
		if (nBytes < sizeof(authstr))
		{
			return (-1);
		}
		unsigned char username[AUTHSTR_SIZE - 16];
		char receivedhash[2 * (size_t)16 + 1];
		char serverdigest[2 * (size_t)16 + 1];
		memcpy(username, authstr + 16, sizeof(username));
		getHexRepresentation(authstr, (size_t)16, receivedhash);
		getHexRepresentation(md5hash, (size_t)16, serverdigest);
		// dumpBuffer("> auth Response: ", authstr, sizeof(authstr));
		printf("auth received from %s: user(%s) - pass_digest(%s)\n", remoteIP, username, receivedhash);
		printf("server have user(%s) - pass_digest(%s)\n", opt_authuser, serverdigest);
		if (isauth(opt_authuser, username, receivedhash, serverdigest))
		{
			hexValue = htonl(0x01000000);
			send(cmdsock, &hexValue, sizeof(hexValue), 0);
		}
		else
		{
			hexValue = htonl(0x00000000);
			send(cmdsock, &hexValue, sizeof(hexValue), 0);
			return (-1);
		}
	}
	/* Send all OK message */
	send(cmdsock, helloStr, sizeof(helloStr), 0);

	if (cmd.proto == CMD_PROTO_UDP)
	{
		if (test_udp(cmd, cmdsock, remoteIP) != -1)
		{
			return (-1);
		}
	}
	else
	{
		if (test_tcp(cmd, cmdsock) == -1)
		{
			return (-1);
		}
	}
	/* loop while connection is live */
	return 0;
}

void packShortLE(unsigned char *buf, unsigned int res)
{
	int i;
	for (i = 0; i < 2; i++)
	{
		buf[i] = res & 0xff;
		res >>= 8;
	}
}

void packLongLE(unsigned char *buf, unsigned long res)
{
	int i;
	for (i = 0; i < 4; i++)
	{
		buf[i] = res & 0xff;
		res >>= 8;
	}
}

void packLongBE(unsigned char *buf, unsigned long res)
{
	int i;
	for (i = 3; i >= 0; i--)
	{
		buf[i] = res & 0xff;
		res >>= 8;
	}
}

void unpackShortLE(unsigned char *buf, unsigned int *pres)
{
	int i;
	*pres = 0;
	for (i = 2; i >= 0; i--)
	{
		*pres <<= 8;
		*pres += buf[i];
	}
}

void unpackLongLE(unsigned char *buf, unsigned long *pres)
{
	int i;
	*pres = 0;
	for (i = 3; i >= 0; i--)
	{
		*pres <<= 8;
		*pres += buf[i];
	}
}

void unpackLongBE(unsigned char *buf, unsigned long *pres)
{
	int i;
	*pres = 0;
	for (i = 0; i < 4; i++)
	{
		*pres <<= 8;
		*pres += buf[i];
	}
}

struct cmdStruct unpackCmdStr(unsigned char *cmdStr)
{
	struct cmdStruct cmd;

	dumpBuffer("Cmd buffer: ", cmdStr, 16);

	cmd.proto = cmdStr[0];
	cmd.direction = cmdStr[1];
	cmd.random = cmdStr[2];
	cmd.tcp_conn_count = cmdStr[3];

	unpackShortLE(&cmdStr[4], &cmd.tx_size);

	/* Assume little endian */
	unpackShortLE(&cmdStr[6], &cmd.client_buf_size);
	unpackLongLE(&cmdStr[8], &cmd.remote_tx_speed);
	unpackLongLE(&cmdStr[12], &cmd.local_tx_speed);

	return (cmd);
}

void packCmdStr(struct cmdStruct *pcmd, unsigned char *buf)
{

	buf[0] = (pcmd->proto == CMD_PROTO_TCP) ? CMD_PROTO_TCP : CMD_PROTO_UDP;
	buf[1] = pcmd->direction & 0x03;
	buf[2] = pcmd->random ? 1 : 0;
	buf[3] = pcmd->tcp_conn_count;

	/* Little endian */
	packShortLE(&buf[4], pcmd->tx_size);

	packShortLE(&buf[6], pcmd->client_buf_size);

	packLongLE(&buf[8], pcmd->remote_tx_speed);
	packLongLE(&buf[12], pcmd->local_tx_speed);

	// dumpBuffer("Packed Buffer: ", buf, 16);

	return;
}

struct statStruct unpackStatStr(unsigned char *buf)
{
	struct statStruct stat;

	// dumpBuffer("Stat buffer: ", buf, 12);

	unpackLongBE(&buf[1], &stat.seq);

	memcpy(stat.unknown, buf + 5, 3);

	unpackLongLE(&buf[8], &stat.recvBytes);

	/* These three are not used */
	stat.maxInterval = 0;
	stat.minInterval = 0;
	stat.lostPackets = 0;
	return (stat);
}

void printStatStruct(char *msg, struct statStruct *pstat)
{
	/* Work out the bit rate - assume status every second */
	double bitRate;
	char bitRateStr[20];

	bitRate = pstat->recvBytes * 8;
	if (bitRate > 10000000)
	{
		sprintf(bitRateStr, "%.1fMb/s", bitRate / 1000000);
	}
	else
	{
		sprintf(bitRateStr, "%.1fkb/s", bitRate / 1000);
	}
	printf("%sSeq: %lu, Rate: %s",
		   msg,
		   pstat->seq,
		   bitRateStr);
	if (pstat->maxInterval > 0)
	{
		printf(", Lost: %ld, Min: %.4lfms, Max: %.4lfms, Diff %0.4lfms\n",
			   pstat->lostPackets,
			   ((double)pstat->minInterval) / 1000,
			   ((double)pstat->maxInterval) / 1000,
			   ((double)pstat->maxInterval) / 1000 - ((double)pstat->minInterval) / 1000);
	}
	else
	{
		printf("\n");
	}
}

void packStatStr(struct statStruct *pstat, unsigned char *buf)
{

	buf[0] = 0x07;
	packLongBE(&buf[1], pstat->seq);
	buf[5] = 0x00;
	buf[6] = 0x00;
	buf[7] = 0x00;
	packLongLE(&buf[8], pstat->recvBytes);

	// dumpBuffer("Stat buffer: ", buf, 12);

	return;
}

int udpSocket;

void *test_udp_tx(void *arg)
{
	struct cmdStruct *pcmd;
	unsigned char *buf;
	int seq = 1;
	int i;
	struct timespec interval; /* Interval between packets in nano seconds */
	struct timespec nextPacketTime;
	struct timespec now;
	int tx_speed_variable = 0;
	unsigned long tx_speed;

	// printf("Calling test_udp_tx()\n");
	// sleep(1);
	pcmd = (struct cmdStruct *)arg;
	// printf("Calling test_udp_tx(%d)\n", pcmd->tx_size);
	if (opt_server)
	{
		tx_speed = pcmd->remote_tx_speed;
	}
	else
	{
		tx_speed = pcmd->local_tx_speed;
	}
	printf("Tx speed: %lu\n", tx_speed);
	if (tx_speed == 0)
	{
		tx_speed_variable = 1;
		tx_speed = 1000000;
	}
	new_tx_speed = tx_speed;

	calc_interval(&interval, tx_speed, pcmd->tx_size);
	timespec_dump("Interval: ", &interval);
	buf = (unsigned char *)malloc(pcmd->tx_size - 28);
	// printf("Calling test_udp_tx(more)\n");
	bzero(buf, pcmd->tx_size - 28);

	/* Get current time and add the interval to it */
	clock_gettime(CLOCK_MONOTONIC, &nextPacketTime);
	timespec_dump("gettime: ", &nextPacketTime);
	while (1)
	{
		if (tx_speed_variable && tx_speed_changed)
		{
			tx_speed_changed = 0;
			tx_speed = new_tx_speed;
			calc_interval(&interval, tx_speed, pcmd->tx_size);
			// printf("New Tx speed: %lf\n", new_tx_speed);
		}
		nextPacketTime.tv_sec += interval.tv_sec;
		nextPacketTime.tv_nsec += interval.tv_nsec;
		if (nextPacketTime.tv_nsec >= 1000000000)
		{
			nextPacketTime.tv_sec += nextPacketTime.tv_nsec / 1000000000;
			nextPacketTime.tv_nsec %= 1000000000;
		}
		int tmp = seq++;

		// printf("seq=%d\n", seq);
		/* Put in sequence number */
		for (i = 3; i >= 0; i--)
		{
			buf[i] = tmp % 256;
			tmp = tmp >> 8;
		}
		// printf("Sleep until %lu:%lu\n", nextPacketTime.tv_sec, nextPacketTime.tv_nsec);
		// timespec_dump("sleep until: ", &nextPacketTime);
		clock_gettime(CLOCK_MONOTONIC, &now);
		if (now.tv_sec <= nextPacketTime.tv_sec && now.tv_nsec < nextPacketTime.tv_nsec)
		{
#ifdef __MACH__
			clock_nanosleep_abstime(&nextPacketTime);
#else
			if (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &nextPacketTime, NULL) < 0)
			{
				perror("clock_nanosleep: ");
			}
#endif
		}

		send(udpSocket, buf, pcmd->tx_size - 28, 0);
		/*
		if (send(udpSocket, buf, pcmd->tx_size-28,0)<0) {
			perror("send udp: ");
			exit(-1);
		}
		*/
	}
}

struct statStruct recvStats;

void *test_udp_rx(void *arg)
{
	struct cmdStruct *pcmd;
	unsigned char *buf;
	int nBytes;
	struct timespec last;
	struct timespec now;
	struct timespec interval;
	unsigned long intervalUs;
	unsigned long lastSeq = 0;
	unsigned long thisSeq = 0;

	recvStats.recvBytes = 0;
	recvStats.maxInterval = 0;
	recvStats.minInterval = 0;
	recvStats.lostPackets = 0;
	pcmd = (struct cmdStruct *)arg;
	// printf("Calling test_udp_rx(tx_size=%d)\n", pcmd->tx_size);
	buf = (unsigned char *)malloc(pcmd->tx_size);
	last.tv_sec = 0;
	last.tv_nsec = 0;
	while (1)
	{
		if ((nBytes = recv(udpSocket, buf, pcmd->tx_size, 0)) < 0)
		{
			/* Ignore connection refused - the other end probably wasn't ready */
			if (errno != ECONNREFUSED)
			{
				perror("test_udp_rx: recv udp: ");
				exit(-1);
			}
		}
		if (nBytes > 0)
		{
			clock_gettime(CLOCK_MONOTONIC, &now);

			thisSeq = buf[0];
			thisSeq = thisSeq * 256 + buf[1];
			thisSeq = thisSeq * 256 + buf[2];
			thisSeq = thisSeq * 256 + buf[3];
			if (lastSeq > 0)
			{
				/* Ignore the first one - we often lose packets initially */
				/*
								if (thisSeq-lastSeq-1 != 0) {
									printf("loss: thisSeq=%lu, lastSeq=%lu\n", thisSeq, lastSeq);
								}
				*/
				recvStats.lostPackets += thisSeq - lastSeq - 1;
			}

			if (last.tv_sec != 0 && last.tv_nsec != 0)
			{
				/* Work out time difference */
				timespec_diff(&last, &now, &interval);
				intervalUs = interval.tv_nsec / 1000;
				intervalUs += interval.tv_sec * 1000000;

				/* Divide by the difference in the sequence
				** number so that we don't get a bump in the
				** interval due to a bunch of missed packets
				*/
				intervalUs /= thisSeq - lastSeq;

				if (recvStats.maxInterval == 0)
				{
					/* First result */
					// printf("First: %lu\n", intervalUs);
					recvStats.maxInterval = intervalUs;
					recvStats.minInterval = intervalUs;
				}
				else
				{
					if (intervalUs > recvStats.maxInterval)
					{
						recvStats.maxInterval = intervalUs;
					}
					if (intervalUs < recvStats.minInterval)
					{
						recvStats.minInterval = intervalUs;
					}
				}
			}
			last = now;
			recvStats.recvBytes += nBytes + 28;
			lastSeq = thisSeq;
		}
	}
}

int test_udp(struct cmdStruct cmd, int cmdsock, char *remoteIP)
{
	unsigned char socknumbuf[2];
	pthread_t pth_tx;
	pthread_t pth_rx;
	struct sockaddr_in serverAddr, clientAddr;
	socklen_t addr_size;
	int nBytes;
	unsigned char buffer[1024];
	struct statStruct remoteStats;
	struct timespec interval; /* Interval between status messages */
	struct timespec nextStatusTime;
	struct timespec timeout;
	struct timespec now;

	// printf("Calling test_udp()\n");
	if (opt_server)
	{
		/* Send server socket number */
		udpport++;
		socknumbuf[0] = udpport / 256;
		socknumbuf[1] = udpport % 256;
		send(cmdsock, socknumbuf, sizeof(socknumbuf), 0);
	}
	else
	{
		if (recv(cmdsock, socknumbuf, sizeof(socknumbuf), 0) < sizeof(socknumbuf))
		{
			fprintf(stderr, "Did not recieve remote port number\n");
			return (-1);
		}
		// dumpBuffer("Socket number buffer: ", socknumbuf, 2);
		udpport = (socknumbuf[0] << 8) + socknumbuf[1];
	}
	// printf("Calling test_udp(udpport=%d)\n", udpport);

	addr_size = sizeof(clientAddr);

	/* Create a UDP socket to transmit/recieve the data */
	udpSocket = socket(PF_INET, SOCK_DGRAM, 0);
	serverAddr.sin_family = AF_INET;
	if (opt_server)
	{
		serverAddr.sin_port = htons(udpport);
	}
	else
	{
		serverAddr.sin_port = htons(udpport + BTEST_PORT_CLIENT_OFFSET);
	}
	serverAddr.sin_addr.s_addr = INADDR_ANY;
	memset(serverAddr.sin_zero, '\0', sizeof serverAddr.sin_zero);

	bind(udpSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr));

	clientAddr.sin_family = AF_INET;
	if (opt_server)
	{
		clientAddr.sin_port = htons(udpport + BTEST_PORT_CLIENT_OFFSET);
	}
	else
	{
		clientAddr.sin_port = htons(udpport);
	}
	clientAddr.sin_addr.s_addr = inet_addr(remoteIP);
	memset(clientAddr.sin_zero, '\0', sizeof clientAddr.sin_zero);

	/* Connect it to the remote end */
	connect(udpSocket, (struct sockaddr *)&clientAddr, sizeof(clientAddr));

	/* We have to swap the command round between server and client */
	if (((cmd.direction & CMD_DIR_TX) && opt_server) || ((cmd.direction & CMD_DIR_RX) && !opt_server))
	{
		pthread_create(&pth_tx, NULL, test_udp_tx, (void *)&cmd);
	}
	if (((cmd.direction & CMD_DIR_RX) && opt_server) || ((cmd.direction & CMD_DIR_TX) && !opt_server))
	{
		if (opt_nat)
		{
			/* Send a zero length packet to open any firewall */
			send(udpSocket, buffer, 0, 0);
		}
		pthread_create(&pth_rx, NULL, test_udp_rx, (void *)&cmd);
	}

	/* Interval between status messages */
	interval.tv_nsec = 0;
	interval.tv_sec = 1;

	/* Get current time and add the interval to it */
	clock_gettime(CLOCK_MONOTONIC, &nextStatusTime);
	timespec_add(&nextStatusTime, &interval);
	recvStats.seq = 0;
	while (1)
	{
		int ready;
		fd_set readfds;

		clock_gettime(CLOCK_MONOTONIC, &now);
		timespec_diff(&now, &nextStatusTime, &timeout);

		FD_ZERO(&readfds);
		FD_SET(cmdsock, &readfds);
		ready = pselect(cmdsock + 1, &readfds, NULL, NULL, &timeout, NULL);
		if (ready)
		{
			nBytes = recv(cmdsock, buffer, 1024, 0);
			if (nBytes <= 0)
			{
				exit(0);
			}
			remoteStats = unpackStatStr(buffer);
			if (((cmd.direction & CMD_DIR_TX) && opt_server) || ((cmd.direction & CMD_DIR_RX) && !opt_server))
			{
				/* Only print this if we are transmitting */
				if (opt_display)
				{
					double bitRate = remoteStats.recvBytes * 8;
					if (bitRate > 10000000)
					{
						printf("%.1f Mb/s\n", bitRate / 1000000);
					}
					else
					{
						printf("%.1f kb/s\n", bitRate / 1000);
					}
				}
				else
				{
					printStatStruct("Remote: ", &remoteStats);
				}

				fflush(stdout);
				/* Set the outgoing speed to be twice the rate reported */
				new_tx_speed = remoteStats.recvBytes * 8;
				new_tx_speed *= 1.5;
				tx_speed_changed = 1;
			}
		}

		clock_gettime(CLOCK_MONOTONIC, &now);
		if (timespec_cmp(&now, &nextStatusTime) > 0)
		{
			recvStats.seq++;
			packStatStr(&recvStats, buffer);
			if (((cmd.direction & CMD_DIR_RX) && opt_server) || ((cmd.direction & CMD_DIR_TX) && !opt_server))
			{
				/* Only print this if we are recieving */
				if (opt_display)
				{
					double bitRate = recvStats.recvBytes * 8;
					if (bitRate > 10000000)
					{
						printf("%.1f Mb/s\n", bitRate / 1000000);
					}
					else
					{
						printf("%.1f kb/s\n", bitRate / 1000);
					}
				}
				else
				{
					printStatStruct("Local : ", &recvStats);
				}
				fflush(stdout);
			}

			send(cmdsock, buffer, 12, 0);
			timespec_add(&nextStatusTime, &interval);
			recvStats.recvBytes = 0;
			recvStats.maxInterval = 0;
			recvStats.minInterval = 0;
			recvStats.lostPackets = 0;
		}
	}
}

int tcpSocket;

void *test_tcp_tx(void *arg)
{
	struct cmdStruct *pcmd;
	unsigned char *buf;

	// printf("Calling test_tcp_tx()\n");
	sleep(1);
	pcmd = (struct cmdStruct *)arg;
	buf = (unsigned char *)malloc(pcmd->tx_size);
	bzero(buf, pcmd->tx_size);

	buf[0] = 0x07;
	buf[4] = 0x01;

	while (send(tcpSocket, buf, pcmd->tx_size, 0) > 0)
		;
	return NULL;
}

int test_tcp(struct cmdStruct cmd, int cmdsock)
{
	pthread_t pth_tx;
	// pthread_t pth_rx;
	int nBytes, i;
	unsigned char buffer[1024];

	// printf("Calling test_tcp()\n");
	tcpSocket = cmdsock;
	if (cmd.direction == CMD_DIR_TX)
	{
		pthread_create(&pth_tx, NULL, test_tcp_tx, (void *)&cmd);
	}

	printf("Listening on TCP cmdsock\n");
	while ((nBytes = recv(cmdsock, buffer, 1024, 0)))
	{
		for (i = 0; i < nBytes - 1; i++)
		{
			printf("%02x", buffer[i]);
		}
		printf("\n");
	}
	return 0;
}

/* Calculate the difference between two timespec's */
void timespec_diff(struct timespec *start, struct timespec *stop,
				   struct timespec *result)
{
	if ((stop->tv_nsec - start->tv_nsec) < 0)
	{
		result->tv_sec = stop->tv_sec - start->tv_sec - 1;
		result->tv_nsec = stop->tv_nsec - start->tv_nsec + 1000000000;
	}
	else
	{
		result->tv_sec = stop->tv_sec - start->tv_sec;
		result->tv_nsec = stop->tv_nsec - start->tv_nsec;
	}

	return;
}

/* Add timespec t2 onto timespec t1 onto an existing one  */
void timespec_add(struct timespec *t1, struct timespec *t2)
{
	t1->tv_sec += t2->tv_sec;
	t1->tv_nsec += t2->tv_nsec;
	if (t1->tv_nsec >= 1000000000)
	{
		t1->tv_sec += t1->tv_nsec / 1000000000;
		t1->tv_nsec %= 1000000000;
	}
}

/* Return -1, 0, or +1 if t1 less than, equal to or greater than t2 */
int timespec_cmp(struct timespec *t1, struct timespec *t2)
{
	if (t1->tv_sec < t2->tv_sec)
	{
		return (-1);
	}
	else if (t1->tv_sec > t2->tv_sec)
	{
		return (1);
	}
	else
	{
		if (t1->tv_nsec < t2->tv_nsec)
		{
			return (-1);
		}
		else if (t1->tv_nsec > t2->tv_nsec)
		{
			return (1);
		}
		else
		{
			return (0);
		}
	}
}

void timespec_dump(char *msg, struct timespec *t1)
{
	printf("%s %lu:%lu\n", msg, t1->tv_sec, t1->tv_nsec);
}

void dumpBuffer(const char *msg, unsigned char *buffer, int len)
{
	int i;
	printf("%s", msg);
	for (i = 0; i < len; i++)
	{
		printf("%02x", buffer[i]);
	}
	printf("\n");
}

void calc_interval(struct timespec *ts, unsigned long tx_speed, unsigned int tx_size)
{
	if (tx_speed > 0)
	{
		// pthread_getcpuclockid(pthread_self(), &clock_id);
		double interval_nsec; // We use a double so we don't overflow the various ints

		interval_nsec = 1000000000;
		interval_nsec *= tx_size * 8;
		interval_nsec /= tx_speed;

		ts->tv_sec = 0;
		ts->tv_nsec = interval_nsec;

		/* Duplicate bug? in MT where anything less than 2 packets per second gets converted to 1 packet second */
		if (ts->tv_nsec > 500000000)
		{
			ts->tv_nsec = 0;
			ts->tv_sec = 1;
		}
	}
	else
	{
		ts->tv_nsec = 0;
		ts->tv_sec = 0;
	}
}

unsigned char *
calc_md5auth(unsigned char *nonce, char *passwd)
{
	MD5_CTX ctx;
	static unsigned char hash[16];

	MD5_Init(&ctx);
	MD5_Update(&ctx, (void *)passwd, strlen(passwd));
	MD5_Update(&ctx, (void *)nonce, 16);
	MD5_Final(hash, &ctx);

	MD5_Init(&ctx);
	MD5_Update(&ctx, (void *)passwd, strlen(passwd));
	MD5_Update(&ctx, (void *)hash, 16);
	MD5_Final(hash, &ctx);

	return (hash);
}


================================================
FILE: build.sh
================================================
gcc -o btest *.c -lpthread && chmod +x btest

================================================
FILE: configure.ac
================================================
AC_INIT([btest], [1.0], [bug-report@address])
AM_INIT_AUTOMAKE
AC_ARG_ENABLE(debugging, [  --disable-debugging             disable debugging code], ac_cv_debugging=$enableval, ac_cv_debugging=yes)
AC_PROG_CC
dnl Checks for header files.
AC_HEADER_STDC
AC_HEADER_SYS_WAIT
AC_CHECK_HEADERS()
AC_TYPE_UINT8_T
AC_TYPE_UINT16_T
AC_TYPE_UINT32_T
AC_CONFIG_HEADERS([config.h])
AC_CONFIG_FILES([Makefile])
AC_OUTPUT


================================================
FILE: docker-compose.yml
================================================
version: '3'

services:
  btest-service:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: btest_tool
    image: btest_tools:latest
    environment:
      - USERNAME=_________ # username here ...
      - PASSWORD=_________ # password here ...


================================================
FILE: install-sh
================================================
#!/bin/sh
# install - install a program, script, or datafile

scriptversion=2011-11-20.07; # UTC

# This originates from X11R5 (mit/util/scripts/install.sh), which was
# later released in X11R6 (xc/config/util/install.sh) with the
# following copyright and license.
#
# Copyright (C) 1994 X Consortium
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC-
# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# Except as contained in this notice, the name of the X Consortium shall not
# be used in advertising or otherwise to promote the sale, use or other deal-
# ings in this Software without prior written authorization from the X Consor-
# tium.
#
#
# FSF changes to this file are in the public domain.
#
# Calling this script install-sh is preferred over install.sh, to prevent
# 'make' implicit rules from creating a file called install from it
# when there is no Makefile.
#
# This script is compatible with the BSD install script, but was written
# from scratch.

nl='
'
IFS=" ""	$nl"

# set DOITPROG to echo to test this script

# Don't use :- since 4.3BSD and earlier shells don't like it.
doit=${DOITPROG-}
if test -z "$doit"; then
  doit_exec=exec
else
  doit_exec=$doit
fi

# Put in absolute file names if you don't have them in your path;
# or use environment vars.

chgrpprog=${CHGRPPROG-chgrp}
chmodprog=${CHMODPROG-chmod}
chownprog=${CHOWNPROG-chown}
cmpprog=${CMPPROG-cmp}
cpprog=${CPPROG-cp}
mkdirprog=${MKDIRPROG-mkdir}
mvprog=${MVPROG-mv}
rmprog=${RMPROG-rm}
stripprog=${STRIPPROG-strip}

posix_glob='?'
initialize_posix_glob='
  test "$posix_glob" != "?" || {
    if (set -f) 2>/dev/null; then
      posix_glob=
    else
      posix_glob=:
    fi
  }
'

posix_mkdir=

# Desired mode of installed file.
mode=0755

chgrpcmd=
chmodcmd=$chmodprog
chowncmd=
mvcmd=$mvprog
rmcmd="$rmprog -f"
stripcmd=

src=
dst=
dir_arg=
dst_arg=

copy_on_change=false
no_target_directory=

usage="\
Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE
   or: $0 [OPTION]... SRCFILES... DIRECTORY
   or: $0 [OPTION]... -t DIRECTORY SRCFILES...
   or: $0 [OPTION]... -d DIRECTORIES...

In the 1st form, copy SRCFILE to DSTFILE.
In the 2nd and 3rd, copy all SRCFILES to DIRECTORY.
In the 4th, create DIRECTORIES.

Options:
     --help     display this help and exit.
     --version  display version info and exit.

  -c            (ignored)
  -C            install only if different (preserve the last data modification time)
  -d            create directories instead of installing files.
  -g GROUP      $chgrpprog installed files to GROUP.
  -m MODE       $chmodprog installed files to MODE.
  -o USER       $chownprog installed files to USER.
  -s            $stripprog installed files.
  -t DIRECTORY  install into DIRECTORY.
  -T            report an error if DSTFILE is a directory.

Environment variables override the default commands:
  CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG
  RMPROG STRIPPROG
"

while test $# -ne 0; do
  case $1 in
    -c) ;;

    -C) copy_on_change=true;;

    -d) dir_arg=true;;

    -g) chgrpcmd="$chgrpprog $2"
	shift;;

    --help) echo "$usage"; exit $?;;

    -m) mode=$2
	case $mode in
	  *' '* | *'	'* | *'
'*	  | *'*'* | *'?'* | *'['*)
	    echo "$0: invalid mode: $mode" >&2
	    exit 1;;
	esac
	shift;;

    -o) chowncmd="$chownprog $2"
	shift;;

    -s) stripcmd=$stripprog;;

    -t) dst_arg=$2
	# Protect names problematic for 'test' and other utilities.
	case $dst_arg in
	  -* | [=\(\)!]) dst_arg=./$dst_arg;;
	esac
	shift;;

    -T) no_target_directory=true;;

    --version) echo "$0 $scriptversion"; exit $?;;

    --)	shift
	break;;

    -*)	echo "$0: invalid option: $1" >&2
	exit 1;;

    *)  break;;
  esac
  shift
done

if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then
  # When -d is used, all remaining arguments are directories to create.
  # When -t is used, the destination is already specified.
  # Otherwise, the last argument is the destination.  Remove it from $@.
  for arg
  do
    if test -n "$dst_arg"; then
      # $@ is not empty: it contains at least $arg.
      set fnord "$@" "$dst_arg"
      shift # fnord
    fi
    shift # arg
    dst_arg=$arg
    # Protect names problematic for 'test' and other utilities.
    case $dst_arg in
      -* | [=\(\)!]) dst_arg=./$dst_arg;;
    esac
  done
fi

if test $# -eq 0; then
  if test -z "$dir_arg"; then
    echo "$0: no input file specified." >&2
    exit 1
  fi
  # It's OK to call 'install-sh -d' without argument.
  # This can happen when creating conditional directories.
  exit 0
fi

if test -z "$dir_arg"; then
  do_exit='(exit $ret); exit $ret'
  trap "ret=129; $do_exit" 1
  trap "ret=130; $do_exit" 2
  trap "ret=141; $do_exit" 13
  trap "ret=143; $do_exit" 15

  # Set umask so as not to create temps with too-generous modes.
  # However, 'strip' requires both read and write access to temps.
  case $mode in
    # Optimize common cases.
    *644) cp_umask=133;;
    *755) cp_umask=22;;

    *[0-7])
      if test -z "$stripcmd"; then
	u_plus_rw=
      else
	u_plus_rw='% 200'
      fi
      cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;;
    *)
      if test -z "$stripcmd"; then
	u_plus_rw=
      else
	u_plus_rw=,u+rw
      fi
      cp_umask=$mode$u_plus_rw;;
  esac
fi

for src
do
  # Protect names problematic for 'test' and other utilities.
  case $src in
    -* | [=\(\)!]) src=./$src;;
  esac

  if test -n "$dir_arg"; then
    dst=$src
    dstdir=$dst
    test -d "$dstdir"
    dstdir_status=$?
  else

    # Waiting for this to be detected by the "$cpprog $src $dsttmp" command
    # might cause directories to be created, which would be especially bad
    # if $src (and thus $dsttmp) contains '*'.
    if test ! -f "$src" && test ! -d "$src"; then
      echo "$0: $src does not exist." >&2
      exit 1
    fi

    if test -z "$dst_arg"; then
      echo "$0: no destination specified." >&2
      exit 1
    fi
    dst=$dst_arg

    # If destination is a directory, append the input filename; won't work
    # if double slashes aren't ignored.
    if test -d "$dst"; then
      if test -n "$no_target_directory"; then
	echo "$0: $dst_arg: Is a directory" >&2
	exit 1
      fi
      dstdir=$dst
      dst=$dstdir/`basename "$src"`
      dstdir_status=0
    else
      # Prefer dirname, but fall back on a substitute if dirname fails.
      dstdir=`
	(dirname "$dst") 2>/dev/null ||
	expr X"$dst" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
	     X"$dst" : 'X\(//\)[^/]' \| \
	     X"$dst" : 'X\(//\)$' \| \
	     X"$dst" : 'X\(/\)' \| . 2>/dev/null ||
	echo X"$dst" |
	    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
		   s//\1/
		   q
		 }
		 /^X\(\/\/\)[^/].*/{
		   s//\1/
		   q
		 }
		 /^X\(\/\/\)$/{
		   s//\1/
		   q
		 }
		 /^X\(\/\).*/{
		   s//\1/
		   q
		 }
		 s/.*/./; q'
      `

      test -d "$dstdir"
      dstdir_status=$?
    fi
  fi

  obsolete_mkdir_used=false

  if test $dstdir_status != 0; then
    case $posix_mkdir in
      '')
	# Create intermediate dirs using mode 755 as modified by the umask.
	# This is like FreeBSD 'install' as of 1997-10-28.
	umask=`umask`
	case $stripcmd.$umask in
	  # Optimize common cases.
	  *[2367][2367]) mkdir_umask=$umask;;
	  .*0[02][02] | .[02][02] | .[02]) mkdir_umask=22;;

	  *[0-7])
	    mkdir_umask=`expr $umask + 22 \
	      - $umask % 100 % 40 + $umask % 20 \
	      - $umask % 10 % 4 + $umask % 2
	    `;;
	  *) mkdir_umask=$umask,go-w;;
	esac

	# With -d, create the new directory with the user-specified mode.
	# Otherwise, rely on $mkdir_umask.
	if test -n "$dir_arg"; then
	  mkdir_mode=-m$mode
	else
	  mkdir_mode=
	fi

	posix_mkdir=false
	case $umask in
	  *[123567][0-7][0-7])
	    # POSIX mkdir -p sets u+wx bits regardless of umask, which
	    # is incompatible with FreeBSD 'install' when (umask & 300) != 0.
	    ;;
	  *)
	    tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$
	    trap 'ret=$?; rmdir "$tmpdir/d" "$tmpdir" 2>/dev/null; exit $ret' 0

	    if (umask $mkdir_umask &&
		exec $mkdirprog $mkdir_mode -p -- "$tmpdir/d") >/dev/null 2>&1
	    then
	      if test -z "$dir_arg" || {
		   # Check for POSIX incompatibilities with -m.
		   # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or
		   # other-writable bit of parent directory when it shouldn't.
		   # FreeBSD 6.1 mkdir -m -p sets mode of existing directory.
		   ls_ld_tmpdir=`ls -ld "$tmpdir"`
		   case $ls_ld_tmpdir in
		     d????-?r-*) different_mode=700;;
		     d????-?--*) different_mode=755;;
		     *) false;;
		   esac &&
		   $mkdirprog -m$different_mode -p -- "$tmpdir" && {
		     ls_ld_tmpdir_1=`ls -ld "$tmpdir"`
		     test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1"
		   }
		 }
	      then posix_mkdir=:
	      fi
	      rmdir "$tmpdir/d" "$tmpdir"
	    else
	      # Remove any dirs left behind by ancient mkdir implementations.
	      rmdir ./$mkdir_mode ./-p ./-- 2>/dev/null
	    fi
	    trap '' 0;;
	esac;;
    esac

    if
      $posix_mkdir && (
	umask $mkdir_umask &&
	$doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir"
      )
    then :
    else

      # The umask is ridiculous, or mkdir does not conform to POSIX,
      # or it failed possibly due to a race condition.  Create the
      # directory the slow way, step by step, checking for races as we go.

      case $dstdir in
	/*) prefix='/';;
	[-=\(\)!]*) prefix='./';;
	*)  prefix='';;
      esac

      eval "$initialize_posix_glob"

      oIFS=$IFS
      IFS=/
      $posix_glob set -f
      set fnord $dstdir
      shift
      $posix_glob set +f
      IFS=$oIFS

      prefixes=

      for d
      do
	test X"$d" = X && continue

	prefix=$prefix$d
	if test -d "$prefix"; then
	  prefixes=
	else
	  if $posix_mkdir; then
	    (umask=$mkdir_umask &&
	     $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break
	    # Don't fail if two instances are running concurrently.
	    test -d "$prefix" || exit 1
	  else
	    case $prefix in
	      *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;;
	      *) qprefix=$prefix;;
	    esac
	    prefixes="$prefixes '$qprefix'"
	  fi
	fi
	prefix=$prefix/
      done

      if test -n "$prefixes"; then
	# Don't fail if two instances are running concurrently.
	(umask $mkdir_umask &&
	 eval "\$doit_exec \$mkdirprog $prefixes") ||
	  test -d "$dstdir" || exit 1
	obsolete_mkdir_used=true
      fi
    fi
  fi

  if test -n "$dir_arg"; then
    { test -z "$chowncmd" || $doit $chowncmd "$dst"; } &&
    { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } &&
    { test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false ||
      test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1
  else

    # Make a couple of temp file names in the proper directory.
    dsttmp=$dstdir/_inst.$$_
    rmtmp=$dstdir/_rm.$$_

    # Trap to clean up those temp files at exit.
    trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0

    # Copy the file name to the temp name.
    (umask $cp_umask && $doit_exec $cpprog "$src" "$dsttmp") &&

    # and set any options; do chmod last to preserve setuid bits.
    #
    # If any of these fail, we abort the whole thing.  If we want to
    # ignore errors from any of these, just make sure not to ignore
    # errors from the above "$doit $cpprog $src $dsttmp" command.
    #
    { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } &&
    { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } &&
    { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } &&
    { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } &&

    # If -C, don't bother to copy if it wouldn't change the file.
    if $copy_on_change &&
       old=`LC_ALL=C ls -dlL "$dst"	2>/dev/null` &&
       new=`LC_ALL=C ls -dlL "$dsttmp"	2>/dev/null` &&

       eval "$initialize_posix_glob" &&
       $posix_glob set -f &&
       set X $old && old=:$2:$4:$5:$6 &&
       set X $new && new=:$2:$4:$5:$6 &&
       $posix_glob set +f &&

       test "$old" = "$new" &&
       $cmpprog "$dst" "$dsttmp" >/dev/null 2>&1
    then
      rm -f "$dsttmp"
    else
      # Rename the file to the real destination.
      $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null ||

      # The rename failed, perhaps because mv can't rename something else
      # to itself, or perhaps because mv is so ancient that it does not
      # support -f.
      {
	# Now remove or move aside any old file at destination location.
	# We try this two ways since rm can't unlink itself on some
	# systems and the destination file might be busy for other
	# reasons.  In this case, the final cleanup might fail but the new
	# file should still install successfully.
	{
	  test ! -f "$dst" ||
	  $doit $rmcmd -f "$dst" 2>/dev/null ||
	  { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null &&
	    { $doit $rmcmd -f "$rmtmp" 2>/dev/null; :; }
	  } ||
	  { echo "$0: cannot unlink or rename $dst" >&2
	    (exit 1); exit 1
	  }
	} &&

	# Now rename the file to the real destination.
	$doit $mvcmd "$dsttmp" "$dst"
      }
    fi || exit 1

    trap '' 0
  fi
done

# Local variables:
# eval: (add-hook 'write-file-hooks 'time-stamp)
# time-stamp-start: "scriptversion="
# time-stamp-format: "%:y-%02m-%02d.%02H"
# time-stamp-time-zone: "UTC"
# time-stamp-end: "; # UTC"
# End:


================================================
FILE: md5.c
================================================
/*
 * This is an OpenSSL-compatible implementation of the RSA Data Security,
 * Inc. MD5 Message-Digest Algorithm.
 *
 * Written by Solar Designer <solar at openwall.com> in 2001, and placed
 * in the public domain.  There's absolutely no warranty.
 *
 * This differs from Colin Plumb's older public domain implementation in
 * that no 32-bit integer data type is required, there's no compile-time
 * endianness configuration, and the function prototypes match OpenSSL's.
 * The primary goals are portability and ease of use.
 *
 * This implementation is meant to be fast, but not as fast as possible.
 * Some known optimizations are not included to reduce source code size
 * and avoid compile-time configuration.
 */

#include <string.h>
#include "md5.h"

/*
 * The basic MD5 functions.
 *
 * F is optimized compared to its RFC 1321 definition just like in Colin
 * Plumb's implementation.
 */
#define F(x, y, z)	((z) ^ ((x) & ((y) ^ (z))))
#define G(x, y, z)	((y) ^ ((z) & ((x) ^ (y))))
#define H(x, y, z)	((x) ^ (y) ^ (z))
#define I(x, y, z)	((y) ^ ((x) | ~(z)))

/*
 * The MD5 transformation for all four rounds.
 */
#define STEP(f, a, b, c, d, x, t, s) \
    (a) += f((b), (c), (d)) + (x) + (t); \
    (a) = (((a) << (s)) | (((a) & 0xffffffff) >> (32 - (s)))); \
    (a) += (b);

/*
 * SET reads 4 input bytes in little-endian byte order and stores them
 * in a properly aligned word in host byte order.
 *
 * The check for little-endian architectures which tolerate unaligned
 * memory accesses is just an optimization.  Nothing will break if it
 * doesn't work.
 */
#if defined(__i386__) || defined(__vax__)
# define SET(n) (*(MD5_u32plus *)&ptr[(n) * 4])
# define GET(n) SET(n)
#else
# define SET(n) \
    (ctx->block[(n)] = \
    (MD5_u32plus)ptr[(n) * 4] | \
    ((MD5_u32plus)ptr[(n) * 4 + 1] << 8) | \
    ((MD5_u32plus)ptr[(n) * 4 + 2] << 16) | \
    ((MD5_u32plus)ptr[(n) * 4 + 3] << 24))
# define GET(n) \
    (ctx->block[(n)])
#endif

/*
 * This processes one or more 64-byte data blocks, but does NOT update
 * the bit counters.  There're no alignment requirements.
 */
static void *body(MD5_CTX *ctx, void *data, unsigned long size)
{
    unsigned char *ptr;
    MD5_u32plus a, b, c, d;
    MD5_u32plus saved_a, saved_b, saved_c, saved_d;

    ptr = data;

    a = ctx->a;
    b = ctx->b;
    c = ctx->c;
    d = ctx->d;

    do {
	saved_a = a;
	saved_b = b;
	saved_c = c;
	saved_d = d;

	/* Round 1 */
	STEP(F, a, b, c, d, SET(0),  0xd76aa478, 7)
	STEP(F, d, a, b, c, SET(1),  0xe8c7b756, 12)
	STEP(F, c, d, a, b, SET(2),  0x242070db, 17)
	STEP(F, b, c, d, a, SET(3),  0xc1bdceee, 22)
	STEP(F, a, b, c, d, SET(4),  0xf57c0faf, 7)
	STEP(F, d, a, b, c, SET(5),  0x4787c62a, 12)
	STEP(F, c, d, a, b, SET(6),  0xa8304613, 17)
	STEP(F, b, c, d, a, SET(7),  0xfd469501, 22)
	STEP(F, a, b, c, d, SET(8),  0x698098d8, 7)
	STEP(F, d, a, b, c, SET(9),  0x8b44f7af, 12)
	STEP(F, c, d, a, b, SET(10), 0xffff5bb1, 17)
	STEP(F, b, c, d, a, SET(11), 0x895cd7be, 22)
	STEP(F, a, b, c, d, SET(12), 0x6b901122, 7)
	STEP(F, d, a, b, c, SET(13), 0xfd987193, 12)
	STEP(F, c, d, a, b, SET(14), 0xa679438e, 17)
	STEP(F, b, c, d, a, SET(15), 0x49b40821, 22)

	/* Round 2 */
	STEP(G, a, b, c, d, GET(1),  0xf61e2562, 5)
	STEP(G, d, a, b, c, GET(6),  0xc040b340, 9)
	STEP(G, c, d, a, b, GET(11), 0x265e5a51, 14)
	STEP(G, b, c, d, a, GET(0),  0xe9b6c7aa, 20)
	STEP(G, a, b, c, d, GET(5),  0xd62f105d, 5)
	STEP(G, d, a, b, c, GET(10), 0x02441453, 9)
	STEP(G, c, d, a, b, GET(15), 0xd8a1e681, 14)
	STEP(G, b, c, d, a, GET(4),  0xe7d3fbc8, 20)
	STEP(G, a, b, c, d, GET(9),  0x21e1cde6, 5)
	STEP(G, d, a, b, c, GET(14), 0xc33707d6, 9)
	STEP(G, c, d, a, b, GET(3),  0xf4d50d87, 14)
	STEP(G, b, c, d, a, GET(8),  0x455a14ed, 20)
	STEP(G, a, b, c, d, GET(13), 0xa9e3e905, 5)
	STEP(G, d, a, b, c, GET(2),  0xfcefa3f8, 9)
	STEP(G, c, d, a, b, GET(7),  0x676f02d9, 14)
	STEP(G, b, c, d, a, GET(12), 0x8d2a4c8a, 20)

	/* Round 3 */
	STEP(H, a, b, c, d, GET(5),  0xfffa3942, 4)
	STEP(H, d, a, b, c, GET(8),  0x8771f681, 11)
	STEP(H, c, d, a, b, GET(11), 0x6d9d6122, 16)
	STEP(H, b, c, d, a, GET(14), 0xfde5380c, 23)
	STEP(H, a, b, c, d, GET(1),  0xa4beea44, 4)
	STEP(H, d, a, b, c, GET(4),  0x4bdecfa9, 11)
	STEP(H, c, d, a, b, GET(7),  0xf6bb4b60, 16)
	STEP(H, b, c, d, a, GET(10), 0xbebfbc70, 23)
	STEP(H, a, b, c, d, GET(13), 0x289b7ec6, 4)
	STEP(H, d, a, b, c, GET(0),  0xeaa127fa, 11)
	STEP(H, c, d, a, b, GET(3),  0xd4ef3085, 16)
	STEP(H, b, c, d, a, GET(6),  0x04881d05, 23)
	STEP(H, a, b, c, d, GET(9),  0xd9d4d039, 4)
	STEP(H, d, a, b, c, GET(12), 0xe6db99e5, 11)
	STEP(H, c, d, a, b, GET(15), 0x1fa27cf8, 16)
	STEP(H, b, c, d, a, GET(2),  0xc4ac5665, 23)

	/* Round 4 */
	STEP(I, a, b, c, d, GET(0),  0xf4292244, 6)
	STEP(I, d, a, b, c, GET(7),  0x432aff97, 10)
	STEP(I, c, d, a, b, GET(14), 0xab9423a7, 15)
	STEP(I, b, c, d, a, GET(5),  0xfc93a039, 21)
	STEP(I, a, b, c, d, GET(12), 0x655b59c3, 6)
	STEP(I, d, a, b, c, GET(3),  0x8f0ccc92, 10)
	STEP(I, c, d, a, b, GET(10), 0xffeff47d, 15)
	STEP(I, b, c, d, a, GET(1),  0x85845dd1, 21)
	STEP(I, a, b, c, d, GET(8),  0x6fa87e4f, 6)
	STEP(I, d, a, b, c, GET(15), 0xfe2ce6e0, 10)
	STEP(I, c, d, a, b, GET(6),  0xa3014314, 15)
	STEP(I, b, c, d, a, GET(13), 0x4e0811a1, 21)
	STEP(I, a, b, c, d, GET(4),  0xf7537e82, 6)
	STEP(I, d, a, b, c, GET(11), 0xbd3af235, 10)
	STEP(I, c, d, a, b, GET(2),  0x2ad7d2bb, 15)
	STEP(I, b, c, d, a, GET(9),  0xeb86d391, 21)

	a += saved_a;
	b += saved_b;
	c += saved_c;
	d += saved_d;

	ptr += MD5_BLOCK_SZ;
    } while (size -= MD5_BLOCK_SZ);

    ctx->a = a;
    ctx->b = b;
    ctx->c = c;
    ctx->d = d;

    return ptr;
}

void MD5_Init(MD5_CTX *ctx)
{
    ctx->a = 0x67452301;
    ctx->b = 0xefcdab89;
    ctx->c = 0x98badcfe;
    ctx->d = 0x10325476;

    ctx->lo = 0;
    ctx->hi = 0;
}

void MD5_Update(MD5_CTX *ctx, void *data, unsigned long size)
{
    MD5_u32plus saved_lo;
    unsigned long used, free;

    saved_lo = ctx->lo;
    if ((ctx->lo = (saved_lo + size) & 0x1fffffff) < saved_lo)
	ctx->hi++;

    ctx->hi += size >> 29;

    used = saved_lo & 0x3f;

    if (used)
    {
	free = MD5_BLOCK_SZ - used;

	if (size < free)
	{
	    memcpy(&ctx->buffer[used], data, size);
	    return;
	}

	memcpy(&ctx->buffer[used], data, free);
	data = (unsigned char *)data + free;
	size -= free;
	body(ctx, ctx->buffer, MD5_BLOCK_SZ);
    }

    if (size >= MD5_BLOCK_SZ)
    {
	data = body(ctx, data, size & ~(unsigned long)0x3f);
	size &= 0x3f;
    }

    memcpy(ctx->buffer, data, size);
}

void MD5_Final(unsigned char *result, MD5_CTX *ctx)
{
    unsigned long used, free;

    used = ctx->lo & 0x3f;

    ctx->buffer[used++] = 0x80;

    free = MD5_BLOCK_SZ - used;

    if (free < 8)
    {
	memset(&ctx->buffer[used], 0, free);
	body(ctx, ctx->buffer, MD5_BLOCK_SZ);
	used = 0;
	free = MD5_BLOCK_SZ;
    }

    memset(&ctx->buffer[used], 0, free - 8);

    ctx->lo <<= 3;
    ctx->buffer[56] = ctx->lo;
    ctx->buffer[57] = ctx->lo >> 8;
    ctx->buffer[58] = ctx->lo >> 16;
    ctx->buffer[59] = ctx->lo >> 24;
    ctx->buffer[60] = ctx->hi;
    ctx->buffer[61] = ctx->hi >> 8;
    ctx->buffer[62] = ctx->hi >> 16;
    ctx->buffer[63] = ctx->hi >> 24;

    body(ctx, ctx->buffer, MD5_BLOCK_SZ);

    result[0]  = ctx->a;
    result[1]  = ctx->a >> 8;
    result[2]  = ctx->a >> 16;
    result[3]  = ctx->a >> 24;
    result[4]  = ctx->b;
    result[5]  = ctx->b >> 8;
    result[6]  = ctx->b >> 16;
    result[7]  = ctx->b >> 24;
    result[8]  = ctx->c;
    result[9]  = ctx->c >> 8;
    result[10] = ctx->c >> 16;
    result[11] = ctx->c >> 24;
    result[12] = ctx->d;
    result[13] = ctx->d >> 8;
    result[14] = ctx->d >> 16;
    result[15] = ctx->d >> 24;

    memset(ctx, 0, sizeof(*ctx));
}


================================================
FILE: md5.h
================================================
/*
 * This is an OpenSSL-compatible implementation of the RSA Data Security,
 * Inc. MD5 Message-Digest Algorithm.
 *
 * Written by Solar Designer <solar at openwall.com> in 2001, and placed
 * in the public domain.  See md5.c for more information.
 */

#ifndef __MD5_H__
#define __MD5_H__

#define	MD5_DIGEST_SZ	16
#define	MD5_BLOCK_SZ	64

/* Any 32-bit or wider unsigned integer data type will do */
typedef unsigned long MD5_u32plus;

typedef struct {
    MD5_u32plus lo, hi;
    MD5_u32plus a, b, c, d;
    unsigned char buffer[MD5_BLOCK_SZ];
    MD5_u32plus block[MD5_DIGEST_SZ];
} MD5_CTX;

extern void MD5_Init(MD5_CTX *ctx);
extern void MD5_Update(MD5_CTX *ctx, void *data, unsigned long size);
extern void MD5_Final(unsigned char *result, MD5_CTX *ctx);

#endif /* __MD5_H__ */


================================================
FILE: timing_mach.c
================================================
#define _POSIX_C_SOURCE 200809L
#include <unistd.h>

#include <time.h>
#include "timing_mach.h"

/* inline functions - maintain ANSI C compatibility */
#ifdef TIMING_C99
/* *** */
/* C99 */

extern double timespec2secd(const struct timespec *ts_in);
extern void secd2timespec(struct timespec *ts_out, const double sec_d);
extern void timespec_monodiff_lmr(struct timespec *ts_out,
                                  const struct timespec *ts_in);
extern void timespec_monodiff_rml(struct timespec *ts_out,
                                  const struct timespec *ts_in);
extern void timespec_monoadd(struct timespec *ts_out,
                             const struct timespec *ts_in);

#endif

#ifdef __MACH__
/* ******** */
/* __MACH__ */

#include <mach/mach_time.h>
#include <mach/mach.h>
#include <mach/clock.h>

/* timing struct for osx */
static struct TimingMach {
    mach_timebase_info_data_t timebase;
    clock_serv_t cclock;
} timing_mach_g;

/* mach clock port */
extern mach_port_t clock_port;

int timing_mach_init (void) {
    int retval = mach_timebase_info(&timing_mach_g.timebase);
    if (retval != 0) return retval;
    retval = host_get_clock_service(mach_host_self(),
                                    CALENDAR_CLOCK, &timing_mach_g.cclock);
    return retval;
}

int clock_gettime(clockid_t id, struct timespec *tspec) {
    mach_timespec_t mts;
    int retval = 0;
    if (id == CLOCK_REALTIME) {
        retval = clock_get_time(timing_mach_g.cclock, &mts);
        if (retval != 0) return retval;
        tspec->tv_sec = mts.tv_sec;
        tspec->tv_nsec = mts.tv_nsec;
    } else if (id == CLOCK_MONOTONIC) {
        retval = clock_get_time(clock_port, &mts);
        if (retval != 0) return retval;
        tspec->tv_sec = mts.tv_sec;
        tspec->tv_nsec = mts.tv_nsec;
    } else {
        /* only CLOCK_MONOTOIC and CLOCK_REALTIME clocks supported */
        return -1;
    }
    return 0;
}

int clock_nanosleep_abstime(const struct timespec *req) {
    struct timespec ts_delta;
    int retval = clock_gettime(CLOCK_MONOTONIC, &ts_delta);
    if (retval != 0) return retval;
    timespec_monodiff_rml (&ts_delta, req);
    /* mach does not properly return remainder from nanosleep */
    retval = nanosleep(&ts_delta, NULL);
    return retval;
}

/* __MACH__ */
/* ******** */
#endif

int itimer_start (struct timespec *ts_target, const struct timespec *ts_step) {
    int retval = clock_gettime(CLOCK_MONOTONIC, ts_target);
    if (retval != 0) return retval;
    /* add step size to current monotonic time */
    timespec_monoadd(ts_target, ts_step);
    return retval;
}

int itimer_step (struct timespec *ts_target, const struct timespec *ts_step) {
    int retval = clock_nanosleep_abstime(ts_target);
    if (retval != 0) return retval;
    /* move target along */
    timespec_monoadd(ts_target, ts_step);
    return retval;
}



================================================
FILE: timing_mach.h
================================================
#ifndef TIMING_MACH_H
#define TIMING_MACH_H
/* ************* */
/* TIMING_MACH_H */

/* C99 check */
#if defined(__STDC__)
# if defined(__STDC_VERSION__)
#  if (__STDC_VERSION__ >= 199901L)
#   define TIMING_C99
#  endif
# endif
#endif

#include <time.h>

#define TIMING_GIGA (1000000000)
#define TIMING_NANO (1e-9)

/* inline functions - maintain ANSI C compatibility */
#ifndef TIMING_C99
/* this is a bad hack that makes the functions static in a header file.
   Compiler warnings about unused functions will plague anyone using this code with ANSI C. */
#define inline static
#endif

/* timespec to double */
inline double timespec2secd(const struct timespec *ts_in) {
    return ((double) ts_in->tv_sec) + ((double) ts_in->tv_nsec ) * TIMING_NANO;
}

/* double sec to timespec */
inline void secd2timespec(struct timespec *ts_out, const double sec_d) {
    ts_out->tv_sec = (time_t) (sec_d);
    ts_out->tv_nsec = (long) ((sec_d - (double) ts_out->tv_sec) * TIMING_GIGA);
}

/* timespec difference (monotonic) left - right */
inline void timespec_monodiff_lmr(struct timespec *ts_out,
                                    const struct timespec *ts_in) {
    /* out = out - in,
       where out > in
     */
    ts_out->tv_sec = ts_out->tv_sec - ts_in->tv_sec;
    ts_out->tv_nsec = ts_out->tv_nsec - ts_in->tv_nsec;
    if (ts_out->tv_nsec < 0) {
        ts_out->tv_sec = ts_out->tv_sec - 1;
        ts_out->tv_nsec = ts_out->tv_nsec + TIMING_GIGA;
    }
}

/* timespec difference (monotonic) right - left */
inline void timespec_monodiff_rml(struct timespec *ts_out,
                                    const struct timespec *ts_in) {
    /* out = in - out,
       where in > out
     */
    ts_out->tv_sec = ts_in->tv_sec - ts_out->tv_sec;
    ts_out->tv_nsec = ts_in->tv_nsec - ts_out->tv_nsec;
    if (ts_out->tv_nsec < 0) {
        ts_out->tv_sec = ts_out->tv_sec - 1;
        ts_out->tv_nsec = ts_out->tv_nsec + TIMING_GIGA;
    }
}

/* timespec addition (monotonic) */
inline void timespec_monoadd(struct timespec *ts_out,
                             const struct timespec *ts_in) {
    /* out = in + out */
    ts_out->tv_sec = ts_out->tv_sec + ts_in->tv_sec;
    ts_out->tv_nsec = ts_out->tv_nsec + ts_in->tv_nsec;
    if (ts_out->tv_nsec >= TIMING_GIGA) {
        ts_out->tv_sec = ts_out->tv_sec + 1;
        ts_out->tv_nsec = ts_out->tv_nsec - TIMING_GIGA;
    }
}

#ifndef TIMING_C99
#undef inline
#endif

#ifdef __MACH__
/* ******** */
/* __MACH__ */

/* only CLOCK_REALTIME and CLOCK_MONOTONIC are emulated */
#ifndef CLOCK_REALTIME
# define CLOCK_REALTIME 0
#endif
#ifndef CLOCK_MONOTONIC
# define CLOCK_MONOTONIC 1
#endif

/* typdef POSIX clockid_t */
//typedef int clockid_t;

/* initialize mach timing */
int timing_mach_init (void);

/* clock_gettime - emulate POSIX */
int clock_gettime(const clockid_t id, struct timespec *tspec);

/* clock_nanosleep for CLOCK_MONOTONIC and TIMER_ABSTIME */
int clock_nanosleep_abstime(const struct timespec *req);

/* __MACH__ */
/* ******** */
#else
/* ***** */
/* POSIX */

/* clock_nanosleep for CLOCK_MONOTONIC and TIMER_ABSTIME */
# define clock_nanosleep_abstime(req) \
         clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, (req), NULL)

/* POSIX */
/* ***** */
#endif

/* timer functions that make use of clock_nanosleep_abstime
   For POSIX systems, it is recommended to use POSIX timers and signals.
   For Mac OSX (mach), there are no POSIX timers so these functions are very helpful.
*/

/* Sets absolute time ts_target to ts_step after current time */
int itimer_start (struct timespec *ts_target, const struct timespec *ts_step);

/* Nanosleeps to ts_target then adds ts_step to ts_target for next iteration */
int itimer_step (struct timespec *ts_target, const struct timespec *ts_step);

/* TIMING_MACH_H */
/* ************* */
#endif


================================================
FILE: utils.c
================================================
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include "utils.h"

/**
 * generateRandomNonce - Generates a random nonce.
 * @nonce: The array to store the generated nonce.
 */
void generateRandomNonce(unsigned char nonce[16])
{
    srand((unsigned int)time(NULL));
    for (int i = 0; i < 16; ++i)
    {
        nonce[i] = (unsigned char)rand();
    }
}
/**
 * getHexRepresentation - Converts a buffer to its hexadecimal representation.
 * @buffer: Pointer to the input buffer.
 * @size: Size of the input buffer.
 * @hexString: Pointer to the output string for the hexadecimal representation.
 *
 * This function converts the content of the input buffer to its hexadecimal
 * representation and stores it in the output string. The resulting string is
 * null-terminated.
 */
void getHexRepresentation(const unsigned char *buffer, size_t size, char *hexString)
{
    for (size_t i = 0; i < size; ++i)
    {
        sprintf(hexString + i * 2, "%02x", buffer[i]);
    }
}
/**
 * isStringNotEmpty - Checks if a C string is not empty.
 * @str: Pointer to the C string to be checked.
 *
 * This function determines whether the given C string is not empty.
 * It returns 1 if the string is not empty and not NULL, and 0 otherwise.
 *
 * Returns: 1 if the string is not empty, 0 otherwise.
 */
int isStringNotEmpty(const char *str)
{
    return (str != NULL && strlen(str) > 0);
}
/**
 * isauth - Check authentication based on user and hash.
 * @opt_authuser: User-provided authentication username.
 * @username: Server's expected username for comparison.
 * @receivedhash: User-provided received hash for comparison.
 * @serverdigest: Server's expected hash for comparison.
 *
 * It compares opt_authuser with username and receivedhash with serverdigest.
 * Returns: 1 if both username and hash match, 0 otherwise.
 */
int isauth(const char *opt_authuser, const unsigned char *username,
           const char *receivedhash, const char *serverdigest)
{
    // Check if opt_authuser is not empty and equal to username
    int userMatch = (opt_authuser != NULL && *opt_authuser != '\0' &&
                     strncmp(opt_authuser, (const char *)username, USERNAME_SIZE) == 0);

    // Check if receivedhash is not empty and equal to serverdigest
    int hashMatch = (receivedhash != NULL && *receivedhash != '\0' &&
                     strcmp(receivedhash, serverdigest) == 0);

    // Return 1 if both conditions are true, 0 otherwise
    return (userMatch && hashMatch);
}

================================================
FILE: utils.h
================================================
#ifndef UTILS_H
#define UTILS_H
#define USERNAME_SIZE 16
#define HASH_SIZE 32
#define AUTHSTR_SIZE 32
// function to generate random Nonce to send to client for md5 hash
void generateRandomNonce(unsigned char nonce[16]);
// This function converts the content of the input buffer to its hexadecimal
void getHexRepresentation(const unsigned char *buffer, size_t size, char *hexString);
// Checks if a C string is not empty.
int isStringNotEmpty(const char *str);
// This function checks authentication based on the provided username and hash.
int isauth(const char *opt_authuser, const unsigned char *username, const char *receivedhash, const char *serverdigest);
#endif
Download .txt
gitextract_hqjlj7ey/

├── .dockerignore
├── .gitignore
├── Dockerfile
├── LICENSE
├── Makefile.am
├── README.md
├── bclient.pl
├── bootstrap
├── bserver.pl
├── btest.c
├── build.sh
├── configure.ac
├── docker-compose.yml
├── install-sh
├── md5.c
├── md5.h
├── timing_mach.c
├── timing_mach.h
├── utils.c
└── utils.h
Download .txt
SYMBOL INDEX (90 symbols across 6 files)

FILE: btest.c
  type cmdStruct (line 36) | struct cmdStruct
  type statStruct (line 48) | struct statStruct
  type cmdStruct (line 63) | struct cmdStruct
  type cmdStruct (line 64) | struct cmdStruct
  type statStruct (line 65) | struct statStruct
  type statStruct (line 66) | struct statStruct
  type statStruct (line 67) | struct statStruct
  type cmdStruct (line 68) | struct cmdStruct
  type cmdStruct (line 69) | struct cmdStruct
  type timespec (line 70) | struct timespec
  type timespec (line 70) | struct timespec
  type timespec (line 71) | struct timespec
  type timespec (line 72) | struct timespec
  type timespec (line 72) | struct timespec
  type timespec (line 73) | struct timespec
  type timespec (line 73) | struct timespec
  type timespec (line 74) | struct timespec
  type timespec (line 82) | struct timespec
  function main (line 100) | int main(int argc, char **argv)
  function usage (line 187) | void usage()
  function usage_long (line 194) | void usage_long()
  function client (line 218) | int client()
  function server (line 382) | int server()
  function server_conn (line 432) | int server_conn(int cmdsock, char *remoteIP)
  function packShortLE (line 525) | void packShortLE(unsigned char *buf, unsigned int res)
  function packLongLE (line 535) | void packLongLE(unsigned char *buf, unsigned long res)
  function packLongBE (line 545) | void packLongBE(unsigned char *buf, unsigned long res)
  function unpackShortLE (line 555) | void unpackShortLE(unsigned char *buf, unsigned int *pres)
  function unpackLongLE (line 566) | void unpackLongLE(unsigned char *buf, unsigned long *pres)
  function unpackLongBE (line 577) | void unpackLongBE(unsigned char *buf, unsigned long *pres)
  function unpackCmdStr (line 588) | struct cmdStruct unpackCmdStr(unsigned char *cmdStr)
  function packCmdStr (line 609) | void packCmdStr(struct cmdStruct *pcmd, unsigned char *buf)
  function unpackStatStr (line 630) | struct statStruct unpackStatStr(unsigned char *buf)
  function printStatStruct (line 649) | void printStatStruct(char *msg, struct statStruct *pstat)
  function packStatStr (line 682) | void packStatStr(struct statStruct *pstat, unsigned char *buf)
  type cmdStruct (line 701) | struct cmdStruct
  type timespec (line 705) | struct timespec
  type timespec (line 706) | struct timespec
  type timespec (line 707) | struct timespec
  type cmdStruct (line 713) | struct cmdStruct
  type statStruct (line 790) | struct statStruct
  type cmdStruct (line 794) | struct cmdStruct
  type timespec (line 797) | struct timespec
  type timespec (line 798) | struct timespec
  type timespec (line 799) | struct timespec
  type cmdStruct (line 808) | struct cmdStruct
  function test_udp (line 882) | int test_udp(struct cmdStruct cmd, int cmdsock, char *remoteIP)
  type cmdStruct (line 1062) | struct cmdStruct
  type cmdStruct (line 1067) | struct cmdStruct
  function test_tcp (line 1079) | int test_tcp(struct cmdStruct cmd, int cmdsock)
  function timespec_diff (line 1106) | void timespec_diff(struct timespec *start, struct timespec *stop,
  function timespec_add (line 1124) | void timespec_add(struct timespec *t1, struct timespec *t2)
  function timespec_cmp (line 1136) | int timespec_cmp(struct timespec *t1, struct timespec *t2)
  function timespec_dump (line 1163) | void timespec_dump(char *msg, struct timespec *t1)
  function dumpBuffer (line 1168) | void dumpBuffer(const char *msg, unsigned char *buffer, int len)
  function calc_interval (line 1179) | void calc_interval(struct timespec *ts, unsigned long tx_speed, unsigned...

FILE: md5.c
  function MD5_Init (line 173) | void MD5_Init(MD5_CTX *ctx)
  function MD5_Update (line 184) | void MD5_Update(MD5_CTX *ctx, void *data, unsigned long size)
  function MD5_Final (line 222) | void MD5_Final(unsigned char *result, MD5_CTX *ctx)

FILE: md5.h
  type MD5_u32plus (line 16) | typedef unsigned long MD5_u32plus;
  type MD5_CTX (line 18) | typedef struct {

FILE: timing_mach.c
  type timespec (line 12) | struct timespec
  type timespec (line 13) | struct timespec
  type timespec (line 14) | struct timespec
  type timespec (line 15) | struct timespec
  type timespec (line 16) | struct timespec
  type timespec (line 17) | struct timespec
  type timespec (line 18) | struct timespec
  type timespec (line 19) | struct timespec
  type TimingMach (line 32) | struct TimingMach {
  function timing_mach_init (line 40) | int timing_mach_init (void) {
  function clock_gettime (line 48) | int clock_gettime(clockid_t id, struct timespec *tspec) {
  function clock_nanosleep_abstime (line 68) | int clock_nanosleep_abstime(const struct timespec *req) {
  function itimer_start (line 82) | int itimer_start (struct timespec *ts_target, const struct timespec *ts_...
  function itimer_step (line 90) | int itimer_step (struct timespec *ts_target, const struct timespec *ts_s...

FILE: timing_mach.h
  function timespec2secd (line 28) | inline double timespec2secd(const struct timespec *ts_in) {
  function secd2timespec (line 33) | inline void secd2timespec(struct timespec *ts_out, const double sec_d) {
  function timespec_monodiff_lmr (line 39) | inline void timespec_monodiff_lmr(struct timespec *ts_out,
  function timespec_monodiff_rml (line 53) | inline void timespec_monodiff_rml(struct timespec *ts_out,
  function timespec_monoadd (line 67) | inline void timespec_monoadd(struct timespec *ts_out,
  type timespec (line 101) | struct timespec
  type timespec (line 104) | struct timespec
  type timespec (line 126) | struct timespec
  type timespec (line 126) | struct timespec
  type timespec (line 129) | struct timespec
  type timespec (line 129) | struct timespec

FILE: utils.c
  function generateRandomNonce (line 11) | void generateRandomNonce(unsigned char nonce[16])
  function getHexRepresentation (line 29) | void getHexRepresentation(const unsigned char *buffer, size_t size, char...
  function isStringNotEmpty (line 45) | int isStringNotEmpty(const char *str)
  function isauth (line 59) | int isauth(const char *opt_authuser, const unsigned char *username,
Condensed preview — 20 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (88K chars).
[
  {
    "path": ".dockerignore",
    "chars": 0,
    "preview": ""
  },
  {
    "path": ".gitignore",
    "chars": 37,
    "preview": "btest\nrust_btest-server/**\n.vscode/**"
  },
  {
    "path": "Dockerfile",
    "chars": 973,
    "preview": "# Use a minimal Alpine Linux base image with build tools\nFROM alpine:latest\n\n# Set the working directory inside the cont"
  },
  {
    "path": "LICENSE",
    "chars": 1071,
    "preview": "MIT License\n\nCopyright (c) 2016 Alex Samorukov\n\nPermission is hereby granted, free of charge, to any person obtaining a "
  },
  {
    "path": "Makefile.am",
    "chars": 88,
    "preview": "bin_PROGRAMS = btest\nbtest_SOURCES = btest.c md5.c timing_mach.c\nAM_LDFLAGS = -lpthread\n"
  },
  {
    "path": "README.md",
    "chars": 6191,
    "preview": "# Mikrotik bandwith test protocol description\n## about\nThis is attempt to create opensource version of the btest tool. C"
  },
  {
    "path": "bclient.pl",
    "chars": 3215,
    "preview": "#!/usr/bin/env perl\n\n# this is PoC for the Mikrotik bandwith server. On mikorotik use\n# /tool bandwidth-test <ip> durati"
  },
  {
    "path": "bootstrap",
    "chars": 3795,
    "preview": "#! /bin/sh\n\n# bootstrap — generic bootstrap/autogen.sh script for autotools projects\n#\n# Copyright © 2002—2015 Sam Hocev"
  },
  {
    "path": "bserver.pl",
    "chars": 3466,
    "preview": "#!/usr/bin/env perl\n\n# this is PoC for the Mikrotik bandwith server. On mikorotik use\n# /tool bandwidth-test <ip> durati"
  },
  {
    "path": "btest.c",
    "chars": 29217,
    "preview": "/* Program to emulate the Mikrotik Bandwidth test protocol */\n#include <stdio.h>\n#include <sys/socket.h>\n#include <netin"
  },
  {
    "path": "build.sh",
    "chars": 44,
    "preview": "gcc -o btest *.c -lpthread && chmod +x btest"
  },
  {
    "path": "configure.ac",
    "chars": 408,
    "preview": "AC_INIT([btest], [1.0], [bug-report@address])\nAM_INIT_AUTOMAKE\nAC_ARG_ENABLE(debugging, [  --disable-debugging          "
  },
  {
    "path": "docker-compose.yml",
    "chars": 270,
    "preview": "version: '3'\n\nservices:\n  btest-service:\n    build:\n      context: .\n      dockerfile: Dockerfile\n    container_name: bt"
  },
  {
    "path": "install-sh",
    "chars": 13997,
    "preview": "#!/bin/sh\n# install - install a program, script, or datafile\n\nscriptversion=2011-11-20.07; # UTC\n\n# This originates from"
  },
  {
    "path": "md5.c",
    "chars": 7686,
    "preview": "/*\n * This is an OpenSSL-compatible implementation of the RSA Data Security,\n * Inc. MD5 Message-Digest Algorithm.\n *\n *"
  },
  {
    "path": "md5.h",
    "chars": 787,
    "preview": "/*\n * This is an OpenSSL-compatible implementation of the RSA Data Security,\n * Inc. MD5 Message-Digest Algorithm.\n *\n *"
  },
  {
    "path": "timing_mach.c",
    "chars": 2872,
    "preview": "#define _POSIX_C_SOURCE 200809L\n#include <unistd.h>\n\n#include <time.h>\n#include \"timing_mach.h\"\n\n/* inline functions - m"
  },
  {
    "path": "timing_mach.h",
    "chars": 3814,
    "preview": "#ifndef TIMING_MACH_H\n#define TIMING_MACH_H\n/* ************* */\n/* TIMING_MACH_H */\n\n/* C99 check */\n#if defined(__STDC_"
  },
  {
    "path": "utils.c",
    "chars": 2498,
    "preview": "#include <stdio.h>\n#include <stdlib.h>\n#include <time.h>\n#include <string.h>\n#include \"utils.h\"\n\n/**\n * generateRandomNo"
  },
  {
    "path": "utils.h",
    "chars": 668,
    "preview": "#ifndef UTILS_H\n#define UTILS_H\n#define USERNAME_SIZE 16\n#define HASH_SIZE 32\n#define AUTHSTR_SIZE 32\n// function to gen"
  }
]

About this extraction

This page contains the full source code of the samm-git/btest-opensource GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 20 files (79.2 KB), approximately 26.6k tokens, and a symbol index with 90 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!