
#include "config.h"

#if defined(_SQUID_LINUX_)
#else
#define FD_SETSIZE 1024
#endif

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#if defined(_SQUID_SUNOS_)
#elif defined(_SQUID_LINUX_)
#else
#include <sys/select.h>
#endif
#include <signal.h>
#include <time.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <assert.h>
#include <math.h>
#ifdef _SQUID_SUNOS_
#include <search.h>
#endif /* _SQUID_SUNOS_ */

#define HTTP_PORT 8080
#define MAX_FDS 1024
#define READ_BUF_SZ 4096
#define FD_READ 1
#define FD_WRITE 2
#define SIZE_DIST_MAX 512

typedef void (CB) (int, void *);

struct _f {
    CB *rcb;
    CB *wcb;
    void *rdata;
    void *wdata;
};

struct _g {
	int hdrlen;
	int bdylen;
	int totlen;
	int offset;
	char *hdrbuf;
};

struct _d {
    double lval;
    double uval;
    double lcdf;
    double ucdf;
};

int get_random_size(void);
double extrapolate_size(double rv, struct _d *dist, int n);
void load_filesize_dist(char *file);
char * mkrfc850(time_t t);
void fd_close(int fd);
void fd_open(int fd, CB *cb, void *data, int rw);
void sig_intr(int sig);
void write_reply(int fd, void *data);
void handle_request(int listen_sock, void *junk);
int listen_sock(void);
void usage(void);
int main(int argc, char *argv[]);

static char *progname;
static int SIZE_DIST_SZ = 0;
static int http_port = HTTP_PORT;
static int maxfd = 0;
static int nfds = 0;
static int nrequests;
static int opt_ims = 0;
static int reqpersec;
static struct _d SIZE_DIST[SIZE_DIST_MAX];
static struct _f FD[MAX_FDS];
struct timeval now;

#ifdef _SQUID_SUNOS_
extern char *optarg;
extern int optind, opterr;
extern double drand48();
#endif /* _SQUID_SUNOS_ */

char *
mkrfc850(time_t t)
{
    static char buf[128];
    struct tm *gmt = gmtime(&t);
    buf[0] = '\0';
    (void) strftime(buf, 127, "%A, %d-%b-%y %H:%M:%S GMT", gmt);
    return buf;
}

void
fd_close(int fd)
{
    close(fd);
    memset(&FD[fd], '\0', sizeof(struct _f));
    nfds--;
    if (fd == maxfd) {
	while (FD[fd].rcb == NULL && FD[fd].wcb == NULL)
		fd--;
	maxfd = fd;
    }
}

void
fd_open(int fd, CB *cb, void *data, int rw)
{
    if (rw == FD_WRITE) {
    	FD[fd].wcb = cb;
	FD[fd].wdata = data;
    } else if (rw == FD_READ) {
    	FD[fd].rcb = cb;
	FD[fd].rdata = data;
        nfds++;
    }
    if (fd > maxfd)
	maxfd = fd;
}

void
sig_intr(int sig)
{
	fd_close(0);
	printf ("\rWaiting for open connections to finish...\n");
	signal(sig, SIG_DFL);
}

void 
write_reply(int fd, void *data)
{
	struct _g *g = data;
	int len = g->totlen - g->offset;
	int n;
	static int c = 0;
	char buf[4096];
	memset(buf, c++, 4096);
	if (g->hdrbuf) {
		memcpy(buf, g->hdrbuf, g->hdrlen);
		free(g->hdrbuf);
		g->hdrbuf = NULL;
	}
	if (len > 4096)
		len = 4096;
	n = write(fd, buf, len);
	if (n > 0)
		g->offset += n;
	if (n < 0 || g->offset >= g->totlen) {
		fd_close(fd);
		reqpersec++;
		nrequests++;
		free(g);
	}
}

void
read_request(int fd, void *junk)
{
        struct _g *g;
        char headers[4096];
        char line[1024];
        int hlen;
        int blen;
	read(fd, headers, 4096);
	blen = get_random_size() - 130;
	if (blen < 0)
		blen = 0;
	memset(headers, '\0', 4096);
	sprintf(line, "HTTP/1.0 200 OK\r\n");
	strcat(headers, line);
	sprintf(line, "Content-Type: application/octet-stream\r\n");
	strcat(headers, line);
	sprintf(line, "Content-Length: %d\r\n", blen);
	strcat(headers, line);
	sprintf(line, "Last-Modified: %s\r\n", mkrfc850(now.tv_sec));
	strcat(headers, line);
	strcat(headers, "\r\n");
	g = calloc(1, sizeof(struct _g));
	g->hdrlen = strlen(headers);
	g->bdylen = blen;
	g->totlen = g->hdrlen + g->bdylen;
	g->hdrbuf = strdup(headers);
	g->offset = 0;
	fd_open(fd, write_reply, g, FD_WRITE);
}

