diff --git a/ChangeLog b/ChangeLog index 94bf741..3547130 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,10 @@ +2012-02-12 12:43:56 +0100 Georg Hopp + + * make http request and response childs of a common parent http message (HEAD, master) + 2012-02-12 04:13:54 +0100 Georg Hopp - * remove now obsoleted header_sort (HEAD, master) + * remove now obsoleted header_sort 2012-02-12 04:05:38 +0100 Georg Hopp diff --git a/include/class.h b/include/class.h index 8fce433..0a0315f 100644 --- a/include/class.h +++ b/include/class.h @@ -42,28 +42,44 @@ * @TODO: actually i use gcc feature ## for variadoc... think about * a way to make this standard. */ -#define _CALL(object,_iface,method,...) \ - class_ptr class = class_getClass((object)); \ - struct i_##_iface * iface; \ - if (class->init) class->init(); \ - iface = (struct i_##_iface *)class_getInterface(&class, &i_##_iface); \ - while ((NULL == iface || NULL == iface->method) && HAS_PARENT(class)) { \ - class = class->parent; \ - if (class->init) class->init(); \ - iface = (struct i_##_iface *)class_getInterface(&class, &i_##_iface); \ - }; \ - assert(NULL != iface->method); +#define _CALL(object,_iface,method,...) \ + do { \ + class_ptr class = class_getClass((object)); \ + if (class->init) class->init(); \ + iface = (struct i_##_iface *)class_getInterface(&class, &i_##_iface); \ + while ((NULL == iface || NULL == iface->method) && HAS_PARENT(class)) { \ + class = class->parent; \ + if (class->init) class->init(); \ + iface = (struct i_##_iface *)class_getInterface(&class, &i_##_iface); \ + }; \ + assert(NULL != iface->method); \ + } while(0) -#define CALL(object,_iface,method,...) \ - do { \ +#define CALL(object,_iface,method,...) \ + do { \ + struct i_##_iface * iface; \ _CALL(object, _iface, method, ##__VA_ARGS__); \ - iface->method(object, ##__VA_ARGS__); \ + iface->method(object, ##__VA_ARGS__); \ } while(0) -#define RETCALL(object,_iface,method,ret,...) \ - do { \ +#define RETCALL(object,_iface,method,ret,...) \ + do { \ + struct i_##_iface * iface; \ _CALL(object, _iface, method, ##__VA_ARGS__); \ - ret = iface->method(object, ##__VA_ARGS__); \ + ret = iface->method(object, ##__VA_ARGS__); \ + } while(0) + +#define PARENTCALL(object,_iface,method,...) \ + do { \ + struct i_##_iface * iface; \ + class_ptr class = class_getClass((object)); \ + if (class->init) class->init(); \ + assert(HAS_PARENT(class)); \ + class = class->parent; \ + if (class->init) class->init(); \ + iface = (struct i_##_iface *)class_getInterface(&class, &i_##_iface); \ + assert(NULL != iface->method); \ + iface->method(object, ##__VA_ARGS__); \ } while(0) diff --git a/include/http/message.h b/include/http/message.h new file mode 100644 index 0000000..8b3459d --- /dev/null +++ b/include/http/message.h @@ -0,0 +1,28 @@ +#ifndef __HTTP_MESSAGE__ +#define __HTTP_MESSAGE__ + +#include "class.h" +#include "http/header.h" + +typedef enum e_HttpMessageType { + HTTP_MESSAGE_BUFFERED=0, + HTTP_MESSAGE_PIPED +} HttpMessageType; + + +CLASS(HttpMessage) { + char * version; + + HttpHeader header; + + HttpMessageType type; + union { + char * buffer; + int handle; + } body; + int nbody; +}; + +#endif // __HTTP_MESSAGE__ + +// vim: set ts=4 sw=4: diff --git a/include/http/request.h b/include/http/request.h index d8b6c6f..79c346d 100644 --- a/include/http/request.h +++ b/include/http/request.h @@ -2,17 +2,13 @@ #define __HTTP_REQUEST_H__ #include "class.h" -#include "http/header.h" +#include "http/message.h" CLASS(HttpRequest) { + EXTENDS(HttpMessage); + char * method; char * uri; - char * version; - - HttpHeader header; - - char * body; - int nbody; }; char httpRequestHasKeepAlive(HttpRequest); diff --git a/include/http/request/parser.h b/include/http/request/parser.h index 597df4b..d0c61e6 100644 --- a/include/http/request/parser.h +++ b/include/http/request/parser.h @@ -7,6 +7,9 @@ #define HTTP_REQUEST_PARSER_READ_CHUNK 1024 +#define REMAINS(pars) \ + ((pars)->buffer_used - ((pars)->cur_data - (pars)->buffer)) + typedef enum e_HttpRequestState { HTTP_REQUEST_GARBAGE=0, @@ -32,6 +35,8 @@ CLASS(HttpRequestParser) { size_t httpRequestParserRead(HttpRequestParser, int); void httpRequestParserParse(HttpRequestParser); +void httpRequestParserGetBody(HttpRequestParser); + void httpRequestParserGetRequestLine(HttpRequest, char *); void httpRequestParserGetHeader(HttpRequest, char *); diff --git a/include/http/response.h b/include/http/response.h index 2df595f..fadb539 100644 --- a/include/http/response.h +++ b/include/http/response.h @@ -4,18 +4,14 @@ #include #include "class.h" -#include "http/header.h" +#include "http/message.h" CLASS(HttpResponse) { - char * version; + EXTENDS(HttpMessage); + unsigned int status; char * reason; - - HttpHeader header; - - char * body; - int nbody; }; HttpResponse httpResponse404(); diff --git a/include/http/worker.h b/include/http/worker.h new file mode 100644 index 0000000..1cbaa42 --- /dev/null +++ b/include/http/worker.h @@ -0,0 +1,13 @@ +#ifndef __HTTP_WORKER_H__ +#define __HTTP_WORKER_H__ + +#include "class.h" +#include "http/request/parser.h" + +CLASS(HttpWorker) { + HttpRequestParser parser; +}; + +#endif // __HTTP_WORKER_H__ + +// vim: set ts=4 sw=4: diff --git a/src/Makefile.am b/src/Makefile.am index acf744d..5ee8780 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -6,6 +6,7 @@ CLASS = class.c interface.c SOCKET = socket.c socket/accept.c socket/connect.c socket/listen.c SERVER = server.c server/run.c server/close_conn.c LOGGER = logger.c logger/stderr.c logger/syslog.c +MSG = http/message.c REQ = http/request.c http/request/queue.c http/request/has_keep_alive.c RESP = http/response.c http/response/404.c http/response/size_get.c \ http/response/to_string.c @@ -13,7 +14,7 @@ HEADER = http/header.c http/header/get.c http/header/add.c \ http/header/size_get.c http/header/to_string.c PARSER = http/request/parser.c http/request/parser/get_header.c \ http/request/parser/parse.c http/request/parser/get_request_line.c \ - http/request/parser/read.c + http/request/parser/read.c http/request/parser/get_body.c AM_CFLAGS = -Wall -I ../include/ @@ -21,6 +22,6 @@ AM_CFLAGS = -Wall -I ../include/ bin_PROGRAMS = testserver testserver_SOURCES = testserver.c \ - $(IFACE) $(CLASS) $(SOCKET) $(SERVER) $(LOGGER) $(REQ) \ + $(IFACE) $(CLASS) $(SOCKET) $(SERVER) $(LOGGER) $(MSG) $(REQ) \ $(RESP) $(HEADER) $(PARSER) signalHandling.c daemonize.c testserver_CFLAGS = -Wall -I ../include/ diff --git a/src/http/header/add.c b/src/http/header/add.c new file mode 100644 index 0000000..39edfed --- /dev/null +++ b/src/http/header/add.c @@ -0,0 +1,47 @@ +#include +#include +#include +#include + +#include "class.h" +#include "interface/class.h" +#include "http/header.h" + +static +inline +unsigned long +sdbm(const unsigned char * str) +{ + unsigned long hash = 0; + int c; + + while ((c = tolower(*str++))) + hash = c + (hash << 6) + (hash << 16) - hash; + + return hash; +} + +static +inline +int +comp(const void * _a, const void * _b) +{ + HttpHeader a = (HttpHeader)_a; + HttpHeader b = (HttpHeader)_b; + return (a->hash < b->hash)? -1 : (a->hash > b->hash)? 1 : 0; +} + +void +httpHeaderAdd(const HttpHeader * root, HttpHeader header) +{ + HttpHeader * found = tsearch(header, (void **)root, comp); + + if (*found != header) { + puts("uhh, duplicate header set. " + "This is not implemented right now. " + "Keep the first one found."); + delete(&header); + } +} + +// vim: set ts=4 sw=4: diff --git a/src/http/message.c b/src/http/message.c new file mode 100644 index 0000000..5f4f1f3 --- /dev/null +++ b/src/http/message.c @@ -0,0 +1,61 @@ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include + +#include "class.h" +#include "interface/class.h" + +#include "http/message.h" +#include "message/helper.c" + + +static +void +ctor(void * _this, va_list * params) +{ + HttpMessage this = _this; + char * version = va_arg(* params, char *); + + this->version = calloc(1, strlen(version)+1); + strcpy(this->version, version); +} + +static +void +dtor(void * _this) +{ + HttpMessage this = _this; + + _free((void **)&(this->version)); + + /** + * this is a GNU extension...anyway on most non + * GNUish systems i would not use tsearch anyway + * as the trees will be unbalanced. + */ + tdestroy(this->header, tDelete); + + switch (this->type) { + case HTTP_MESSAGE_BUFFERED: + _free((void **)&((this->body).buffer)); + break; + + case HTTP_MESSAGE_PIPED: + close((this->body).handle); + break; + + default: + break; + } +} + +INIT_IFACE(Class, ctor, dtor, NULL); +CREATE_CLASS(HttpMessage, NULL, IFACE(Class)); + +// vim: set ts=4 sw=4: diff --git a/src/http/message/helper.c b/src/http/message/helper.c new file mode 100644 index 0000000..e048efb --- /dev/null +++ b/src/http/message/helper.c @@ -0,0 +1,30 @@ +#ifndef __HTTP_MESSAGE_HELPER_C__ +#define __HTTP_MESSAGE_HELPER_C__ + +#include + +#include "class.h" +#include "interface/class.h" + + +static +inline +void +_free(void ** data) +{ + if (NULL != *data) { + free(*data); + } +} + +static +inline +void +tDelete(void * node) +{ + delete(&node); +} + +#endif // __HTTP_MESSAGE_HELPER_C__ + +// vim: set ts=4 sw=4: diff --git a/src/http/request.c b/src/http/request.c index c195831..57a44fa 100644 --- a/src/http/request.c +++ b/src/http/request.c @@ -1,35 +1,13 @@ -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif - #include #include -#include #include "class.h" #include "interface/class.h" #include "http/request.h" +#include "message/helper.c" -static -inline -void -_free(void ** data) -{ - if (NULL != *data) { - free(*data); - } -} - -static -inline -void -tDelete(void * node) -{ - delete(&node); -} - static void ctor(void * _this, va_list * params) {} @@ -40,21 +18,13 @@ dtor(void * _this) { HttpRequest this = _this; - _free((void **)&(this->version)); _free((void **)&(this->uri)); _free((void **)&(this->method)); - /** - * this is a GNU extension...anyway on most non - * GNUish systems i would not use tsearch anyway - * as the trees will be unbalanced. - */ - tdestroy(this->header, tDelete); - - _free((void **)&(this->body)); + PARENTCALL(_this, Class, dtor); } INIT_IFACE(Class, ctor, dtor, NULL); -CREATE_CLASS(HttpRequest, NULL, IFACE(Class)); +CREATE_CLASS(HttpRequest, HttpMessage, IFACE(Class)); // vim: set ts=4 sw=4: diff --git a/src/http/request/has_keep_alive.c b/src/http/request/has_keep_alive.c index 3256905..e34f852 100644 --- a/src/http/request/has_keep_alive.c +++ b/src/http/request/has_keep_alive.c @@ -1,6 +1,7 @@ #include #include +#include "http/message.h" #include "http/request.h" #include "http/header.h" @@ -8,10 +9,11 @@ char httpRequestHasKeepAlive(HttpRequest request) { - char * header; - char * header_ptr; + HttpMessage message = (HttpMessage)request; + char * header; + char * header_ptr; - header = httpHeaderGet(&(request->header), "connection"); + header = httpHeaderGet(&(message->header), "connection"); if (NULL == header) { return 0; diff --git a/src/http/request/parser/get_body.c b/src/http/request/parser/get_body.c new file mode 100644 index 0000000..81fc8da --- /dev/null +++ b/src/http/request/parser/get_body.c @@ -0,0 +1,38 @@ +#include + +#include "http/header.h" +#include "http/message.h" +#include "http/request/parser.h" + +void +httpRequestParserGetBody(HttpRequestParser this) +{ + HttpMessage message = (HttpMessage)(this->cur_request); + char * nbody; + + if (0 == message->nbody) { + nbody = httpHeaderGet( + &(message->header), + "Content-Length"); + + if (NULL == nbody) { + this->state = HTTP_REQUEST_DONE; + return; + } + else { + message->type = HTTP_MESSAGE_BUFFERED; + message->nbody = atoi(nbody); + } + } + + if (REMAINS(this) >= message->nbody) { + (message->body).buffer = calloc(1, message->nbody + 1); + memcpy((message->body).buffer, + this->cur_data, + message->nbody); + this->cur_data += message->nbody; + this->state = HTTP_REQUEST_DONE; + } +} + +// vim: set ts=4 sw=4: diff --git a/src/http/request/parser/get_header.c b/src/http/request/parser/get_header.c index 1785365..af437ff 100644 --- a/src/http/request/parser/get_header.c +++ b/src/http/request/parser/get_header.c @@ -5,10 +5,10 @@ #include "class.h" #include "interface/class.h" #include "http/header.h" -#include "http/request.h" +#include "http/message.h" void -httpRequestParserGetHeader(HttpRequest request, char * line) +httpRequestParserGetHeader(HttpMessage request, char * line) { char * name = line; char * value = strchr(line, ':'); diff --git a/src/http/request/parser/get_request_line.c b/src/http/request/parser/get_request_line.c index 83bf131..a8ab861 100644 --- a/src/http/request/parser/get_request_line.c +++ b/src/http/request/parser/get_request_line.c @@ -7,6 +7,7 @@ void httpRequestParserGetRequestLine(HttpRequest request, char * line) { + HttpMessage message = (HttpMessage)request; char * method, * uri, * version; method = line; @@ -23,8 +24,8 @@ httpRequestParserGetRequestLine(HttpRequest request, char * line) strcpy(request->method, method); request->uri = malloc(strlen(uri) + 1); strcpy(request->uri, uri); - request->version = malloc(strlen(version) + 1); - strcpy(request->version, method); + message->version = malloc(strlen(version) + 1); + strcpy(message->version, method); } // vim: set ts=4 sw=4: diff --git a/src/http/request/parser/parse.c b/src/http/request/parser/parse.c index 450b698..e7b3bfb 100644 --- a/src/http/request/parser/parse.c +++ b/src/http/request/parser/parse.c @@ -7,10 +7,6 @@ #include "interface/class.h" -#define REMAINS(pars) \ - ((pars)->buffer_used - ((pars)->cur_data - (pars)->buffer)) - - static inline char * @@ -79,33 +75,7 @@ httpRequestParserParse(HttpRequestParser this) break; case HTTP_REQUEST_HEADERS_DONE: - { - char * nbody; - - if (0 == this->cur_request->nbody) { - nbody = httpHeaderGet( - &(this->cur_request->header), - "Content-Length"); - - if (NULL == nbody) { - this->state = HTTP_REQUEST_DONE; - break; - } - else { - this->cur_request->nbody = atoi(nbody); - } - } - - if (REMAINS(this) >= this->cur_request->nbody) { - this->cur_request->body = calloc(1, this->cur_request->nbody + 1); - memcpy(this->cur_request->body, - this->cur_data, - this->cur_request->nbody); - this->cur_data += this->cur_request->nbody; - this->state = HTTP_REQUEST_DONE; - } - } - + httpRequestParserGetBody(this); break; case HTTP_REQUEST_DONE: diff --git a/src/http/request/parser/read.c b/src/http/request/parser/read.c new file mode 100644 index 0000000..6201df3 --- /dev/null +++ b/src/http/request/parser/read.c @@ -0,0 +1,44 @@ +#include +#include + +#include "http/request/parser.h" + + +size_t +httpRequestParserRead(HttpRequestParser this, int fd) +{ + size_t remaining, chunks; + char buffer[1024]; + + ssize_t size = read(fd, buffer, 1024); + + if (0 < size) { + remaining = this->buffer_used % HTTP_REQUEST_PARSER_READ_CHUNK; + chunks = this->buffer_used / HTTP_REQUEST_PARSER_READ_CHUNK; + + /** + * because a division always rounds down + * chunks holds exactly the currently allocated chunks if + * remaining equals 0 but there is no space left. + * Else chunks holds the actually allocated amount of chunks + * minus 1. + * For this reason chunks always has to be increased by 1. + */ + chunks++; + + if (size >= remaining) { + this->buffer = + realloc(this->buffer, chunks * HTTP_REQUEST_PARSER_READ_CHUNK); + } + + memcpy(this->buffer + this->buffer_used, buffer, size); + this->buffer_used += size; + this->buffer[this->buffer_used] = 0; + + httpRequestParserParse(this); + } + + return size; +} + +// vim: set ts=4 sw=4: diff --git a/src/http/response.c b/src/http/response.c index d103881..bc58146 100644 --- a/src/http/response.c +++ b/src/http/response.c @@ -1,8 +1,3 @@ -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif - -#include #include #include @@ -10,60 +5,38 @@ #include "interface/class.h" #include "http/response.h" +#include "message/helper.c" -static -void -_free(void ** data) -{ - if (NULL != *data) { - free(*data); - } -} - static void ctor(void * _this, va_list * params) { - char * version; + HttpResponse this = _this; + char * status; char * reason; - HttpResponse this = _this; + PARENTCALL(_this, Class, ctor, params); - version = va_arg(* params, char *); this->status = va_arg(* params, unsigned int); reason = va_arg(* params, char *); - this->version = calloc(1, strlen(version)+1); - strcpy(this->version, version); - this->reason = calloc(1, strlen(reason)+1); strcpy(this->reason, reason); } -static -inline -void -tDelete(void * node) -{ - delete(&node); -} - static void dtor(void * _this) { HttpResponse this = _this; - _free((void **)&(this->version)); _free((void **)&(this->reason)); - tdestroy(this->header, tDelete); - - _free((void **)&(this->body)); + PARENTCALL(_this, Class, dtor); } INIT_IFACE(Class, ctor, dtor, NULL); -CREATE_CLASS(HttpResponse, NULL, IFACE(Class)); +CREATE_CLASS(HttpResponse, HttpMessage, IFACE(Class)); // vim: set ts=4 sw=4: diff --git a/src/http/response/404.c b/src/http/response/404.c index b629265..ed4e457 100644 --- a/src/http/response/404.c +++ b/src/http/response/404.c @@ -7,6 +7,7 @@ #include "interface/class.h" #include "http/response.h" +#include "http/message.h" #include "http/header.h" @@ -26,26 +27,29 @@ httpResponse404() struct tm * tmp; char buffer[200]; HttpResponse response; + HttpMessage message; response = new(HttpResponse, "HTTP/1.1", 404, "Not Found"); + message = (HttpMessage)response; - httpHeaderAdd(&(response->header), + httpHeaderAdd(&(message->header), new(HttpHeader, "Content-Type", "text/html")); - httpHeaderAdd(&(response->header), + httpHeaderAdd(&(message->header), new(HttpHeader, "Server", "testserver")); - response->nbody = sizeof(RESP_DATA) - 1; - response->body = calloc(1, sizeof(RESP_DATA)); - strcpy(response->body, RESP_DATA); + message->type = HTTP_MESSAGE_BUFFERED; + message->nbody = sizeof(RESP_DATA) - 1; + (message->body).buffer = calloc(1, sizeof(RESP_DATA)); + strcpy((message->body).buffer, RESP_DATA); - sprintf(buffer, "%d", response->nbody); - httpHeaderAdd(&(response->header), + sprintf(buffer, "%d", message->nbody); + httpHeaderAdd(&(message->header), new(HttpHeader, "Content-Length", buffer)); t = time(NULL); tmp = localtime(&t); strftime(buffer, sizeof(buffer), "%a, %d %b %Y %T %Z", tmp); - httpHeaderAdd(&(response->header), + httpHeaderAdd(&(message->header), new(HttpHeader, "Date", buffer)); return response; diff --git a/src/http/response/size_get.c b/src/http/response/size_get.c index f29652f..5f3cfaa 100644 --- a/src/http/response/size_get.c +++ b/src/http/response/size_get.c @@ -2,6 +2,7 @@ #include #include +#include "http/message.h" #include "http/response.h" #include "http/header.h" @@ -20,16 +21,21 @@ addHeaderSize(const void * node, const VISIT which, const int depth) size_t httpResponseSizeGet(HttpResponse response) { + HttpMessage message = (HttpMessage)response; + size = 0; - size += strlen(response->version) + 1; + size += strlen(message->version) + 1; size += 4; // for status size += strlen(response->reason) + 2; - twalk(response->header, addHeaderSize); + twalk(message->header, addHeaderSize); size += 2; - size += response->nbody; + + if (HTTP_MESSAGE_BUFFERED == message->type) { + size += message->nbody; + } return size; } diff --git a/src/http/response/to_string.c b/src/http/response/to_string.c index 0a50712..770c18f 100644 --- a/src/http/response/to_string.c +++ b/src/http/response/to_string.c @@ -20,13 +20,14 @@ addHeaderString(const void * node, const VISIT which, const int depth) char * httpResponseToString(HttpResponse response, char * _string) { - char status[4]; + HttpMessage message = (HttpMessage)response; + char status[4]; string = _string; snprintf(status, 4, "%d", response->status); - strcpy(string, response->version); + strcpy(string, message->version); string += strlen(string); *string++ = ' '; @@ -42,12 +43,14 @@ httpResponseToString(HttpResponse response, char * _string) *string++ = '\r'; *string++ = '\n'; - twalk(response->header, addHeaderString); + twalk(message->header, addHeaderString); *string++ = '\r'; *string++ = '\n'; - memcpy(string, response->body, response->nbody); + if (HTTP_MESSAGE_BUFFERED == message->type) { + memcpy(string, (message->body).buffer, message->nbody); + } return string; } diff --git a/src/server/run.c b/src/server/run.c index 765741b..3f13b1e 100644 --- a/src/server/run.c +++ b/src/server/run.c @@ -117,13 +117,13 @@ serverRun(Server this) if (httpRequestHasKeepAlive(queue->requests[j])) { (this->conns)[fd].keep_alive = 1; httpHeaderAdd( - &(response->header), + &(((HttpMessage)response)->header), new(HttpHeader, "Connection", "Keep-Alive")); } else { (this->conns)[fd].keep_alive = 0; httpHeaderAdd( - &(response->header), + &(((HttpMessage)response)->header), new(HttpHeader, "Connection", "Close")); } diff --git a/tests/tst-tsearch.c b/tests/tst-tsearch.c new file mode 100644 index 0000000..722b6d7 --- /dev/null +++ b/tests/tst-tsearch.c @@ -0,0 +1,333 @@ +/* Test program for tsearch et al. + Copyright (C) 1997, 2000, 2001 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307 USA. */ + +#ifndef _GNU_SOURCE +# define _GNU_SOURCE 1 +#endif + +#include +#include +#include +#include + +#define SEED 0 +#define BALANCED 1 +#define PASSES 100 + +#if BALANCED +#include +#define SIZE 1000 +#else +#define SIZE 100 +#endif + +enum order +{ + ascending, + descending, + randomorder +}; + +enum action +{ + build, + build_and_del, + delete, + find +}; + +/* Set to 1 if a test is flunked. */ +static int error = 0; + +/* The keys we add to the tree. */ +static int x[SIZE]; + +/* Pointers into the key array, possibly permutated, to define an order + for insertion/removal. */ +static int y[SIZE]; + +/* Flags set for each element visited during a tree walk. */ +static int z[SIZE]; + +/* Depths for all the elements, to check that the depth is constant for + all three visits. */ +static int depths[SIZE]; + +/* Maximum depth during a tree walk. */ +static int max_depth; + +/* Compare two keys. */ +static int +cmp_fn (const void *a, const void *b) +{ + return *(const int *) a - *(const int *) b; +} + +/* Permute an array of integers. */ +static void +memfry (int *string) +{ + int i; + + for (i = 0; i < SIZE; ++i) + { + int32_t j; + int c; + + j = random () % SIZE; + + c = string[i]; + string[i] = string[j]; + string[j] = c; + } +} + +static void +walk_action (const void *nodep, const VISIT which, const int depth) +{ + int key = **(int **) nodep; + + if (depth > max_depth) + max_depth = depth; + if (which == leaf || which == preorder) + { + ++z[key]; + depths[key] = depth; + } + else + { + if (depths[key] != depth) + { + fputs ("Depth for one element is not constant during tree walk.\n", + stdout); + } + } +} + +static void +walk_tree (void *root, int expected_count) +{ + int i; + + memset (z, 0, sizeof z); + max_depth = 0; + + twalk (root, walk_action); + for (i = 0; i < expected_count; ++i) + if (z[i] != 1) + { + fputs ("Node was not visited.\n", stdout); + error = 1; + } + +#if BALANCED + if (max_depth > log (expected_count) * 2 + 2) +#else + if (max_depth > expected_count) +#endif + { + fputs ("Depth too large during tree walk.\n", stdout); + error = 1; + } +} + +/* Perform an operation on a tree. */ +static void +mangle_tree (enum order how, enum action what, void **root, int lag) +{ + int i; + + if (how == randomorder) + { + for (i = 0; i < SIZE; ++i) + y[i] = i; + memfry (y); + } + + for (i = 0; i < SIZE + lag; ++i) + { + void *elem; + int j, k; + + switch (how) + { + case randomorder: + if (i >= lag) + k = y[i - lag]; + else + /* Ensure that the array index is within bounds. */ + k = y[(SIZE - i - 1 + lag) % SIZE]; + j = y[i % SIZE]; + break; + + case ascending: + k = i - lag; + j = i; + break; + + case descending: + k = SIZE - i - 1 + lag; + j = SIZE - i - 1; + break; + + default: + /* This never should happen, but gcc isn't smart enough to + recognize it. */ + abort (); + } + + switch (what) + { + case build_and_del: + case build: + if (i < SIZE) + { + if (tfind (x + j, (void *const *) root, cmp_fn) != NULL) + { + fputs ("Found element which is not in tree yet.\n", stdout); + error = 1; + } + elem = tsearch (x + j, root, cmp_fn); + if (elem == 0 + || tfind (x + j, (void *const *) root, cmp_fn) == NULL) + { + fputs ("Couldn't find element after it was added.\n", + stdout); + error = 1; + } + } + + if (what == build || i < lag) + break; + + j = k; + /* fall through */ + + case delete: + elem = tfind (x + j, (void *const *) root, cmp_fn); + if (elem == NULL || tdelete (x + j, root, cmp_fn) == NULL) + { + fputs ("Error deleting element.\n", stdout); + error = 1; + } + break; + + case find: + if (tfind (x + j, (void *const *) root, cmp_fn) == NULL) + { + fputs ("Couldn't find element after it was added.\n", stdout); + error = 1; + } + break; + + } + } +} + + +int +main (int argc, char **argv) +{ + int total_error = 0; + static char state[8] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + void *root = NULL; + int i, j; + + initstate (SEED, state, 8); + + for (i = 0; i < SIZE; ++i) + x[i] = i; + + /* Do this loop several times to get different permutations for the + random case. */ + fputs ("Series I\n", stdout); + for (i = 0; i < PASSES; ++i) + { + fprintf (stdout, "Pass %d... ", i + 1); + fflush (stdout); + error = 0; + + mangle_tree (ascending, build, &root, 0); + mangle_tree (ascending, find, &root, 0); + mangle_tree (descending, find, &root, 0); + mangle_tree (randomorder, find, &root, 0); + walk_tree (root, SIZE); + mangle_tree (ascending, delete, &root, 0); + + mangle_tree (ascending, build, &root, 0); + walk_tree (root, SIZE); + mangle_tree (descending, delete, &root, 0); + + mangle_tree (ascending, build, &root, 0); + walk_tree (root, SIZE); + mangle_tree (randomorder, delete, &root, 0); + + mangle_tree (descending, build, &root, 0); + mangle_tree (ascending, find, &root, 0); + mangle_tree (descending, find, &root, 0); + mangle_tree (randomorder, find, &root, 0); + walk_tree (root, SIZE); + mangle_tree (descending, delete, &root, 0); + + mangle_tree (descending, build, &root, 0); + walk_tree (root, SIZE); + mangle_tree (descending, delete, &root, 0); + + mangle_tree (descending, build, &root, 0); + walk_tree (root, SIZE); + mangle_tree (randomorder, delete, &root, 0); + + mangle_tree (randomorder, build, &root, 0); + mangle_tree (ascending, find, &root, 0); + mangle_tree (descending, find, &root, 0); + mangle_tree (randomorder, find, &root, 0); + walk_tree (root, SIZE); + mangle_tree (randomorder, delete, &root, 0); + + for (j = 1; j < SIZE; j *= 2) + { + mangle_tree (randomorder, build_and_del, &root, j); + } + + fputs (error ? " failed!\n" : " ok.\n", stdout); + total_error |= error; + } + + fputs ("Series II\n", stdout); + for (i = 1; i < SIZE; i *= 2) + { + fprintf (stdout, "For size %d... ", i); + fflush (stdout); + error = 0; + + mangle_tree (ascending, build_and_del, &root, i); + mangle_tree (descending, build_and_del, &root, i); + mangle_tree (ascending, build_and_del, &root, i); + mangle_tree (descending, build_and_del, &root, i); + mangle_tree (ascending, build_and_del, &root, i); + mangle_tree (descending, build_and_del, &root, i); + mangle_tree (ascending, build_and_del, &root, i); + mangle_tree (descending, build_and_del, &root, i); + + fputs (error ? " failed!\n" : " ok.\n", stdout); + total_error |= error; + } + + return total_error; +}