void
handle_request(int listen_sock, void *junk)
{
	int fd = accept(listen_sock, NULL, NULL);
	if (fd < 0) {
		perror ("accept");
		return;
	}
	fd_open(fd, read_request, NULL, FD_READ);
}

int
listen_sock(void)
{
	int fd;
	struct sockaddr_in S;
	fd = socket(AF_INET, SOCK_STREAM, 0);
	if (fd < 0) {
		perror("socket");
		exit(1);
	}
	memset(&S, '\0', sizeof(struct sockaddr_in));
	S.sin_family = AF_INET;
	S.sin_port = htons(http_port);
	S.sin_addr.s_addr = inet_addr("0.0.0.0");
	if (bind(fd, (struct sockaddr *) &S, sizeof(S)) < 0) {
		perror("bind");
		exit(1);
	}
	if (listen(fd, FD_SETSIZE) < 0) {
		perror("listen");
		exit(1);
	}
	return fd;
}

void
usage(void)
{
	fprintf(stderr, "usage: %s: -p port -h host -n max\n", progname);
}

int 
main(int argc, char *argv[])
{
    int i;
    int c;
    int dt;
    fd_set R;
    fd_set W;
    struct timeval start;
    struct timeval last;
    struct timeval to;
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);
    progname = strdup(argv[0]);
    gettimeofday(&start, NULL);
    last = start;
    while ((c = getopt(argc, argv, "p:is:")) != -1) {
        switch (c) {
	case 'p':
		http_port = atoi(optarg);
		break;
	case 'i':
		opt_ims = 1;
		break;
	case 's':
		load_filesize_dist(optarg);
		break;
	default:
		usage();
		return 1;
        }
    }
    fd_open(listen_sock(), handle_request, NULL, FD_READ);
    signal(SIGINT, sig_intr);
    signal(SIGPIPE, SIG_IGN);
    while (nfds) {
	FD_ZERO(&R);
	FD_ZERO(&W);
	to.tv_sec = 0;
	to.tv_usec = 100000;
	for (i = 1; i <= maxfd; i++) {
	    if (FD[i].rcb != NULL)
	        FD_SET(i, &R);
	    if (FD[i].wcb != NULL)
	        FD_SET(i, &W);
	}
	if (select(maxfd + 1, &R, &W, NULL, &to) < 0) {
	    if (errno != EINTR)
	        perror("select");
	    continue;
	}
	for (i = 0; i <= maxfd; i++) {
	    if (FD_ISSET(i, &R))
	    	FD[i].rcb(i, FD[i].rdata);
	    if (FD_ISSET(i, &W))
	    	FD[i].wcb(i, FD[i].wdata);
	}
	gettimeofday(&now, NULL);
	if (now.tv_sec > last.tv_sec && reqpersec) {
		last = now;
		dt = (int) (now.tv_sec - start.tv_sec);
		printf ("T+ %6d: %9d req (%+4d), %4d conn, %3d/sec avg\n",
			dt,
			nrequests,
			reqpersec,
			nfds,
			(int) (nrequests / dt));
		reqpersec = 0;
	}
    }
    return 0;
}

int
hist_compare(const void *a, const void *b)
{
	const struct _d *A = a;
	const struct _d *B = b;
	if (A->lcdf < B->lcdf)
		return -1;
	if (A->lcdf > B->ucdf)
		return 1;
	return 0;
}

int
get_random_size(void)
{
	return (int) extrapolate_size(drand48(), SIZE_DIST, SIZE_DIST_SZ);
}

double
extrapolate_size(double rv, struct _d *dist, int n)
{
	struct _d x;
	struct _d *r;
	double A,B,C,X,Y,Z;
	x.lcdf = rv;
	r = bsearch(&x, dist, n, sizeof(struct _d), hist_compare);
	assert (r != NULL);
	A = log(r->lval);
	C = log(r->uval);
	X = r->lcdf;
	Z = r->ucdf;
	Y = rv;
	B = A + (C-A) * (Y-X) / (Z-X);
	return exp(B);
}


void 
load_filesize_dist(char *file)
{
	char buf[1024];
	struct _d *d;
	double d1,d2,d3,d4;
	FILE *fp;
	fp = fopen(file, "r");
	if (fp == NULL) {
		perror(file);
		exit(1);
	}
	while(fgets(buf, 1024, fp)) {
		if (buf[0] == '#')
			continue;
		if (sscanf(buf, "%lf %lf %lf %lf", &d1,&d2,&d3,&d4) != 4)
			continue;
		assert(d4 >= 0.0);
		assert(d4 <= 1.0);
		d = &SIZE_DIST[SIZE_DIST_SZ++];
		d->lval = d1;
		d->uval = d1;
		d->lcdf = d4;
		d->ucdf = 1.0;
		if (SIZE_DIST_SZ > 1) {
			(d-1)->uval = d1;
			(d-1)->ucdf = d4;
		}
		if (SIZE_DIST_SZ == SIZE_DIST_MAX)
			break;
	}
}
