From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../src/third_party/libevent/test/Makefile.nmake | 79 + ipc/chromium/src/third_party/libevent/test/bench.c | 235 + .../src/third_party/libevent/test/bench_cascade.c | 188 + .../src/third_party/libevent/test/bench_http.c | 200 + .../third_party/libevent/test/bench_httpclient.c | 240 + .../third_party/libevent/test/check-dumpevents.py | 54 + .../src/third_party/libevent/test/include.am | 176 + .../libevent/test/print-winsock-errors.c | 86 + .../src/third_party/libevent/test/regress.c | 3615 +++++++++++++++ .../src/third_party/libevent/test/regress.h | 149 + .../src/third_party/libevent/test/regress.rpc | 25 + .../src/third_party/libevent/test/regress_buffer.c | 2870 ++++++++++++ .../libevent/test/regress_bufferevent.c | 1467 ++++++ .../src/third_party/libevent/test/regress_dns.c | 2518 ++++++++++ .../src/third_party/libevent/test/regress_et.c | 270 ++ .../third_party/libevent/test/regress_finalize.c | 395 ++ .../src/third_party/libevent/test/regress_http.c | 4807 ++++++++++++++++++++ .../src/third_party/libevent/test/regress_iocp.c | 352 ++ .../third_party/libevent/test/regress_listener.c | 348 ++ .../src/third_party/libevent/test/regress_main.c | 528 +++ .../third_party/libevent/test/regress_minheap.c | 99 + .../src/third_party/libevent/test/regress_rpc.c | 952 ++++ .../src/third_party/libevent/test/regress_ssl.c | 1078 +++++ .../third_party/libevent/test/regress_testutils.c | 233 + .../third_party/libevent/test/regress_testutils.h | 67 + .../src/third_party/libevent/test/regress_thread.c | 590 +++ .../src/third_party/libevent/test/regress_thread.h | 56 + .../src/third_party/libevent/test/regress_util.c | 1681 +++++++ .../src/third_party/libevent/test/regress_zlib.c | 348 ++ .../third_party/libevent/test/rpcgen_wrapper.sh | 43 + .../third_party/libevent/test/test-changelist.c | 224 + .../src/third_party/libevent/test/test-closed.c | 111 + .../third_party/libevent/test/test-dumpevents.c | 179 + .../src/third_party/libevent/test/test-eof.c | 117 + .../src/third_party/libevent/test/test-fdleak.c | 252 + .../src/third_party/libevent/test/test-init.c | 65 + .../src/third_party/libevent/test/test-ratelim.c | 688 +++ .../src/third_party/libevent/test/test-ratelim.sh | 88 + .../src/third_party/libevent/test/test-time.c | 123 + .../src/third_party/libevent/test/test-weof.c | 114 + ipc/chromium/src/third_party/libevent/test/test.sh | 188 + .../src/third_party/libevent/test/tinytest.c | 610 +++ .../src/third_party/libevent/test/tinytest.h | 103 + .../src/third_party/libevent/test/tinytest_demo.c | 292 ++ .../src/third_party/libevent/test/tinytest_local.h | 12 + .../third_party/libevent/test/tinytest_macros.h | 213 + 46 files changed, 27128 insertions(+) create mode 100644 ipc/chromium/src/third_party/libevent/test/Makefile.nmake create mode 100644 ipc/chromium/src/third_party/libevent/test/bench.c create mode 100644 ipc/chromium/src/third_party/libevent/test/bench_cascade.c create mode 100644 ipc/chromium/src/third_party/libevent/test/bench_http.c create mode 100644 ipc/chromium/src/third_party/libevent/test/bench_httpclient.c create mode 100755 ipc/chromium/src/third_party/libevent/test/check-dumpevents.py create mode 100644 ipc/chromium/src/third_party/libevent/test/include.am create mode 100644 ipc/chromium/src/third_party/libevent/test/print-winsock-errors.c create mode 100644 ipc/chromium/src/third_party/libevent/test/regress.c create mode 100644 ipc/chromium/src/third_party/libevent/test/regress.h create mode 100644 ipc/chromium/src/third_party/libevent/test/regress.rpc create mode 100644 ipc/chromium/src/third_party/libevent/test/regress_buffer.c create mode 100644 ipc/chromium/src/third_party/libevent/test/regress_bufferevent.c create mode 100644 ipc/chromium/src/third_party/libevent/test/regress_dns.c create mode 100644 ipc/chromium/src/third_party/libevent/test/regress_et.c create mode 100644 ipc/chromium/src/third_party/libevent/test/regress_finalize.c create mode 100644 ipc/chromium/src/third_party/libevent/test/regress_http.c create mode 100644 ipc/chromium/src/third_party/libevent/test/regress_iocp.c create mode 100644 ipc/chromium/src/third_party/libevent/test/regress_listener.c create mode 100644 ipc/chromium/src/third_party/libevent/test/regress_main.c create mode 100644 ipc/chromium/src/third_party/libevent/test/regress_minheap.c create mode 100644 ipc/chromium/src/third_party/libevent/test/regress_rpc.c create mode 100644 ipc/chromium/src/third_party/libevent/test/regress_ssl.c create mode 100644 ipc/chromium/src/third_party/libevent/test/regress_testutils.c create mode 100644 ipc/chromium/src/third_party/libevent/test/regress_testutils.h create mode 100644 ipc/chromium/src/third_party/libevent/test/regress_thread.c create mode 100644 ipc/chromium/src/third_party/libevent/test/regress_thread.h create mode 100644 ipc/chromium/src/third_party/libevent/test/regress_util.c create mode 100644 ipc/chromium/src/third_party/libevent/test/regress_zlib.c create mode 100755 ipc/chromium/src/third_party/libevent/test/rpcgen_wrapper.sh create mode 100644 ipc/chromium/src/third_party/libevent/test/test-changelist.c create mode 100644 ipc/chromium/src/third_party/libevent/test/test-closed.c create mode 100644 ipc/chromium/src/third_party/libevent/test/test-dumpevents.c create mode 100644 ipc/chromium/src/third_party/libevent/test/test-eof.c create mode 100644 ipc/chromium/src/third_party/libevent/test/test-fdleak.c create mode 100644 ipc/chromium/src/third_party/libevent/test/test-init.c create mode 100644 ipc/chromium/src/third_party/libevent/test/test-ratelim.c create mode 100755 ipc/chromium/src/third_party/libevent/test/test-ratelim.sh create mode 100644 ipc/chromium/src/third_party/libevent/test/test-time.c create mode 100644 ipc/chromium/src/third_party/libevent/test/test-weof.c create mode 100755 ipc/chromium/src/third_party/libevent/test/test.sh create mode 100644 ipc/chromium/src/third_party/libevent/test/tinytest.c create mode 100644 ipc/chromium/src/third_party/libevent/test/tinytest.h create mode 100644 ipc/chromium/src/third_party/libevent/test/tinytest_demo.c create mode 100644 ipc/chromium/src/third_party/libevent/test/tinytest_local.h create mode 100644 ipc/chromium/src/third_party/libevent/test/tinytest_macros.h (limited to 'ipc/chromium/src/third_party/libevent/test') diff --git a/ipc/chromium/src/third_party/libevent/test/Makefile.nmake b/ipc/chromium/src/third_party/libevent/test/Makefile.nmake new file mode 100644 index 0000000000..30c3eb792b --- /dev/null +++ b/ipc/chromium/src/third_party/libevent/test/Makefile.nmake @@ -0,0 +1,79 @@ +# WATCH OUT! This makefile is a work in progress. -*- makefile -*- + +!IFDEF OPENSSL_DIR +SSL_CFLAGS=/I$(OPENSSL_DIR)\include /DEVENT__HAVE_OPENSSL +SSL_OBJS=regress_ssl.obj +SSL_LIBS=..\libevent_openssl.lib $(OPENSSL_DIR)\lib\libeay32.lib $(OPENSSL_DIR)\lib\ssleay32.lib gdi32.lib User32.lib +!ELSE +SSL_CFLAGS= +SSL_OBJS= +SSL_LIBS= +!ENDIF + +CFLAGS=/I.. /I../WIN32-Code /I../WIN32-Code/nmake /I../include /I../compat /DHAVE_CONFIG_H /DTINYTEST_LOCAL $(SSL_CFLAGS) + +CFLAGS=$(CFLAGS) /Ox /W3 /wd4996 /nologo + +REGRESS_OBJS=regress.obj regress_buffer.obj regress_http.obj regress_dns.obj \ + regress_testutils.obj \ + regress_rpc.obj regress.gen.obj \ + regress_et.obj regress_bufferevent.obj \ + regress_listener.obj regress_util.obj tinytest.obj \ + regress_main.obj regress_minheap.obj regress_iocp.obj \ + regress_thread.obj regress_finalize.obj $(SSL_OBJS) + +OTHER_OBJS=test-init.obj test-eof.obj test-closed.obj test-weof.obj test-time.obj \ + bench.obj bench_cascade.obj bench_http.obj bench_httpclient.obj \ + test-changelist.obj \ + print-winsock-errors.obj + +PROGRAMS=regress.exe \ + test-init.exe test-eof.exe test-closed.exe test-weof.exe test-time.exe \ + test-changelist.exe \ + print-winsock-errors.exe + +# Disabled for now: +# bench.exe bench_cascade.exe bench_http.exe bench_httpclient.exe + + +LIBS=..\libevent.lib ws2_32.lib shell32.lib advapi32.lib + +all: $(PROGRAMS) + +regress.exe: $(REGRESS_OBJS) + $(CC) $(CFLAGS) $(LIBS) $(SSL_LIBS) $(REGRESS_OBJS) + +test-init.exe: test-init.obj + $(CC) $(CFLAGS) $(LIBS) test-init.obj +test-eof.exe: test-eof.obj + $(CC) $(CFLAGS) $(LIBS) test-eof.obj +test-closed.exe: test-closed.obj + $(CC) $(CFLAGS) $(LIBS) test-closed.obj +test-changelist.exe: test-changelist.obj + $(CC) $(CFLAGS) $(LIBS) test-changelist.obj +test-weof.exe: test-weof.obj + $(CC) $(CFLAGS) $(LIBS) test-weof.obj +test-time.exe: test-time.obj + $(CC) $(CFLAGS) $(LIBS) test-time.obj + +print-winsock-errors.exe: print-winsock-errors.obj + $(CC) $(CFLAGS) $(LIBS) print-winsock-errors.obj + +bench.exe: bench.obj + $(CC) $(CFLAGS) $(LIBS) bench.obj +bench_cascade.exe: bench_cascade.obj + $(CC) $(CFLAGS) $(LIBS) bench_cascade.obj +bench_http.exe: bench_http.obj + $(CC) $(CFLAGS) $(LIBS) bench_http.obj +bench_httpclient.exe: bench_httpclient.obj + $(CC) $(CFLAGS) $(LIBS) bench_httpclient.obj + +regress.gen.c regress.gen.h: regress.rpc ../event_rpcgen.py + echo // > regress.gen.c + echo #define NO_PYTHON_EXISTS > regress.gen.h + -python ..\event_rpcgen.py regress.rpc + +clean: + -del $(REGRESS_OBJS) + -del $(OTHER_OBJS) + -del $(PROGRAMS) diff --git a/ipc/chromium/src/third_party/libevent/test/bench.c b/ipc/chromium/src/third_party/libevent/test/bench.c new file mode 100644 index 0000000000..f2af4d3f30 --- /dev/null +++ b/ipc/chromium/src/third_party/libevent/test/bench.c @@ -0,0 +1,235 @@ +/* + * Copyright 2003-2007 Niels Provos + * Copyright 2007-2012 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * + * Mon 03/10/2003 - Modified by Davide Libenzi + * + * Added chain event propagation to improve the sensitivity of + * the measure respect to the event loop efficency. + * + * + */ + +#include "event2/event-config.h" +#include "../util-internal.h" + +#include +#include +#ifdef EVENT__HAVE_SYS_TIME_H +#include +#endif +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#else +#include +#include +#include +#endif +#include +#include +#include +#include +#ifdef EVENT__HAVE_UNISTD_H +#include +#endif +#include + +#ifdef _WIN32 +#include +#endif + +#include +#include + +static ev_ssize_t count, fired; +static int writes, failures; +static evutil_socket_t *pipes; +static int num_pipes, num_active, num_writes; +static struct event *events; +static struct event_base *base; + + +static void +read_cb(evutil_socket_t fd, short which, void *arg) +{ + ev_intptr_t idx = (ev_intptr_t) arg, widx = idx + 1; + unsigned char ch; + ev_ssize_t n; + + n = recv(fd, (char*)&ch, sizeof(ch), 0); + if (n >= 0) + count += n; + else + failures++; + if (writes) { + if (widx >= num_pipes) + widx -= num_pipes; + n = send(pipes[2 * widx + 1], "e", 1, 0); + if (n != 1) + failures++; + writes--; + fired++; + } +} + +static struct timeval * +run_once(void) +{ + evutil_socket_t *cp, space; + long i; + static struct timeval ts, te; + + for (cp = pipes, i = 0; i < num_pipes; i++, cp += 2) { + if (event_initialized(&events[i])) + event_del(&events[i]); + event_assign(&events[i], base, cp[0], EV_READ | EV_PERSIST, read_cb, (void *)(ev_intptr_t) i); + event_add(&events[i], NULL); + } + + event_base_loop(base, EVLOOP_ONCE | EVLOOP_NONBLOCK); + + fired = 0; + space = num_pipes / num_active; + space = space * 2; + for (i = 0; i < num_active; i++, fired++) + (void) send(pipes[i * space + 1], "e", 1, 0); + + count = 0; + writes = num_writes; + { + int xcount = 0; + evutil_gettimeofday(&ts, NULL); + do { + event_base_loop(base, EVLOOP_ONCE | EVLOOP_NONBLOCK); + xcount++; + } while (count != fired); + evutil_gettimeofday(&te, NULL); + + if (xcount != count) + fprintf(stderr, "Xcount: %d, Rcount: " EV_SSIZE_FMT "\n", + xcount, count); + } + + evutil_timersub(&te, &ts, &te); + + return (&te); +} + +int +main(int argc, char **argv) +{ +#ifdef EVENT__HAVE_SETRLIMIT + struct rlimit rl; +#endif + int i, c; + struct timeval *tv; + evutil_socket_t *cp; + const char **methods; + const char *method = NULL; + struct event_config *cfg = NULL; + +#ifdef _WIN32 + WSADATA WSAData; + WSAStartup(0x101, &WSAData); +#endif + num_pipes = 100; + num_active = 1; + num_writes = num_pipes; + while ((c = getopt(argc, argv, "n:a:w:m:l")) != -1) { + switch (c) { + case 'n': + num_pipes = atoi(optarg); + break; + case 'a': + num_active = atoi(optarg); + break; + case 'w': + num_writes = atoi(optarg); + break; + case 'm': + method = optarg; + break; + case 'l': + methods = event_get_supported_methods(); + fprintf(stdout, "Using Libevent %s. Available methods are:\n", + event_get_version()); + for (i = 0; methods[i] != NULL; ++i) + printf(" %s\n", methods[i]); + exit(0); + default: + fprintf(stderr, "Illegal argument \"%c\"\n", c); + exit(1); + } + } + +#ifdef EVENT__HAVE_SETRLIMIT + rl.rlim_cur = rl.rlim_max = num_pipes * 2 + 50; + if (setrlimit(RLIMIT_NOFILE, &rl) == -1) { + perror("setrlimit"); + exit(1); + } +#endif + + events = calloc(num_pipes, sizeof(struct event)); + pipes = calloc(num_pipes * 2, sizeof(evutil_socket_t)); + if (events == NULL || pipes == NULL) { + perror("malloc"); + exit(1); + } + + if (method != NULL) { + cfg = event_config_new(); + methods = event_get_supported_methods(); + for (i = 0; methods[i] != NULL; ++i) + if (strcmp(methods[i], method)) + event_config_avoid_method(cfg, methods[i]); + base = event_base_new_with_config(cfg); + event_config_free(cfg); + } else + base = event_base_new(); + + for (cp = pipes, i = 0; i < num_pipes; i++, cp += 2) { +#ifdef USE_PIPES + if (pipe(cp) == -1) { +#else + if (evutil_socketpair(AF_UNIX, SOCK_STREAM, 0, cp) == -1) { +#endif + perror("pipe"); + exit(1); + } + } + + for (i = 0; i < 25; i++) { + tv = run_once(); + if (tv == NULL) + exit(1); + fprintf(stdout, "%ld\n", + tv->tv_sec * 1000000L + tv->tv_usec); + } + + exit(0); +} diff --git a/ipc/chromium/src/third_party/libevent/test/bench_cascade.c b/ipc/chromium/src/third_party/libevent/test/bench_cascade.c new file mode 100644 index 0000000000..29a3203ecd --- /dev/null +++ b/ipc/chromium/src/third_party/libevent/test/bench_cascade.c @@ -0,0 +1,188 @@ +/* + * Copyright 2007-2012 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "event2/event-config.h" + +#include +#include +#ifdef EVENT__HAVE_SYS_TIME_H +#include +#endif +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#else /* _WIN32 */ +#include +#include +#endif +#include +#include +#include +#include +#include +#ifdef EVENT__HAVE_UNISTD_H +#include +#endif +#include +#include +#include + +/* + * This benchmark tests how quickly we can propagate a write down a chain + * of socket pairs. We start by writing to the first socket pair and all + * events will fire subsequently until the last socket pair has been reached + * and the benchmark terminates. + */ + +static int fired; +static evutil_socket_t *pipes; +static struct event *events; + +static void +read_cb(evutil_socket_t fd, short which, void *arg) +{ + char ch; + evutil_socket_t sock = (evutil_socket_t)(ev_intptr_t)arg; + + (void) recv(fd, &ch, sizeof(ch), 0); + if (sock >= 0) { + if (send(sock, "e", 1, 0) < 0) + perror("send"); + } + fired++; +} + +static struct timeval * +run_once(int num_pipes) +{ + int i; + evutil_socket_t *cp; + static struct timeval ts, te, tv_timeout; + + events = (struct event *)calloc(num_pipes, sizeof(struct event)); + pipes = (evutil_socket_t *)calloc(num_pipes * 2, sizeof(evutil_socket_t)); + + if (events == NULL || pipes == NULL) { + perror("malloc"); + exit(1); + } + + for (cp = pipes, i = 0; i < num_pipes; i++, cp += 2) { + if (evutil_socketpair(AF_UNIX, SOCK_STREAM, 0, cp) == -1) { + perror("socketpair"); + exit(1); + } + } + + /* measurements includes event setup */ + evutil_gettimeofday(&ts, NULL); + + /* provide a default timeout for events */ + evutil_timerclear(&tv_timeout); + tv_timeout.tv_sec = 60; + + for (cp = pipes, i = 0; i < num_pipes; i++, cp += 2) { + evutil_socket_t fd = i < num_pipes - 1 ? cp[3] : -1; + event_set(&events[i], cp[0], EV_READ, read_cb, + (void *)(ev_intptr_t)fd); + event_add(&events[i], &tv_timeout); + } + + fired = 0; + + /* kick everything off with a single write */ + if (send(pipes[1], "e", 1, 0) < 0) + perror("send"); + + event_dispatch(); + + evutil_gettimeofday(&te, NULL); + evutil_timersub(&te, &ts, &te); + + for (cp = pipes, i = 0; i < num_pipes; i++, cp += 2) { + event_del(&events[i]); + evutil_closesocket(cp[0]); + evutil_closesocket(cp[1]); + } + + free(pipes); + free(events); + + return (&te); +} + +int +main(int argc, char **argv) +{ +#ifdef EVENT__HAVE_SETRLIMIT + struct rlimit rl; +#endif + int i, c; + struct timeval *tv; + + int num_pipes = 100; +#ifdef _WIN32 + WSADATA WSAData; + WSAStartup(0x101, &WSAData); +#endif + + while ((c = getopt(argc, argv, "n:")) != -1) { + switch (c) { + case 'n': + num_pipes = atoi(optarg); + break; + default: + fprintf(stderr, "Illegal argument \"%c\"\n", c); + exit(1); + } + } + +#ifdef EVENT__HAVE_SETRLIMIT + rl.rlim_cur = rl.rlim_max = num_pipes * 2 + 50; + if (setrlimit(RLIMIT_NOFILE, &rl) == -1) { + perror("setrlimit"); + exit(1); + } +#endif + + event_init(); + + for (i = 0; i < 25; i++) { + tv = run_once(num_pipes); + if (tv == NULL) + exit(1); + fprintf(stdout, "%ld\n", + tv->tv_sec * 1000000L + tv->tv_usec); + } + +#ifdef _WIN32 + WSACleanup(); +#endif + + exit(0); +} diff --git a/ipc/chromium/src/third_party/libevent/test/bench_http.c b/ipc/chromium/src/third_party/libevent/test/bench_http.c new file mode 100644 index 0000000000..80377ee605 --- /dev/null +++ b/ipc/chromium/src/third_party/libevent/test/bench_http.c @@ -0,0 +1,200 @@ +/* + * Copyright 2008-2012 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include +#include +#ifdef _WIN32 +#include +#else +#include +#include +#include +#include +#endif +#include +#include +#include +#include +#include +#include + +#include "event2/event.h" +#include "event2/buffer.h" +#include "event2/util.h" +#include "event2/http.h" +#include "event2/thread.h" + +static void http_basic_cb(struct evhttp_request *req, void *arg); + +static char *content; +static size_t content_len = 0; + +static void +http_basic_cb(struct evhttp_request *req, void *arg) +{ + struct evbuffer *evb = evbuffer_new(); + + evbuffer_add(evb, content, content_len); + + /* allow sending of an empty reply */ + evhttp_send_reply(req, HTTP_OK, "Everything is fine", evb); + + evbuffer_free(evb); +} + +#if LIBEVENT_VERSION_NUMBER >= 0x02000200 +static void +http_ref_cb(struct evhttp_request *req, void *arg) +{ + struct evbuffer *evb = evbuffer_new(); + + evbuffer_add_reference(evb, content, content_len, NULL, NULL); + + /* allow sending of an empty reply */ + evhttp_send_reply(req, HTTP_OK, "Everything is fine", evb); + + evbuffer_free(evb); +} +#endif + +int +main(int argc, char **argv) +{ + struct event_config *cfg = event_config_new(); + struct event_base *base; + struct evhttp *http; + int i; + int c; + int use_iocp = 0; + ev_uint16_t port = 8080; + char *endptr = NULL; + +#ifdef _WIN32 + WSADATA WSAData; + WSAStartup(0x101, &WSAData); +#else + if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) + return (1); +#endif + + setbuf(stdout, NULL); + setbuf(stderr, NULL); + + for (i = 1; i < argc; ++i) { + if (*argv[i] != '-') + continue; + + c = argv[i][1]; + + if ((c == 'p' || c == 'l') && i + 1 >= argc) { + fprintf(stderr, "-%c requires argument.\n", c); + exit(1); + } + + switch (c) { + case 'p': + if (i+1 >= argc || !argv[i+1]) { + fprintf(stderr, "Missing port\n"); + exit(1); + } + port = (int)strtol(argv[i+1], &endptr, 10); + if (*endptr != '\0') { + fprintf(stderr, "Bad port\n"); + exit(1); + } + break; + case 'l': + if (i+1 >= argc || !argv[i+1]) { + fprintf(stderr, "Missing content length\n"); + exit(1); + } + content_len = (size_t)strtol(argv[i+1], &endptr, 10); + if (*endptr != '\0' || content_len == 0) { + fprintf(stderr, "Bad content length\n"); + exit(1); + } + break; +#ifdef _WIN32 + case 'i': + use_iocp = 1; +#ifdef EVTHREAD_USE_WINDOWS_THREADS_IMPLEMENTED + evthread_use_windows_threads(); +#endif + event_config_set_flag(cfg,EVENT_BASE_FLAG_STARTUP_IOCP); + break; +#endif + default: + fprintf(stderr, "Illegal argument \"%c\"\n", c); + exit(1); + } + } + + base = event_base_new_with_config(cfg); + if (!base) { + fprintf(stderr, "creating event_base failed. Exiting.\n"); + return 1; + } + + http = evhttp_new(base); + + content = malloc(content_len); + if (content == NULL) { + fprintf(stderr, "Cannot allocate content\n"); + exit(1); + } else { + int i = 0; + for (i = 0; i < (int)content_len; ++i) + content[i] = (i & 255); + } + + evhttp_set_cb(http, "/ind", http_basic_cb, NULL); + fprintf(stderr, "/ind - basic content (memory copy)\n"); + + evhttp_set_cb(http, "/ref", http_ref_cb, NULL); + fprintf(stderr, "/ref - basic content (reference)\n"); + + fprintf(stderr, "Serving %d bytes on port %d using %s\n", + (int)content_len, port, + use_iocp? "IOCP" : event_base_get_method(base)); + + evhttp_bind_socket(http, "0.0.0.0", port); + +#ifdef _WIN32 + if (use_iocp) { + struct timeval tv={99999999,0}; + event_base_loopexit(base, &tv); + } +#endif + event_base_dispatch(base); + +#ifdef _WIN32 + WSACleanup(); +#endif + + /* NOTREACHED */ + return (0); +} diff --git a/ipc/chromium/src/third_party/libevent/test/bench_httpclient.c b/ipc/chromium/src/third_party/libevent/test/bench_httpclient.c new file mode 100644 index 0000000000..7c7ee470a3 --- /dev/null +++ b/ipc/chromium/src/third_party/libevent/test/bench_httpclient.c @@ -0,0 +1,240 @@ +/* + * Copyright 2009-2012 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/* for EVUTIL_ERR_CONNECT_RETRIABLE macro */ +#include "util-internal.h" + +#include +#ifdef _WIN32 +#include +#else +#include +#include +# ifdef _XOPEN_SOURCE_EXTENDED +# include +# endif +#endif +#include +#include +#include + +#include "event2/event.h" +#include "event2/bufferevent.h" +#include "event2/buffer.h" +#include "event2/util.h" + +const char *resource = NULL; +struct event_base *base = NULL; + +int total_n_handled = 0; +int total_n_errors = 0; +int total_n_launched = 0; +size_t total_n_bytes = 0; +struct timeval total_time = {0,0}; +int n_errors = 0; + +const int PARALLELISM = 200; +const int N_REQUESTS = 20000; + +struct request_info { + size_t n_read; + struct timeval started; +}; + +static int launch_request(void); +static void readcb(struct bufferevent *b, void *arg); +static void errorcb(struct bufferevent *b, short what, void *arg); + +static void +readcb(struct bufferevent *b, void *arg) +{ + struct request_info *ri = arg; + struct evbuffer *input = bufferevent_get_input(b); + size_t n = evbuffer_get_length(input); + + ri->n_read += n; + evbuffer_drain(input, n); +} + +static void +errorcb(struct bufferevent *b, short what, void *arg) +{ + struct request_info *ri = arg; + struct timeval now, diff; + if (what & BEV_EVENT_EOF) { + ++total_n_handled; + total_n_bytes += ri->n_read; + evutil_gettimeofday(&now, NULL); + evutil_timersub(&now, &ri->started, &diff); + evutil_timeradd(&diff, &total_time, &total_time); + + if (total_n_handled && (total_n_handled%1000)==0) + printf("%d requests done\n",total_n_handled); + + if (total_n_launched < N_REQUESTS) { + if (launch_request() < 0) + perror("Can't launch"); + } + } else { + ++total_n_errors; + perror("Unexpected error"); + } + + bufferevent_setcb(b, NULL, NULL, NULL, NULL); + free(ri); + bufferevent_disable(b, EV_READ|EV_WRITE); + bufferevent_free(b); +} + +static void +frob_socket(evutil_socket_t sock) +{ +#ifdef EVENT__HAVE_STRUCT_LINGER + struct linger l; +#endif + int one = 1; + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void*)&one, sizeof(one))<0) + perror("setsockopt(SO_REUSEADDR)"); +#ifdef EVENT__HAVE_STRUCT_LINGER + l.l_onoff = 1; + l.l_linger = 0; + if (setsockopt(sock, SOL_SOCKET, SO_LINGER, (void*)&l, sizeof(l))<0) + perror("setsockopt(SO_LINGER)"); +#endif +} + +static int +launch_request(void) +{ + evutil_socket_t sock; + struct sockaddr_in sin; + struct bufferevent *b; + + struct request_info *ri; + + memset(&sin, 0, sizeof(sin)); + + ++total_n_launched; + + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(0x7f000001); + sin.sin_port = htons(8080); + if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) + return -1; + if (evutil_make_socket_nonblocking(sock) < 0) { + evutil_closesocket(sock); + return -1; + } + frob_socket(sock); + if (connect(sock, (struct sockaddr*)&sin, sizeof(sin)) < 0) { + int e = evutil_socket_geterror(sock); + if (! EVUTIL_ERR_CONNECT_RETRIABLE(e)) { + evutil_closesocket(sock); + return -1; + } + } + + ri = malloc(sizeof(*ri)); + ri->n_read = 0; + evutil_gettimeofday(&ri->started, NULL); + + b = bufferevent_socket_new(base, sock, BEV_OPT_CLOSE_ON_FREE); + + bufferevent_setcb(b, readcb, NULL, errorcb, ri); + bufferevent_enable(b, EV_READ|EV_WRITE); + + evbuffer_add_printf(bufferevent_get_output(b), + "GET %s HTTP/1.0\r\n\r\n", resource); + + return 0; +} + + +int +main(int argc, char **argv) +{ + int i; + struct timeval start, end, total; + long long usec; + double throughput; + +#ifdef _WIN32 + WSADATA WSAData; + WSAStartup(0x101, &WSAData); +#endif + + resource = "/ref"; + + setvbuf(stdout, NULL, _IONBF, 0); + + base = event_base_new(); + + for (i=0; i < PARALLELISM; ++i) { + if (launch_request() < 0) + perror("launch"); + } + + evutil_gettimeofday(&start, NULL); + + event_base_dispatch(base); + + evutil_gettimeofday(&end, NULL); + evutil_timersub(&end, &start, &total); + usec = total_time.tv_sec * (long long)1000000 + total_time.tv_usec; + + if (!total_n_handled) { + puts("Nothing worked. You probably did something dumb."); + return 0; + } + + + throughput = total_n_handled / + (total.tv_sec+ ((double)total.tv_usec)/1000000.0); + +#ifdef _WIN32 +#define I64_FMT "%I64d" +#define I64_TYP __int64 +#else +#define I64_FMT "%lld" +#define I64_TYP long long int +#endif + + printf("\n%d requests in %d.%06d sec. (%.2f throughput)\n" + "Each took about %.02f msec latency\n" + I64_FMT "bytes read. %d errors.\n", + total_n_handled, + (int)total.tv_sec, (int)total.tv_usec, + throughput, + (double)(usec/1000) / total_n_handled, + (I64_TYP)total_n_bytes, n_errors); + +#ifdef _WIN32 + WSACleanup(); +#endif + + return 0; +} diff --git a/ipc/chromium/src/third_party/libevent/test/check-dumpevents.py b/ipc/chromium/src/third_party/libevent/test/check-dumpevents.py new file mode 100755 index 0000000000..3e1df30c4f --- /dev/null +++ b/ipc/chromium/src/third_party/libevent/test/check-dumpevents.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python +# +# Post-process the output of test-dumpevents and check it for correctness. +# + +import math +import re +import sys + +text = sys.stdin.readlines() + +try: + expect_inserted_pos = text.index("Inserted:\n") + expect_active_pos = text.index("Active:\n") + got_inserted_pos = text.index("Inserted events:\n") + got_active_pos = text.index("Active events:\n") +except ValueError: + sys.stderr.write("Missing expected dividing line in dumpevents output") + sys.exit(1) + +if not (expect_inserted_pos < expect_active_pos < + got_inserted_pos < got_active_pos): + sys.stderr.write("Sections out of order in dumpevents output") + sys.exit(1) + +now,T= text[1].split() +T = float(T) + +want_inserted = set(text[expect_inserted_pos+1:expect_active_pos]) +want_active = set(text[expect_active_pos+1:got_inserted_pos-1]) +got_inserted = set(text[got_inserted_pos+1:got_active_pos]) +got_active = set(text[got_active_pos+1:]) + +pat = re.compile(r'Timeout=([0-9\.]+)') +def replace_time(m): + t = float(m.group(1)) + if .9 < abs(t-T) < 1.1: + return "Timeout=T+1" + elif 2.4 < abs(t-T) < 2.6: + return "Timeout=T+2.5" + else: + return m.group(0) + +cleaned_inserted = set( pat.sub(replace_time, s) for s in got_inserted + if "Internal" not in s) + +if cleaned_inserted != want_inserted: + sys.stderr.write("Inserted event lists were not as expected!") + sys.exit(1) + +if set(got_active) != set(want_active): + sys.stderr.write("Active event lists were not as expected!") + sys.exit(1) + diff --git a/ipc/chromium/src/third_party/libevent/test/include.am b/ipc/chromium/src/third_party/libevent/test/include.am new file mode 100644 index 0000000000..043752471a --- /dev/null +++ b/ipc/chromium/src/third_party/libevent/test/include.am @@ -0,0 +1,176 @@ +# test/Makefile.am for libevent +# Copyright 2000-2007 Niels Provos +# Copyright 2007-2012 Niels Provos and Nick Mathewson +# +# See LICENSE for copying information. + +regress_CPPFLAGS = -DTINYTEST_LOCAL + +EXTRA_DIST+= \ + test/check-dumpevents.py \ + test/regress.gen.c \ + test/regress.gen.h \ + test/regress.rpc \ + test/rpcgen_wrapper.sh \ + test/print-winsock-errors.c \ + test/test.sh + +TESTPROGRAMS = \ + test/bench \ + test/bench_cascade \ + test/bench_http \ + test/bench_httpclient \ + test/test-changelist \ + test/test-dumpevents \ + test/test-eof \ + test/test-closed \ + test/test-fdleak \ + test/test-init \ + test/test-ratelim \ + test/test-time \ + test/test-weof \ + test/regress + +if BUILD_REGRESS +noinst_PROGRAMS += $(TESTPROGRAMS) +EXTRA_PROGRAMS+= test/regress +endif + +noinst_HEADERS+= \ + test/regress.h \ + test/regress_thread.h \ + test/tinytest.h \ + test/tinytest_local.h \ + test/tinytest_macros.h + +TESTS = \ + test_runner_epoll \ + test_runner_select \ + test_runner_kqueue \ + test_runner_evport \ + test_runner_devpoll \ + test_runner_poll \ + test_runner_win32 \ + test_runner_timerfd \ + test_runner_changelist \ + test_runner_timerfd_changelist +LOG_COMPILER = true +TESTS_COMPILER = true + +test_runner_epoll: $(top_srcdir)/test/test.sh + $(top_srcdir)/test/test.sh -b EPOLL +test_runner_select: $(top_srcdir)/test/test.sh + $(top_srcdir)/test/test.sh -b SELECT +test_runner_kqueue: $(top_srcdir)/test/test.sh + $(top_srcdir)/test/test.sh -b KQUEUE +test_runner_evport: $(top_srcdir)/test/test.sh + $(top_srcdir)/test/test.sh -b EVPORT +test_runner_devpoll: $(top_srcdir)/test/test.sh + $(top_srcdir)/test/test.sh -b DEVPOLL +test_runner_poll: $(top_srcdir)/test/test.sh + $(top_srcdir)/test/test.sh -b POLL +test_runner_win32: $(top_srcdir)/test/test.sh + $(top_srcdir)/test/test.sh -b WIN32 +test_runner_timerfd: $(top_srcdir)/test/test.sh + $(top_srcdir)/test/test.sh -b "" -t +test_runner_changelist: $(top_srcdir)/test/test.sh + $(top_srcdir)/test/test.sh -b "" -c +test_runner_timerfd_changelist: $(top_srcdir)/test/test.sh + $(top_srcdir)/test/test.sh -b "" -T + +DISTCLEANFILES += test/regress.gen.c test/regress.gen.h + +if BUILD_REGRESS +BUILT_SOURCES += test/regress.gen.c test/regress.gen.h +endif + +test_test_init_SOURCES = test/test-init.c +test_test_init_LDADD = libevent_core.la +test_test_dumpevents_SOURCES = test/test-dumpevents.c +test_test_dumpevents_LDADD = libevent_core.la +test_test_eof_SOURCES = test/test-eof.c +test_test_eof_LDADD = libevent_core.la +test_test_closed_SOURCES = test/test-closed.c +test_test_closed_LDADD = libevent_core.la +test_test_changelist_SOURCES = test/test-changelist.c +test_test_changelist_LDADD = libevent_core.la +test_test_weof_SOURCES = test/test-weof.c +test_test_weof_LDADD = libevent_core.la +test_test_time_SOURCES = test/test-time.c +test_test_time_LDADD = libevent_core.la +test_test_ratelim_SOURCES = test/test-ratelim.c +test_test_ratelim_LDADD = libevent_core.la -lm +test_test_fdleak_SOURCES = test/test-fdleak.c +test_test_fdleak_LDADD = libevent_core.la + +test_regress_SOURCES = \ + test/regress.c \ + test/regress.gen.c \ + test/regress.gen.h \ + test/regress_buffer.c \ + test/regress_bufferevent.c \ + test/regress_dns.c \ + test/regress_et.c \ + test/regress_finalize.c \ + test/regress_http.c \ + test/regress_listener.c \ + test/regress_main.c \ + test/regress_minheap.c \ + test/regress_rpc.c \ + test/regress_testutils.c \ + test/regress_testutils.h \ + test/regress_util.c \ + test/tinytest.c \ + $(regress_thread_SOURCES) \ + $(regress_zlib_SOURCES) + +if PTHREADS +regress_thread_SOURCES = test/regress_thread.c +PTHREAD_LIBS += libevent_pthreads.la +endif +if BUILD_WIN32 +if THREADS +regress_thread_SOURCES = test/regress_thread.c +endif +endif +if ZLIB_REGRESS +regress_zlib_SOURCES = test/regress_zlib.c +endif +if BUILD_WIN32 +test_regress_SOURCES += test/regress_iocp.c +endif + +test_regress_LDADD = $(LIBEVENT_GC_SECTIONS) libevent_core.la libevent_extra.la $(PTHREAD_LIBS) $(ZLIB_LIBS) +test_regress_CPPFLAGS = $(AM_CPPFLAGS) $(PTHREAD_CFLAGS) $(ZLIB_CFLAGS) -Itest +test_regress_LDFLAGS = $(PTHREAD_CFLAGS) + +if OPENSSL +test_regress_SOURCES += test/regress_ssl.c +test_regress_CPPFLAGS += $(OPENSSL_INCS) +test_regress_LDADD += libevent_openssl.la $(OPENSSL_LIBS) ${OPENSSL_LIBADD} +endif + +test_bench_SOURCES = test/bench.c +test_bench_LDADD = $(LIBEVENT_GC_SECTIONS) libevent.la +test_bench_cascade_SOURCES = test/bench_cascade.c +test_bench_cascade_LDADD = $(LIBEVENT_GC_SECTIONS) libevent.la +test_bench_http_SOURCES = test/bench_http.c +test_bench_http_LDADD = $(LIBEVENT_GC_SECTIONS) libevent.la +test_bench_httpclient_SOURCES = test/bench_httpclient.c +test_bench_httpclient_LDADD = $(LIBEVENT_GC_SECTIONS) libevent_core.la + +test/regress.gen.c test/regress.gen.h: test/rpcgen-attempted + +test/rpcgen-attempted: test/regress.rpc event_rpcgen.py test/rpcgen_wrapper.sh + $(AM_V_GEN)date -u > $@ + $(AM_V_at)if $(srcdir)/test/rpcgen_wrapper.sh $(srcdir)/test; then \ + true; \ + else \ + echo "No Python installed; stubbing out RPC test." >&2; \ + echo " "> test/regress.gen.c; \ + echo "#define NO_PYTHON_EXISTS" > test/regress.gen.h; \ + fi + +CLEANFILES += test/rpcgen-attempted + +$(TESTPROGRAMS) : libevent.la diff --git a/ipc/chromium/src/third_party/libevent/test/print-winsock-errors.c b/ipc/chromium/src/third_party/libevent/test/print-winsock-errors.c new file mode 100644 index 0000000000..64d6b0e70e --- /dev/null +++ b/ipc/chromium/src/third_party/libevent/test/print-winsock-errors.c @@ -0,0 +1,86 @@ +#include +#include + +#include +#include + +#include "event2/event.h" +#include "event2/util.h" +#include "event2/thread.h" + +#define E(x) printf (#x " -> \"%s\"\n", evutil_socket_error_to_string (x)); + +int main (int argc, char **argv) +{ + int i, j; + const char *s1, *s2; + +#ifdef EVTHREAD_USE_WINDOWS_THREADS_IMPLEMENTED + evthread_use_windows_threads (); +#endif + + s1 = evutil_socket_error_to_string (WSAEINTR); + + for (i = 0; i < 3; i++) { + printf ("\niteration %d:\n\n", i); + E(WSAEINTR); + E(WSAEACCES); + E(WSAEFAULT); + E(WSAEINVAL); + E(WSAEMFILE); + E(WSAEWOULDBLOCK); + E(WSAEINPROGRESS); + E(WSAEALREADY); + E(WSAENOTSOCK); + E(WSAEDESTADDRREQ); + E(WSAEMSGSIZE); + E(WSAEPROTOTYPE); + E(WSAENOPROTOOPT); + E(WSAEPROTONOSUPPORT); + E(WSAESOCKTNOSUPPORT); + E(WSAEOPNOTSUPP); + E(WSAEPFNOSUPPORT); + E(WSAEAFNOSUPPORT); + E(WSAEADDRINUSE); + E(WSAEADDRNOTAVAIL); + E(WSAENETDOWN); + E(WSAENETUNREACH); + E(WSAENETRESET); + E(WSAECONNABORTED); + E(WSAECONNRESET); + E(WSAENOBUFS); + E(WSAEISCONN); + E(WSAENOTCONN); + E(WSAESHUTDOWN); + E(WSAETIMEDOUT); + E(WSAECONNREFUSED); + E(WSAEHOSTDOWN); + E(WSAEHOSTUNREACH); + E(WSAEPROCLIM); + E(WSASYSNOTREADY); + E(WSAVERNOTSUPPORTED); + E(WSANOTINITIALISED); + E(WSAEDISCON); + E(WSATYPE_NOT_FOUND); + E(WSAHOST_NOT_FOUND); + E(WSATRY_AGAIN); + E(WSANO_RECOVERY); + E(WSANO_DATA); + E(0xdeadbeef); /* test the case where no message is available */ + + /* fill up the hash table a bit to make sure it grows properly */ + for (j = 0; j < 50; j++) { + int err; + evutil_secure_rng_get_bytes(&err, sizeof(err)); + evutil_socket_error_to_string(err); + } + } + + s2 = evutil_socket_error_to_string (WSAEINTR); + if (s1 != s2) + printf ("caching failed!\n"); + + libevent_global_shutdown (); + + return EXIT_SUCCESS; +} diff --git a/ipc/chromium/src/third_party/libevent/test/regress.c b/ipc/chromium/src/third_party/libevent/test/regress.c new file mode 100644 index 0000000000..08c30fab9b --- /dev/null +++ b/ipc/chromium/src/third_party/libevent/test/regress.c @@ -0,0 +1,3615 @@ +/* + * Copyright (c) 2003-2007 Niels Provos + * Copyright (c) 2007-2012 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "util-internal.h" + +#ifdef _WIN32 +#include +#include +#endif + +#include "event2/event-config.h" + +#include +#include +#ifdef EVENT__HAVE_SYS_TIME_H +#include +#endif +#include +#ifndef _WIN32 +#include +#include +#include +#include +#include +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include + +#include "event2/event.h" +#include "event2/event_struct.h" +#include "event2/event_compat.h" +#include "event2/tag.h" +#include "event2/buffer.h" +#include "event2/buffer_compat.h" +#include "event2/util.h" +#include "event-internal.h" +#include "evthread-internal.h" +#include "log-internal.h" +#include "time-internal.h" + +#include "regress.h" +#include "regress_thread.h" + +#ifndef _WIN32 +#include "regress.gen.h" +#endif + +evutil_socket_t pair[2]; +int test_ok; +int called; +struct event_base *global_base; + +static char wbuf[4096]; +static char rbuf[4096]; +static int woff; +static int roff; +static int usepersist; +static struct timeval tset; +static struct timeval tcalled; + + +#define TEST1 "this is a test" + +#ifdef _WIN32 +#define write(fd,buf,len) send((fd),(buf),(int)(len),0) +#define read(fd,buf,len) recv((fd),(buf),(int)(len),0) +#endif + +struct basic_cb_args +{ + struct event_base *eb; + struct event *ev; + unsigned int callcount; +}; + +static void +simple_read_cb(evutil_socket_t fd, short event, void *arg) +{ + char buf[256]; + int len; + + len = read(fd, buf, sizeof(buf)); + + if (len) { + if (!called) { + if (event_add(arg, NULL) == -1) + exit(1); + } + } else if (called == 1) + test_ok = 1; + + called++; +} + +static void +basic_read_cb(evutil_socket_t fd, short event, void *data) +{ + char buf[256]; + int len; + struct basic_cb_args *arg = data; + + len = read(fd, buf, sizeof(buf)); + + if (len < 0) { + tt_fail_perror("read (callback)"); + } else { + switch (arg->callcount++) { + case 0: /* first call: expect to read data; cycle */ + if (len > 0) + return; + + tt_fail_msg("EOF before data read"); + break; + + case 1: /* second call: expect EOF; stop */ + if (len > 0) + tt_fail_msg("not all data read on first cycle"); + break; + + default: /* third call: should not happen */ + tt_fail_msg("too many cycles"); + } + } + + event_del(arg->ev); + event_base_loopexit(arg->eb, NULL); +} + +static void +dummy_read_cb(evutil_socket_t fd, short event, void *arg) +{ +} + +static void +simple_write_cb(evutil_socket_t fd, short event, void *arg) +{ + int len; + + len = write(fd, TEST1, strlen(TEST1) + 1); + if (len == -1) + test_ok = 0; + else + test_ok = 1; +} + +static void +multiple_write_cb(evutil_socket_t fd, short event, void *arg) +{ + struct event *ev = arg; + int len; + + len = 128; + if (woff + len >= (int)sizeof(wbuf)) + len = sizeof(wbuf) - woff; + + len = write(fd, wbuf + woff, len); + if (len == -1) { + fprintf(stderr, "%s: write\n", __func__); + if (usepersist) + event_del(ev); + return; + } + + woff += len; + + if (woff >= (int)sizeof(wbuf)) { + shutdown(fd, EVUTIL_SHUT_WR); + if (usepersist) + event_del(ev); + return; + } + + if (!usepersist) { + if (event_add(ev, NULL) == -1) + exit(1); + } +} + +static void +multiple_read_cb(evutil_socket_t fd, short event, void *arg) +{ + struct event *ev = arg; + int len; + + len = read(fd, rbuf + roff, sizeof(rbuf) - roff); + if (len == -1) + fprintf(stderr, "%s: read\n", __func__); + if (len <= 0) { + if (usepersist) + event_del(ev); + return; + } + + roff += len; + if (!usepersist) { + if (event_add(ev, NULL) == -1) + exit(1); + } +} + +static void +timeout_cb(evutil_socket_t fd, short event, void *arg) +{ + evutil_gettimeofday(&tcalled, NULL); +} + +struct both { + struct event ev; + int nread; +}; + +static void +combined_read_cb(evutil_socket_t fd, short event, void *arg) +{ + struct both *both = arg; + char buf[128]; + int len; + + len = read(fd, buf, sizeof(buf)); + if (len == -1) + fprintf(stderr, "%s: read\n", __func__); + if (len <= 0) + return; + + both->nread += len; + if (event_add(&both->ev, NULL) == -1) + exit(1); +} + +static void +combined_write_cb(evutil_socket_t fd, short event, void *arg) +{ + struct both *both = arg; + char buf[128]; + int len; + + len = sizeof(buf); + if (len > both->nread) + len = both->nread; + + memset(buf, 'q', len); + + len = write(fd, buf, len); + if (len == -1) + fprintf(stderr, "%s: write\n", __func__); + if (len <= 0) { + shutdown(fd, EVUTIL_SHUT_WR); + return; + } + + both->nread -= len; + if (event_add(&both->ev, NULL) == -1) + exit(1); +} + +/* These macros used to replicate the work of the legacy test wrapper code */ +#define setup_test(x) do { \ + if (!in_legacy_test_wrapper) { \ + TT_FAIL(("Legacy test %s not wrapped properly", x)); \ + return; \ + } \ + } while (0) +#define cleanup_test() setup_test("cleanup") + +static void +test_simpleread(void) +{ + struct event ev; + + /* Very simple read test */ + setup_test("Simple read: "); + + if (write(pair[0], TEST1, strlen(TEST1)+1) < 0) { + tt_fail_perror("write"); + } + + shutdown(pair[0], EVUTIL_SHUT_WR); + + event_set(&ev, pair[1], EV_READ, simple_read_cb, &ev); + if (event_add(&ev, NULL) == -1) + exit(1); + event_dispatch(); + + cleanup_test(); +} + +static void +test_simplewrite(void) +{ + struct event ev; + + /* Very simple write test */ + setup_test("Simple write: "); + + event_set(&ev, pair[0], EV_WRITE, simple_write_cb, &ev); + if (event_add(&ev, NULL) == -1) + exit(1); + event_dispatch(); + + cleanup_test(); +} + +static void +simpleread_multiple_cb(evutil_socket_t fd, short event, void *arg) +{ + if (++called == 2) + test_ok = 1; +} + +static void +test_simpleread_multiple(void) +{ + struct event one, two; + + /* Very simple read test */ + setup_test("Simple read to multiple evens: "); + + if (write(pair[0], TEST1, strlen(TEST1)+1) < 0) { + tt_fail_perror("write"); + } + + shutdown(pair[0], EVUTIL_SHUT_WR); + + event_set(&one, pair[1], EV_READ, simpleread_multiple_cb, NULL); + if (event_add(&one, NULL) == -1) + exit(1); + event_set(&two, pair[1], EV_READ, simpleread_multiple_cb, NULL); + if (event_add(&two, NULL) == -1) + exit(1); + event_dispatch(); + + cleanup_test(); +} + +static int have_closed = 0; +static int premature_event = 0; +static void +simpleclose_close_fd_cb(evutil_socket_t s, short what, void *ptr) +{ + evutil_socket_t **fds = ptr; + TT_BLATHER(("Closing")); + evutil_closesocket(*fds[0]); + evutil_closesocket(*fds[1]); + *fds[0] = -1; + *fds[1] = -1; + have_closed = 1; +} + +static void +record_event_cb(evutil_socket_t s, short what, void *ptr) +{ + short *whatp = ptr; + if (!have_closed) + premature_event = 1; + *whatp = what; + TT_BLATHER(("Recorded %d on socket %d", (int)what, (int)s)); +} + +static void +test_simpleclose_rw(void *ptr) +{ + /* Test that a close of FD is detected as a read and as a write. */ + struct event_base *base = event_base_new(); + evutil_socket_t pair1[2]={-1,-1}, pair2[2] = {-1, -1}; + evutil_socket_t *to_close[2]; + struct event *rev=NULL, *wev=NULL, *closeev=NULL; + struct timeval tv; + short got_read_on_close = 0, got_write_on_close = 0; + char buf[1024]; + memset(buf, 99, sizeof(buf)); +#ifdef _WIN32 +#define LOCAL_SOCKETPAIR_AF AF_INET +#else +#define LOCAL_SOCKETPAIR_AF AF_UNIX +#endif + if (evutil_socketpair(LOCAL_SOCKETPAIR_AF, SOCK_STREAM, 0, pair1)<0) + TT_DIE(("socketpair: %s", strerror(errno))); + if (evutil_socketpair(LOCAL_SOCKETPAIR_AF, SOCK_STREAM, 0, pair2)<0) + TT_DIE(("socketpair: %s", strerror(errno))); + if (evutil_make_socket_nonblocking(pair1[1]) < 0) + TT_DIE(("make_socket_nonblocking")); + if (evutil_make_socket_nonblocking(pair2[1]) < 0) + TT_DIE(("make_socket_nonblocking")); + + /** Stuff pair2[1] full of data, until write fails */ + while (1) { + int r = write(pair2[1], buf, sizeof(buf)); + if (r<0) { + int err = evutil_socket_geterror(pair2[1]); + if (! EVUTIL_ERR_RW_RETRIABLE(err)) + TT_DIE(("write failed strangely: %s", + evutil_socket_error_to_string(err))); + break; + } + } + to_close[0] = &pair1[0]; + to_close[1] = &pair2[0]; + + closeev = event_new(base, -1, EV_TIMEOUT, simpleclose_close_fd_cb, + to_close); + rev = event_new(base, pair1[1], EV_READ, record_event_cb, + &got_read_on_close); + TT_BLATHER(("Waiting for read on %d", (int)pair1[1])); + wev = event_new(base, pair2[1], EV_WRITE, record_event_cb, + &got_write_on_close); + TT_BLATHER(("Waiting for write on %d", (int)pair2[1])); + tv.tv_sec = 0; + tv.tv_usec = 100*1000; /* Close pair1[0] after a little while, and make + * sure we get a read event. */ + event_add(closeev, &tv); + event_add(rev, NULL); + event_add(wev, NULL); + /* Don't let the test go on too long. */ + tv.tv_sec = 0; + tv.tv_usec = 200*1000; + event_base_loopexit(base, &tv); + event_base_loop(base, 0); + + tt_int_op(got_read_on_close, ==, EV_READ); + tt_int_op(got_write_on_close, ==, EV_WRITE); + tt_int_op(premature_event, ==, 0); + +end: + if (pair1[0] >= 0) + evutil_closesocket(pair1[0]); + if (pair1[1] >= 0) + evutil_closesocket(pair1[1]); + if (pair2[0] >= 0) + evutil_closesocket(pair2[0]); + if (pair2[1] >= 0) + evutil_closesocket(pair2[1]); + if (rev) + event_free(rev); + if (wev) + event_free(wev); + if (closeev) + event_free(closeev); + if (base) + event_base_free(base); +} + +static void +test_simpleclose(void *ptr) +{ + struct basic_test_data *data = ptr; + struct event_base *base = data->base; + evutil_socket_t *pair = data->pair; + const char *flags = (const char *)data->setup_data; + int et = !!strstr(flags, "ET"); + int persist = !!strstr(flags, "persist"); + short events = EV_CLOSED | (et ? EV_ET : 0) | (persist ? EV_PERSIST : 0); + struct event *ev = NULL; + short got_event; + + if (!(event_base_get_features(data->base) & EV_FEATURE_EARLY_CLOSE)) + tt_skip(); + + /* XXX: should this code moved to regress_et.c ? */ + if (et && !(event_base_get_features(data->base) & EV_FEATURE_ET)) + tt_skip(); + + ev = event_new(base, pair[0], events, record_event_cb, &got_event); + tt_assert(ev); + tt_assert(!event_add(ev, NULL)); + + got_event = 0; + if (strstr(flags, "close")) { + tt_assert(!evutil_closesocket(pair[1])); + /* avoid closing in setup routines */ + pair[1] = -1; + } else if (strstr(flags, "shutdown")) { + tt_assert(!shutdown(pair[1], EVUTIL_SHUT_WR)); + } else { + tt_abort_msg("unknown flags"); + } + + /* w/o edge-triggerd but w/ persist it will not stop */ + if (!et && persist) { + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 10000; + tt_assert(!event_base_loopexit(base, &tv)); + } + + tt_int_op(event_base_loop(base, EVLOOP_NONBLOCK), ==, !persist); + tt_int_op(got_event, ==, (events & ~EV_PERSIST)); + +end: + if (ev) + event_free(ev); +} + +static void +test_multiple(void) +{ + struct event ev, ev2; + int i; + + /* Multiple read and write test */ + setup_test("Multiple read/write: "); + memset(rbuf, 0, sizeof(rbuf)); + for (i = 0; i < (int)sizeof(wbuf); i++) + wbuf[i] = i; + + roff = woff = 0; + usepersist = 0; + + event_set(&ev, pair[0], EV_WRITE, multiple_write_cb, &ev); + if (event_add(&ev, NULL) == -1) + exit(1); + event_set(&ev2, pair[1], EV_READ, multiple_read_cb, &ev2); + if (event_add(&ev2, NULL) == -1) + exit(1); + event_dispatch(); + + if (roff == woff) + test_ok = memcmp(rbuf, wbuf, sizeof(wbuf)) == 0; + + cleanup_test(); +} + +static void +test_persistent(void) +{ + struct event ev, ev2; + int i; + + /* Multiple read and write test with persist */ + setup_test("Persist read/write: "); + memset(rbuf, 0, sizeof(rbuf)); + for (i = 0; i < (int)sizeof(wbuf); i++) + wbuf[i] = i; + + roff = woff = 0; + usepersist = 1; + + event_set(&ev, pair[0], EV_WRITE|EV_PERSIST, multiple_write_cb, &ev); + if (event_add(&ev, NULL) == -1) + exit(1); + event_set(&ev2, pair[1], EV_READ|EV_PERSIST, multiple_read_cb, &ev2); + if (event_add(&ev2, NULL) == -1) + exit(1); + event_dispatch(); + + if (roff == woff) + test_ok = memcmp(rbuf, wbuf, sizeof(wbuf)) == 0; + + cleanup_test(); +} + +static void +test_combined(void) +{ + struct both r1, r2, w1, w2; + + setup_test("Combined read/write: "); + memset(&r1, 0, sizeof(r1)); + memset(&r2, 0, sizeof(r2)); + memset(&w1, 0, sizeof(w1)); + memset(&w2, 0, sizeof(w2)); + + w1.nread = 4096; + w2.nread = 8192; + + event_set(&r1.ev, pair[0], EV_READ, combined_read_cb, &r1); + event_set(&w1.ev, pair[0], EV_WRITE, combined_write_cb, &w1); + event_set(&r2.ev, pair[1], EV_READ, combined_read_cb, &r2); + event_set(&w2.ev, pair[1], EV_WRITE, combined_write_cb, &w2); + tt_assert(event_add(&r1.ev, NULL) != -1); + tt_assert(!event_add(&w1.ev, NULL)); + tt_assert(!event_add(&r2.ev, NULL)); + tt_assert(!event_add(&w2.ev, NULL)); + event_dispatch(); + + if (r1.nread == 8192 && r2.nread == 4096) + test_ok = 1; + +end: + cleanup_test(); +} + +static void +test_simpletimeout(void) +{ + struct timeval tv; + struct event ev; + + setup_test("Simple timeout: "); + + tv.tv_usec = 200*1000; + tv.tv_sec = 0; + evutil_timerclear(&tcalled); + evtimer_set(&ev, timeout_cb, NULL); + evtimer_add(&ev, &tv); + + evutil_gettimeofday(&tset, NULL); + event_dispatch(); + test_timeval_diff_eq(&tset, &tcalled, 200); + + test_ok = 1; +end: + cleanup_test(); +} + +static void +periodic_timeout_cb(evutil_socket_t fd, short event, void *arg) +{ + int *count = arg; + + (*count)++; + if (*count == 6) { + /* call loopexit only once - on slow machines(?), it is + * apparently possible for this to get called twice. */ + test_ok = 1; + event_base_loopexit(global_base, NULL); + } +} + +static void +test_persistent_timeout(void) +{ + struct timeval tv; + struct event ev; + int count = 0; + + evutil_timerclear(&tv); + tv.tv_usec = 10000; + + event_assign(&ev, global_base, -1, EV_TIMEOUT|EV_PERSIST, + periodic_timeout_cb, &count); + event_add(&ev, &tv); + + event_dispatch(); + + event_del(&ev); +} + +static void +test_persistent_timeout_jump(void *ptr) +{ + struct basic_test_data *data = ptr; + struct event ev; + int count = 0; + struct timeval msec100 = { 0, 100 * 1000 }; + struct timeval msec50 = { 0, 50 * 1000 }; + struct timeval msec300 = { 0, 300 * 1000 }; + + event_assign(&ev, data->base, -1, EV_PERSIST, periodic_timeout_cb, &count); + event_add(&ev, &msec100); + /* Wait for a bit */ + evutil_usleep_(&msec300); + event_base_loopexit(data->base, &msec50); + event_base_dispatch(data->base); + tt_int_op(count, ==, 1); + +end: + event_del(&ev); +} + +struct persist_active_timeout_called { + int n; + short events[16]; + struct timeval tvs[16]; +}; + +static void +activate_cb(evutil_socket_t fd, short event, void *arg) +{ + struct event *ev = arg; + event_active(ev, EV_READ, 1); +} + +static void +persist_active_timeout_cb(evutil_socket_t fd, short event, void *arg) +{ + struct persist_active_timeout_called *c = arg; + if (c->n < 15) { + c->events[c->n] = event; + evutil_gettimeofday(&c->tvs[c->n], NULL); + ++c->n; + } +} + +static void +test_persistent_active_timeout(void *ptr) +{ + struct timeval tv, tv2, tv_exit, start; + struct event ev; + struct persist_active_timeout_called res; + + struct basic_test_data *data = ptr; + struct event_base *base = data->base; + + memset(&res, 0, sizeof(res)); + + tv.tv_sec = 0; + tv.tv_usec = 200 * 1000; + event_assign(&ev, base, -1, EV_TIMEOUT|EV_PERSIST, + persist_active_timeout_cb, &res); + event_add(&ev, &tv); + + tv2.tv_sec = 0; + tv2.tv_usec = 100 * 1000; + event_base_once(base, -1, EV_TIMEOUT, activate_cb, &ev, &tv2); + + tv_exit.tv_sec = 0; + tv_exit.tv_usec = 600 * 1000; + event_base_loopexit(base, &tv_exit); + + event_base_assert_ok_(base); + evutil_gettimeofday(&start, NULL); + + event_base_dispatch(base); + event_base_assert_ok_(base); + + tt_int_op(res.n, ==, 3); + tt_int_op(res.events[0], ==, EV_READ); + tt_int_op(res.events[1], ==, EV_TIMEOUT); + tt_int_op(res.events[2], ==, EV_TIMEOUT); + test_timeval_diff_eq(&start, &res.tvs[0], 100); + test_timeval_diff_eq(&start, &res.tvs[1], 300); + test_timeval_diff_eq(&start, &res.tvs[2], 500); +end: + event_del(&ev); +} + +struct common_timeout_info { + struct event ev; + struct timeval called_at; + int which; + int count; +}; + +static void +common_timeout_cb(evutil_socket_t fd, short event, void *arg) +{ + struct common_timeout_info *ti = arg; + ++ti->count; + evutil_gettimeofday(&ti->called_at, NULL); + if (ti->count >= 4) + event_del(&ti->ev); +} + +static void +test_common_timeout(void *ptr) +{ + struct basic_test_data *data = ptr; + + struct event_base *base = data->base; + int i; + struct common_timeout_info info[100]; + + struct timeval start; + struct timeval tmp_100_ms = { 0, 100*1000 }; + struct timeval tmp_200_ms = { 0, 200*1000 }; + struct timeval tmp_5_sec = { 5, 0 }; + struct timeval tmp_5M_usec = { 0, 5*1000*1000 }; + + const struct timeval *ms_100, *ms_200, *sec_5; + + ms_100 = event_base_init_common_timeout(base, &tmp_100_ms); + ms_200 = event_base_init_common_timeout(base, &tmp_200_ms); + sec_5 = event_base_init_common_timeout(base, &tmp_5_sec); + tt_assert(ms_100); + tt_assert(ms_200); + tt_assert(sec_5); + tt_ptr_op(event_base_init_common_timeout(base, &tmp_200_ms), + ==, ms_200); + tt_ptr_op(event_base_init_common_timeout(base, ms_200), ==, ms_200); + tt_ptr_op(event_base_init_common_timeout(base, &tmp_5M_usec), ==, sec_5); + tt_int_op(ms_100->tv_sec, ==, 0); + tt_int_op(ms_200->tv_sec, ==, 0); + tt_int_op(sec_5->tv_sec, ==, 5); + tt_int_op(ms_100->tv_usec, ==, 100000|0x50000000); + tt_int_op(ms_200->tv_usec, ==, 200000|0x50100000); + tt_int_op(sec_5->tv_usec, ==, 0|0x50200000); + + memset(info, 0, sizeof(info)); + + for (i=0; i<100; ++i) { + info[i].which = i; + event_assign(&info[i].ev, base, -1, EV_TIMEOUT|EV_PERSIST, + common_timeout_cb, &info[i]); + if (i % 2) { + if ((i%20)==1) { + /* Glass-box test: Make sure we survive the + * transition to non-common timeouts. It's + * a little tricky. */ + event_add(&info[i].ev, ms_200); + event_add(&info[i].ev, &tmp_100_ms); + } else if ((i%20)==3) { + /* Check heap-to-common too. */ + event_add(&info[i].ev, &tmp_200_ms); + event_add(&info[i].ev, ms_100); + } else if ((i%20)==5) { + /* Also check common-to-common. */ + event_add(&info[i].ev, ms_200); + event_add(&info[i].ev, ms_100); + } else { + event_add(&info[i].ev, ms_100); + } + } else { + event_add(&info[i].ev, ms_200); + } + } + + event_base_assert_ok_(base); + evutil_gettimeofday(&start, NULL); + event_base_dispatch(base); + + event_base_assert_ok_(base); + + for (i=0; i<10; ++i) { + tt_int_op(info[i].count, ==, 4); + if (i % 2) { + test_timeval_diff_eq(&start, &info[i].called_at, 400); + } else { + test_timeval_diff_eq(&start, &info[i].called_at, 800); + } + } + + /* Make sure we can free the base with some events in. */ + for (i=0; i<100; ++i) { + if (i % 2) { + event_add(&info[i].ev, ms_100); + } else { + event_add(&info[i].ev, ms_200); + } + } + +end: + event_base_free(data->base); /* need to do this here before info is + * out-of-scope */ + data->base = NULL; +} + +#ifndef _WIN32 + +#define current_base event_global_current_base_ +extern struct event_base *current_base; + +static void +fork_signal_cb(evutil_socket_t fd, short events, void *arg) +{ + event_del(arg); +} + +int child_pair[2] = { -1, -1 }; +static void +simple_child_read_cb(evutil_socket_t fd, short event, void *arg) +{ + char buf[256]; + int len; + + len = read(fd, buf, sizeof(buf)); + if (write(child_pair[0], "", 1) < 0) + tt_fail_perror("write"); + + if (len) { + if (!called) { + if (event_add(arg, NULL) == -1) + exit(1); + } + } else if (called == 1) + test_ok = 1; + + called++; +} + +#define TEST_FORK_EXIT_SUCCESS 76 +static void fork_wait_check(int pid) +{ + int status; + + TT_BLATHER(("Before waitpid")); + +#ifdef WNOWAIT + if ((waitpid(pid, &status, WNOWAIT) == -1 && errno == EINVAL) && +#else + if ( +#endif + waitpid(pid, &status, 0) == -1) { + perror("waitpid"); + exit(1); + } + TT_BLATHER(("After waitpid")); + + if (WEXITSTATUS(status) != TEST_FORK_EXIT_SUCCESS) { + fprintf(stdout, "FAILED (exit): %d\n", WEXITSTATUS(status)); + exit(1); + } +} +static void +test_fork(void) +{ + char c; + struct event ev, sig_ev, usr_ev, existing_ev; + pid_t pid; + + setup_test("After fork: "); + + { + if (evutil_socketpair(AF_UNIX, SOCK_STREAM, 0, child_pair) == -1) { + fprintf(stderr, "%s: socketpair\n", __func__); + exit(1); + } + + if (evutil_make_socket_nonblocking(child_pair[0]) == -1) { + fprintf(stderr, "fcntl(O_NONBLOCK)"); + exit(1); + } + } + + tt_assert(current_base); + evthread_make_base_notifiable(current_base); + + if (write(pair[0], TEST1, strlen(TEST1)+1) < 0) { + tt_fail_perror("write"); + } + + event_set(&ev, pair[1], EV_READ, simple_child_read_cb, &ev); + if (event_add(&ev, NULL) == -1) + exit(1); + + evsignal_set(&sig_ev, SIGCHLD, fork_signal_cb, &sig_ev); + evsignal_add(&sig_ev, NULL); + + evsignal_set(&existing_ev, SIGUSR2, fork_signal_cb, &existing_ev); + evsignal_add(&existing_ev, NULL); + + event_base_assert_ok_(current_base); + TT_BLATHER(("Before fork")); + if ((pid = regress_fork()) == 0) { + /* in the child */ + TT_BLATHER(("In child, before reinit")); + event_base_assert_ok_(current_base); + if (event_reinit(current_base) == -1) { + fprintf(stdout, "FAILED (reinit)\n"); + exit(1); + } + TT_BLATHER(("After reinit")); + event_base_assert_ok_(current_base); + TT_BLATHER(("After assert-ok")); + + evsignal_del(&sig_ev); + + evsignal_set(&usr_ev, SIGUSR1, fork_signal_cb, &usr_ev); + evsignal_add(&usr_ev, NULL); + kill(getpid(), SIGUSR1); + kill(getpid(), SIGUSR2); + + called = 0; + + event_dispatch(); + + event_base_free(current_base); + + /* we do not send an EOF; simple_read_cb requires an EOF + * to set test_ok. we just verify that the callback was + * called. */ + exit(test_ok != 0 || called != 2 ? -2 : TEST_FORK_EXIT_SUCCESS); + } + + /** wait until client read first message */ + if (read(child_pair[1], &c, 1) < 0) { + tt_fail_perror("read"); + } + if (write(pair[0], TEST1, strlen(TEST1)+1) < 0) { + tt_fail_perror("write"); + } + + fork_wait_check(pid); + + /* test that the current event loop still works */ + if (write(pair[0], TEST1, strlen(TEST1)+1) < 0) { + fprintf(stderr, "%s: write\n", __func__); + } + + shutdown(pair[0], EVUTIL_SHUT_WR); + + evsignal_set(&usr_ev, SIGUSR1, fork_signal_cb, &usr_ev); + evsignal_add(&usr_ev, NULL); + kill(getpid(), SIGUSR1); + kill(getpid(), SIGUSR2); + + event_dispatch(); + + evsignal_del(&sig_ev); + tt_int_op(test_ok, ==, 1); + + end: + cleanup_test(); + if (child_pair[0] != -1) + evutil_closesocket(child_pair[0]); + if (child_pair[1] != -1) + evutil_closesocket(child_pair[1]); +} + +#ifdef EVTHREAD_USE_PTHREADS_IMPLEMENTED +static void* del_wait_thread(void *arg) +{ + struct timeval tv_start, tv_end; + + evutil_gettimeofday(&tv_start, NULL); + event_dispatch(); + evutil_gettimeofday(&tv_end, NULL); + + test_timeval_diff_eq(&tv_start, &tv_end, 300); + + end: + return NULL; +} + +static void +del_wait_cb(evutil_socket_t fd, short event, void *arg) +{ + struct timeval delay = { 0, 300*1000 }; + TT_BLATHER(("Sleeping: %i", test_ok)); + evutil_usleep_(&delay); + ++test_ok; +} + +static void +test_del_wait(void) +{ + struct event ev; + THREAD_T thread; + + setup_test("event_del will wait: "); + + event_set(&ev, pair[1], EV_READ|EV_PERSIST, del_wait_cb, &ev); + event_add(&ev, NULL); + + THREAD_START(thread, del_wait_thread, NULL); + + if (write(pair[0], TEST1, strlen(TEST1)+1) < 0) { + tt_fail_perror("write"); + } + + { + struct timeval delay = { 0, 30*1000 }; + evutil_usleep_(&delay); + } + + { + struct timeval tv_start, tv_end; + evutil_gettimeofday(&tv_start, NULL); + event_del(&ev); + evutil_gettimeofday(&tv_end, NULL); + test_timeval_diff_eq(&tv_start, &tv_end, 270); + } + + THREAD_JOIN(thread); + + tt_int_op(test_ok, ==, 1); + + end: + ; +} + +static void null_cb(evutil_socket_t fd, short what, void *arg) {} +static void* test_del_notify_thread(void *arg) +{ + event_dispatch(); + return NULL; +} +static void +test_del_notify(void) +{ + struct event ev; + THREAD_T thread; + + test_ok = 1; + + event_set(&ev, -1, EV_READ, null_cb, &ev); + event_add(&ev, NULL); + + THREAD_START(thread, test_del_notify_thread, NULL); + + { + struct timeval delay = { 0, 1000 }; + evutil_usleep_(&delay); + } + + event_del(&ev); + THREAD_JOIN(thread); +} +#endif + +static void +signal_cb_sa(int sig) +{ + test_ok = 2; +} + +static void +signal_cb(evutil_socket_t fd, short event, void *arg) +{ + struct event *ev = arg; + + evsignal_del(ev); + test_ok = 1; +} + +static void +test_simplesignal_impl(int find_reorder) +{ + struct event ev; + struct itimerval itv; + + evsignal_set(&ev, SIGALRM, signal_cb, &ev); + evsignal_add(&ev, NULL); + /* find bugs in which operations are re-ordered */ + if (find_reorder) { + evsignal_del(&ev); + evsignal_add(&ev, NULL); + } + + memset(&itv, 0, sizeof(itv)); + itv.it_value.tv_sec = 0; + itv.it_value.tv_usec = 100000; + if (setitimer(ITIMER_REAL, &itv, NULL) == -1) + goto skip_simplesignal; + + event_dispatch(); + skip_simplesignal: + if (evsignal_del(&ev) == -1) + test_ok = 0; + + cleanup_test(); +} + +static void +test_simplestsignal(void) +{ + setup_test("Simplest one signal: "); + test_simplesignal_impl(0); +} + +static void +test_simplesignal(void) +{ + setup_test("Simple signal: "); + test_simplesignal_impl(1); +} + +static void +test_multiplesignal(void) +{ + struct event ev_one, ev_two; + struct itimerval itv; + + setup_test("Multiple signal: "); + + evsignal_set(&ev_one, SIGALRM, signal_cb, &ev_one); + evsignal_add(&ev_one, NULL); + + evsignal_set(&ev_two, SIGALRM, signal_cb, &ev_two); + evsignal_add(&ev_two, NULL); + + memset(&itv, 0, sizeof(itv)); + itv.it_value.tv_sec = 0; + itv.it_value.tv_usec = 100000; + if (setitimer(ITIMER_REAL, &itv, NULL) == -1) + goto skip_simplesignal; + + event_dispatch(); + + skip_simplesignal: + if (evsignal_del(&ev_one) == -1) + test_ok = 0; + if (evsignal_del(&ev_two) == -1) + test_ok = 0; + + cleanup_test(); +} + +static void +test_immediatesignal(void) +{ + struct event ev; + + test_ok = 0; + evsignal_set(&ev, SIGUSR1, signal_cb, &ev); + evsignal_add(&ev, NULL); + kill(getpid(), SIGUSR1); + event_loop(EVLOOP_NONBLOCK); + evsignal_del(&ev); + cleanup_test(); +} + +static void +test_signal_dealloc(void) +{ + /* make sure that evsignal_event is event_del'ed and pipe closed */ + struct event ev; + struct event_base *base = event_init(); + evsignal_set(&ev, SIGUSR1, signal_cb, &ev); + evsignal_add(&ev, NULL); + evsignal_del(&ev); + event_base_free(base); + /* If we got here without asserting, we're fine. */ + test_ok = 1; + cleanup_test(); +} + +static void +test_signal_pipeloss(void) +{ + /* make sure that the base1 pipe is closed correctly. */ + struct event_base *base1, *base2; + int pipe1; + test_ok = 0; + base1 = event_init(); + pipe1 = base1->sig.ev_signal_pair[0]; + base2 = event_init(); + event_base_free(base2); + event_base_free(base1); + if (close(pipe1) != -1 || errno!=EBADF) { + /* fd must be closed, so second close gives -1, EBADF */ + printf("signal pipe not closed. "); + test_ok = 0; + } else { + test_ok = 1; + } + cleanup_test(); +} + +/* + * make two bases to catch signals, use both of them. this only works + * for event mechanisms that use our signal pipe trick. kqueue handles + * signals internally, and all interested kqueues get all the signals. + */ +static void +test_signal_switchbase(void) +{ + struct event ev1, ev2; + struct event_base *base1, *base2; + int is_kqueue; + test_ok = 0; + base1 = event_init(); + base2 = event_init(); + is_kqueue = !strcmp(event_get_method(),"kqueue"); + evsignal_set(&ev1, SIGUSR1, signal_cb, &ev1); + evsignal_set(&ev2, SIGUSR1, signal_cb, &ev2); + if (event_base_set(base1, &ev1) || + event_base_set(base2, &ev2) || + event_add(&ev1, NULL) || + event_add(&ev2, NULL)) { + fprintf(stderr, "%s: cannot set base, add\n", __func__); + exit(1); + } + + tt_ptr_op(event_get_base(&ev1), ==, base1); + tt_ptr_op(event_get_base(&ev2), ==, base2); + + test_ok = 0; + /* can handle signal before loop is called */ + kill(getpid(), SIGUSR1); + event_base_loop(base2, EVLOOP_NONBLOCK); + if (is_kqueue) { + if (!test_ok) + goto end; + test_ok = 0; + } + event_base_loop(base1, EVLOOP_NONBLOCK); + if (test_ok && !is_kqueue) { + test_ok = 0; + + /* set base1 to handle signals */ + event_base_loop(base1, EVLOOP_NONBLOCK); + kill(getpid(), SIGUSR1); + event_base_loop(base1, EVLOOP_NONBLOCK); + event_base_loop(base2, EVLOOP_NONBLOCK); + } +end: + event_base_free(base1); + event_base_free(base2); + cleanup_test(); +} + +/* + * assert that a signal event removed from the event queue really is + * removed - with no possibility of it's parent handler being fired. + */ +static void +test_signal_assert(void) +{ + struct event ev; + struct event_base *base = event_init(); + test_ok = 0; + /* use SIGCONT so we don't kill ourselves when we signal to nowhere */ + evsignal_set(&ev, SIGCONT, signal_cb, &ev); + evsignal_add(&ev, NULL); + /* + * if evsignal_del() fails to reset the handler, it's current handler + * will still point to evsig_handler(). + */ + evsignal_del(&ev); + + kill(getpid(), SIGCONT); +#if 0 + /* only way to verify we were in evsig_handler() */ + /* XXXX Now there's no longer a good way. */ + if (base->sig.evsig_caught) + test_ok = 0; + else + test_ok = 1; +#else + test_ok = 1; +#endif + + event_base_free(base); + cleanup_test(); + return; +} + +/* + * assert that we restore our previous signal handler properly. + */ +static void +test_signal_restore(void) +{ + struct event ev; + struct event_base *base = event_init(); +#ifdef EVENT__HAVE_SIGACTION + struct sigaction sa; +#endif + + test_ok = 0; +#ifdef EVENT__HAVE_SIGACTION + sa.sa_handler = signal_cb_sa; + sa.sa_flags = 0x0; + sigemptyset(&sa.sa_mask); + if (sigaction(SIGUSR1, &sa, NULL) == -1) + goto out; +#else + if (signal(SIGUSR1, signal_cb_sa) == SIG_ERR) + goto out; +#endif + evsignal_set(&ev, SIGUSR1, signal_cb, &ev); + evsignal_add(&ev, NULL); + evsignal_del(&ev); + + kill(getpid(), SIGUSR1); + /* 1 == signal_cb, 2 == signal_cb_sa, we want our previous handler */ + if (test_ok != 2) + test_ok = 0; +out: + event_base_free(base); + cleanup_test(); + return; +} + +static void +signal_cb_swp(int sig, short event, void *arg) +{ + called++; + if (called < 5) + kill(getpid(), sig); + else + event_loopexit(NULL); +} +static void +timeout_cb_swp(evutil_socket_t fd, short event, void *arg) +{ + if (called == -1) { + struct timeval tv = {5, 0}; + + called = 0; + evtimer_add((struct event *)arg, &tv); + kill(getpid(), SIGUSR1); + return; + } + test_ok = 0; + event_loopexit(NULL); +} + +static void +test_signal_while_processing(void) +{ + struct event_base *base = event_init(); + struct event ev, ev_timer; + struct timeval tv = {0, 0}; + + setup_test("Receiving a signal while processing other signal: "); + + called = -1; + test_ok = 1; + signal_set(&ev, SIGUSR1, signal_cb_swp, NULL); + signal_add(&ev, NULL); + evtimer_set(&ev_timer, timeout_cb_swp, &ev_timer); + evtimer_add(&ev_timer, &tv); + event_dispatch(); + + event_base_free(base); + cleanup_test(); + return; +} +#endif + +static void +test_free_active_base(void *ptr) +{ + struct basic_test_data *data = ptr; + struct event_base *base1; + struct event ev1; + + base1 = event_init(); + tt_assert(base1); + event_assign(&ev1, base1, data->pair[1], EV_READ, dummy_read_cb, NULL); + event_add(&ev1, NULL); + event_base_free(base1); /* should not crash */ + + base1 = event_init(); + tt_assert(base1); + event_assign(&ev1, base1, data->pair[0], 0, dummy_read_cb, NULL); + event_active(&ev1, EV_READ, 1); + event_base_free(base1); +end: + ; +} + +static void +test_manipulate_active_events(void *ptr) +{ + struct basic_test_data *data = ptr; + struct event_base *base = data->base; + struct event ev1; + + event_assign(&ev1, base, -1, EV_TIMEOUT, dummy_read_cb, NULL); + + /* Make sure an active event is pending. */ + event_active(&ev1, EV_READ, 1); + tt_int_op(event_pending(&ev1, EV_READ|EV_TIMEOUT|EV_WRITE, NULL), + ==, EV_READ); + + /* Make sure that activating an event twice works. */ + event_active(&ev1, EV_WRITE, 1); + tt_int_op(event_pending(&ev1, EV_READ|EV_TIMEOUT|EV_WRITE, NULL), + ==, EV_READ|EV_WRITE); + +end: + event_del(&ev1); +} + +static void +event_selfarg_cb(evutil_socket_t fd, short event, void *arg) +{ + struct event *ev = arg; + struct event_base *base = event_get_base(ev); + event_base_assert_ok_(base); + event_base_loopexit(base, NULL); + tt_want(ev == event_base_get_running_event(base)); +} + +static void +test_event_new_selfarg(void *ptr) +{ + struct basic_test_data *data = ptr; + struct event_base *base = data->base; + struct event *ev = event_new(base, -1, EV_READ, event_selfarg_cb, + event_self_cbarg()); + + event_active(ev, EV_READ, 1); + event_base_dispatch(base); + + event_free(ev); +} + +static void +test_event_assign_selfarg(void *ptr) +{ + struct basic_test_data *data = ptr; + struct event_base *base = data->base; + struct event ev; + + event_assign(&ev, base, -1, EV_READ, event_selfarg_cb, + event_self_cbarg()); + event_active(&ev, EV_READ, 1); + event_base_dispatch(base); +} + +static void +test_event_base_get_num_events(void *ptr) +{ + struct basic_test_data *data = ptr; + struct event_base *base = data->base; + struct event ev; + int event_count_active; + int event_count_virtual; + int event_count_added; + int event_count_active_virtual; + int event_count_active_added; + int event_count_virtual_added; + int event_count_active_added_virtual; + + struct timeval qsec = {0, 100000}; + + event_assign(&ev, base, -1, EV_READ, event_selfarg_cb, + event_self_cbarg()); + + event_add(&ev, &qsec); + event_count_active = event_base_get_num_events(base, + EVENT_BASE_COUNT_ACTIVE); + event_count_virtual = event_base_get_num_events(base, + EVENT_BASE_COUNT_VIRTUAL); + event_count_added = event_base_get_num_events(base, + EVENT_BASE_COUNT_ADDED); + event_count_active_virtual = event_base_get_num_events(base, + EVENT_BASE_COUNT_ACTIVE|EVENT_BASE_COUNT_VIRTUAL); + event_count_active_added = event_base_get_num_events(base, + EVENT_BASE_COUNT_ACTIVE|EVENT_BASE_COUNT_ADDED); + event_count_virtual_added = event_base_get_num_events(base, + EVENT_BASE_COUNT_VIRTUAL|EVENT_BASE_COUNT_ADDED); + event_count_active_added_virtual = event_base_get_num_events(base, + EVENT_BASE_COUNT_ACTIVE| + EVENT_BASE_COUNT_ADDED| + EVENT_BASE_COUNT_VIRTUAL); + tt_int_op(event_count_active, ==, 0); + tt_int_op(event_count_virtual, ==, 0); + /* libevent itself adds a timeout event, so the event_count is 2 here */ + tt_int_op(event_count_added, ==, 2); + tt_int_op(event_count_active_virtual, ==, 0); + tt_int_op(event_count_active_added, ==, 2); + tt_int_op(event_count_virtual_added, ==, 2); + tt_int_op(event_count_active_added_virtual, ==, 2); + + event_active(&ev, EV_READ, 1); + event_count_active = event_base_get_num_events(base, + EVENT_BASE_COUNT_ACTIVE); + event_count_virtual = event_base_get_num_events(base, + EVENT_BASE_COUNT_VIRTUAL); + event_count_added = event_base_get_num_events(base, + EVENT_BASE_COUNT_ADDED); + event_count_active_virtual = event_base_get_num_events(base, + EVENT_BASE_COUNT_ACTIVE|EVENT_BASE_COUNT_VIRTUAL); + event_count_active_added = event_base_get_num_events(base, + EVENT_BASE_COUNT_ACTIVE|EVENT_BASE_COUNT_ADDED); + event_count_virtual_added = event_base_get_num_events(base, + EVENT_BASE_COUNT_VIRTUAL|EVENT_BASE_COUNT_ADDED); + event_count_active_added_virtual = event_base_get_num_events(base, + EVENT_BASE_COUNT_ACTIVE| + EVENT_BASE_COUNT_ADDED| + EVENT_BASE_COUNT_VIRTUAL); + tt_int_op(event_count_active, ==, 1); + tt_int_op(event_count_virtual, ==, 0); + tt_int_op(event_count_added, ==, 3); + tt_int_op(event_count_active_virtual, ==, 1); + tt_int_op(event_count_active_added, ==, 4); + tt_int_op(event_count_virtual_added, ==, 3); + tt_int_op(event_count_active_added_virtual, ==, 4); + + event_base_loop(base, 0); + event_count_active = event_base_get_num_events(base, + EVENT_BASE_COUNT_ACTIVE); + event_count_virtual = event_base_get_num_events(base, + EVENT_BASE_COUNT_VIRTUAL); + event_count_added = event_base_get_num_events(base, + EVENT_BASE_COUNT_ADDED); + event_count_active_virtual = event_base_get_num_events(base, + EVENT_BASE_COUNT_ACTIVE|EVENT_BASE_COUNT_VIRTUAL); + event_count_active_added = event_base_get_num_events(base, + EVENT_BASE_COUNT_ACTIVE|EVENT_BASE_COUNT_ADDED); + event_count_virtual_added = event_base_get_num_events(base, + EVENT_BASE_COUNT_VIRTUAL|EVENT_BASE_COUNT_ADDED); + event_count_active_added_virtual = event_base_get_num_events(base, + EVENT_BASE_COUNT_ACTIVE| + EVENT_BASE_COUNT_ADDED| + EVENT_BASE_COUNT_VIRTUAL); + tt_int_op(event_count_active, ==, 0); + tt_int_op(event_count_virtual, ==, 0); + tt_int_op(event_count_added, ==, 0); + tt_int_op(event_count_active_virtual, ==, 0); + tt_int_op(event_count_active_added, ==, 0); + tt_int_op(event_count_virtual_added, ==, 0); + tt_int_op(event_count_active_added_virtual, ==, 0); + + event_base_add_virtual_(base); + event_count_active = event_base_get_num_events(base, + EVENT_BASE_COUNT_ACTIVE); + event_count_virtual = event_base_get_num_events(base, + EVENT_BASE_COUNT_VIRTUAL); + event_count_added = event_base_get_num_events(base, + EVENT_BASE_COUNT_ADDED); + event_count_active_virtual = event_base_get_num_events(base, + EVENT_BASE_COUNT_ACTIVE|EVENT_BASE_COUNT_VIRTUAL); + event_count_active_added = event_base_get_num_events(base, + EVENT_BASE_COUNT_ACTIVE|EVENT_BASE_COUNT_ADDED); + event_count_virtual_added = event_base_get_num_events(base, + EVENT_BASE_COUNT_VIRTUAL|EVENT_BASE_COUNT_ADDED); + event_count_active_added_virtual = event_base_get_num_events(base, + EVENT_BASE_COUNT_ACTIVE| + EVENT_BASE_COUNT_ADDED| + EVENT_BASE_COUNT_VIRTUAL); + tt_int_op(event_count_active, ==, 0); + tt_int_op(event_count_virtual, ==, 1); + tt_int_op(event_count_added, ==, 0); + tt_int_op(event_count_active_virtual, ==, 1); + tt_int_op(event_count_active_added, ==, 0); + tt_int_op(event_count_virtual_added, ==, 1); + tt_int_op(event_count_active_added_virtual, ==, 1); + +end: + ; +} + +static void +test_event_base_get_max_events(void *ptr) +{ + struct basic_test_data *data = ptr; + struct event_base *base = data->base; + struct event ev; + struct event ev2; + int event_count_active; + int event_count_virtual; + int event_count_added; + int event_count_active_virtual; + int event_count_active_added; + int event_count_virtual_added; + int event_count_active_added_virtual; + + struct timeval qsec = {0, 100000}; + + event_assign(&ev, base, -1, EV_READ, event_selfarg_cb, + event_self_cbarg()); + event_assign(&ev2, base, -1, EV_READ, event_selfarg_cb, + event_self_cbarg()); + + event_add(&ev, &qsec); + event_add(&ev2, &qsec); + event_del(&ev2); + + event_count_active = event_base_get_max_events(base, + EVENT_BASE_COUNT_ACTIVE, 0); + event_count_virtual = event_base_get_max_events(base, + EVENT_BASE_COUNT_VIRTUAL, 0); + event_count_added = event_base_get_max_events(base, + EVENT_BASE_COUNT_ADDED, 0); + event_count_active_virtual = event_base_get_max_events(base, + EVENT_BASE_COUNT_ACTIVE | EVENT_BASE_COUNT_VIRTUAL, 0); + event_count_active_added = event_base_get_max_events(base, + EVENT_BASE_COUNT_ACTIVE | EVENT_BASE_COUNT_ADDED, 0); + event_count_virtual_added = event_base_get_max_events(base, + EVENT_BASE_COUNT_VIRTUAL | EVENT_BASE_COUNT_ADDED, 0); + event_count_active_added_virtual = event_base_get_max_events(base, + EVENT_BASE_COUNT_ACTIVE | + EVENT_BASE_COUNT_ADDED | + EVENT_BASE_COUNT_VIRTUAL, 0); + + tt_int_op(event_count_active, ==, 0); + tt_int_op(event_count_virtual, ==, 0); + /* libevent itself adds a timeout event, so the event_count is 4 here */ + tt_int_op(event_count_added, ==, 4); + tt_int_op(event_count_active_virtual, ==, 0); + tt_int_op(event_count_active_added, ==, 4); + tt_int_op(event_count_virtual_added, ==, 4); + tt_int_op(event_count_active_added_virtual, ==, 4); + + event_active(&ev, EV_READ, 1); + event_count_active = event_base_get_max_events(base, + EVENT_BASE_COUNT_ACTIVE, 0); + event_count_virtual = event_base_get_max_events(base, + EVENT_BASE_COUNT_VIRTUAL, 0); + event_count_added = event_base_get_max_events(base, + EVENT_BASE_COUNT_ADDED, 0); + event_count_active_virtual = event_base_get_max_events(base, + EVENT_BASE_COUNT_ACTIVE | EVENT_BASE_COUNT_VIRTUAL, 0); + event_count_active_added = event_base_get_max_events(base, + EVENT_BASE_COUNT_ACTIVE | EVENT_BASE_COUNT_ADDED, 0); + event_count_virtual_added = event_base_get_max_events(base, + EVENT_BASE_COUNT_VIRTUAL | EVENT_BASE_COUNT_ADDED, 0); + event_count_active_added_virtual = event_base_get_max_events(base, + EVENT_BASE_COUNT_ACTIVE | + EVENT_BASE_COUNT_ADDED | + EVENT_BASE_COUNT_VIRTUAL, 0); + + tt_int_op(event_count_active, ==, 1); + tt_int_op(event_count_virtual, ==, 0); + tt_int_op(event_count_added, ==, 4); + tt_int_op(event_count_active_virtual, ==, 1); + tt_int_op(event_count_active_added, ==, 5); + tt_int_op(event_count_virtual_added, ==, 4); + tt_int_op(event_count_active_added_virtual, ==, 5); + + event_base_loop(base, 0); + event_count_active = event_base_get_max_events(base, + EVENT_BASE_COUNT_ACTIVE, 1); + event_count_virtual = event_base_get_max_events(base, + EVENT_BASE_COUNT_VIRTUAL, 1); + event_count_added = event_base_get_max_events(base, + EVENT_BASE_COUNT_ADDED, 1); + event_count_active_virtual = event_base_get_max_events(base, + EVENT_BASE_COUNT_ACTIVE | EVENT_BASE_COUNT_VIRTUAL, 0); + event_count_active_added = event_base_get_max_events(base, + EVENT_BASE_COUNT_ACTIVE | EVENT_BASE_COUNT_ADDED, 0); + event_count_virtual_added = event_base_get_max_events(base, + EVENT_BASE_COUNT_VIRTUAL | EVENT_BASE_COUNT_ADDED, 0); + event_count_active_added_virtual = event_base_get_max_events(base, + EVENT_BASE_COUNT_ACTIVE | + EVENT_BASE_COUNT_ADDED | + EVENT_BASE_COUNT_VIRTUAL, 1); + + tt_int_op(event_count_active, ==, 1); + tt_int_op(event_count_virtual, ==, 0); + tt_int_op(event_count_added, ==, 4); + tt_int_op(event_count_active_virtual, ==, 0); + tt_int_op(event_count_active_added, ==, 0); + tt_int_op(event_count_virtual_added, ==, 0); + tt_int_op(event_count_active_added_virtual, ==, 0); + + event_count_active = event_base_get_max_events(base, + EVENT_BASE_COUNT_ACTIVE, 0); + event_count_virtual = event_base_get_max_events(base, + EVENT_BASE_COUNT_VIRTUAL, 0); + event_count_added = event_base_get_max_events(base, + EVENT_BASE_COUNT_ADDED, 0); + tt_int_op(event_count_active, ==, 0); + tt_int_op(event_count_virtual, ==, 0); + tt_int_op(event_count_added, ==, 0); + + event_base_add_virtual_(base); + event_count_active = event_base_get_max_events(base, + EVENT_BASE_COUNT_ACTIVE, 0); + event_count_virtual = event_base_get_max_events(base, + EVENT_BASE_COUNT_VIRTUAL, 0); + event_count_added = event_base_get_max_events(base, + EVENT_BASE_COUNT_ADDED, 0); + event_count_active_virtual = event_base_get_max_events(base, + EVENT_BASE_COUNT_ACTIVE | EVENT_BASE_COUNT_VIRTUAL, 0); + event_count_active_added = event_base_get_max_events(base, + EVENT_BASE_COUNT_ACTIVE | EVENT_BASE_COUNT_ADDED, 0); + event_count_virtual_added = event_base_get_max_events(base, + EVENT_BASE_COUNT_VIRTUAL | EVENT_BASE_COUNT_ADDED, 0); + event_count_active_added_virtual = event_base_get_max_events(base, + EVENT_BASE_COUNT_ACTIVE | + EVENT_BASE_COUNT_ADDED | + EVENT_BASE_COUNT_VIRTUAL, 0); + + tt_int_op(event_count_active, ==, 0); + tt_int_op(event_count_virtual, ==, 1); + tt_int_op(event_count_added, ==, 0); + tt_int_op(event_count_active_virtual, ==, 1); + tt_int_op(event_count_active_added, ==, 0); + tt_int_op(event_count_virtual_added, ==, 1); + tt_int_op(event_count_active_added_virtual, ==, 1); + +end: + ; +} + +static void +test_bad_assign(void *ptr) +{ + struct event ev; + int r; + /* READ|SIGNAL is not allowed */ + r = event_assign(&ev, NULL, -1, EV_SIGNAL|EV_READ, dummy_read_cb, NULL); + tt_int_op(r,==,-1); + +end: + ; +} + +static int reentrant_cb_run = 0; + +static void +bad_reentrant_run_loop_cb(evutil_socket_t fd, short what, void *ptr) +{ + struct event_base *base = ptr; + int r; + reentrant_cb_run = 1; + /* This reentrant call to event_base_loop should be detected and + * should fail */ + r = event_base_loop(base, 0); + tt_int_op(r, ==, -1); +end: + ; +} + +static void +test_bad_reentrant(void *ptr) +{ + struct basic_test_data *data = ptr; + struct event_base *base = data->base; + struct event ev; + int r; + event_assign(&ev, base, -1, + 0, bad_reentrant_run_loop_cb, base); + + event_active(&ev, EV_WRITE, 1); + r = event_base_loop(base, 0); + tt_int_op(r, ==, 1); + tt_int_op(reentrant_cb_run, ==, 1); +end: + ; +} + +static int n_write_a_byte_cb=0; +static int n_read_and_drain_cb=0; +static int n_activate_other_event_cb=0; +static void +write_a_byte_cb(evutil_socket_t fd, short what, void *arg) +{ + char buf[] = "x"; + if (write(fd, buf, 1) == 1) + ++n_write_a_byte_cb; +} +static void +read_and_drain_cb(evutil_socket_t fd, short what, void *arg) +{ + char buf[128]; + int n; + ++n_read_and_drain_cb; + while ((n = read(fd, buf, sizeof(buf))) > 0) + ; +} + +static void +activate_other_event_cb(evutil_socket_t fd, short what, void *other_) +{ + struct event *ev_activate = other_; + ++n_activate_other_event_cb; + event_active_later_(ev_activate, EV_READ); +} + +static void +test_active_later(void *ptr) +{ + struct basic_test_data *data = ptr; + struct event *ev1 = NULL, *ev2 = NULL; + struct event ev3, ev4; + struct timeval qsec = {0, 100000}; + ev1 = event_new(data->base, data->pair[0], EV_READ|EV_PERSIST, read_and_drain_cb, NULL); + ev2 = event_new(data->base, data->pair[1], EV_WRITE|EV_PERSIST, write_a_byte_cb, NULL); + event_assign(&ev3, data->base, -1, 0, activate_other_event_cb, &ev4); + event_assign(&ev4, data->base, -1, 0, activate_other_event_cb, &ev3); + event_add(ev1, NULL); + event_add(ev2, NULL); + event_active_later_(&ev3, EV_READ); + + event_base_loopexit(data->base, &qsec); + + event_base_loop(data->base, 0); + + TT_BLATHER(("%d write calls, %d read calls, %d activate-other calls.", + n_write_a_byte_cb, n_read_and_drain_cb, n_activate_other_event_cb)); + event_del(&ev3); + event_del(&ev4); + + tt_int_op(n_write_a_byte_cb, ==, n_activate_other_event_cb); + tt_int_op(n_write_a_byte_cb, >, 100); + tt_int_op(n_read_and_drain_cb, >, 100); + tt_int_op(n_activate_other_event_cb, >, 100); + + event_active_later_(&ev4, EV_READ); + event_active(&ev4, EV_READ, 1); /* This should make the event + active immediately. */ + tt_assert((ev4.ev_flags & EVLIST_ACTIVE) != 0); + tt_assert((ev4.ev_flags & EVLIST_ACTIVE_LATER) == 0); + + /* Now leave this one around, so that event_free sees it and removes + * it. */ + event_active_later_(&ev3, EV_READ); + event_base_assert_ok_(data->base); + +end: + if (ev1) + event_free(ev1); + if (ev2) + event_free(ev2); + + event_base_free(data->base); + data->base = NULL; +} + + +static void incr_arg_cb(evutil_socket_t fd, short what, void *arg) +{ + int *intptr = arg; + (void) fd; (void) what; + ++*intptr; +} +static void remove_timers_cb(evutil_socket_t fd, short what, void *arg) +{ + struct event **ep = arg; + (void) fd; (void) what; + event_remove_timer(ep[0]); + event_remove_timer(ep[1]); +} +static void send_a_byte_cb(evutil_socket_t fd, short what, void *arg) +{ + evutil_socket_t *sockp = arg; + (void) fd; (void) what; + if (write(*sockp, "A", 1) < 0) + tt_fail_perror("write"); +} +struct read_not_timeout_param +{ + struct event **ev; + int events; + int count; +}; +static void read_not_timeout_cb(evutil_socket_t fd, short what, void *arg) +{ + struct read_not_timeout_param *rntp = arg; + char c; + ev_ssize_t n; + (void) fd; (void) what; + n = read(fd, &c, 1); + tt_int_op(n, ==, 1); + rntp->events |= what; + ++rntp->count; + if(2 == rntp->count) event_del(rntp->ev[0]); +end: + ; +} + +static void +test_event_remove_timeout(void *ptr) +{ + struct basic_test_data *data = ptr; + struct event_base *base = data->base; + struct event *ev[5]; + int ev1_fired=0; + struct timeval ms25 = { 0, 25*1000 }, + ms40 = { 0, 40*1000 }, + ms75 = { 0, 75*1000 }, + ms125 = { 0, 125*1000 }; + struct read_not_timeout_param rntp = { ev, 0, 0 }; + + event_base_assert_ok_(base); + + ev[0] = event_new(base, data->pair[0], EV_READ|EV_PERSIST, + read_not_timeout_cb, &rntp); + ev[1] = evtimer_new(base, incr_arg_cb, &ev1_fired); + ev[2] = evtimer_new(base, remove_timers_cb, ev); + ev[3] = evtimer_new(base, send_a_byte_cb, &data->pair[1]); + ev[4] = evtimer_new(base, send_a_byte_cb, &data->pair[1]); + tt_assert(base); + event_add(ev[2], &ms25); /* remove timers */ + event_add(ev[4], &ms40); /* write to test if timer re-activates */ + event_add(ev[0], &ms75); /* read */ + event_add(ev[1], &ms75); /* timer */ + event_add(ev[3], &ms125); /* timeout. */ + event_base_assert_ok_(base); + + event_base_dispatch(base); + + tt_int_op(ev1_fired, ==, 0); + tt_int_op(rntp.events, ==, EV_READ); + + event_base_assert_ok_(base); +end: + event_free(ev[0]); + event_free(ev[1]); + event_free(ev[2]); + event_free(ev[3]); + event_free(ev[4]); +} + +static void +test_event_base_new(void *ptr) +{ + struct basic_test_data *data = ptr; + struct event_base *base = 0; + struct event ev1; + struct basic_cb_args args; + + int towrite = (int)strlen(TEST1)+1; + int len = write(data->pair[0], TEST1, towrite); + + if (len < 0) + tt_abort_perror("initial write"); + else if (len != towrite) + tt_abort_printf(("initial write fell short (%d of %d bytes)", + len, towrite)); + + if (shutdown(data->pair[0], EVUTIL_SHUT_WR)) + tt_abort_perror("initial write shutdown"); + + base = event_base_new(); + if (!base) + tt_abort_msg("failed to create event base"); + + args.eb = base; + args.ev = &ev1; + args.callcount = 0; + event_assign(&ev1, base, data->pair[1], + EV_READ|EV_PERSIST, basic_read_cb, &args); + + if (event_add(&ev1, NULL)) + tt_abort_perror("initial event_add"); + + if (event_base_loop(base, 0)) + tt_abort_msg("unsuccessful exit from event loop"); + +end: + if (base) + event_base_free(base); +} + +static void +test_loopexit(void) +{ + struct timeval tv, tv_start, tv_end; + struct event ev; + + setup_test("Loop exit: "); + + tv.tv_usec = 0; + tv.tv_sec = 60*60*24; + evtimer_set(&ev, timeout_cb, NULL); + evtimer_add(&ev, &tv); + + tv.tv_usec = 300*1000; + tv.tv_sec = 0; + event_loopexit(&tv); + + evutil_gettimeofday(&tv_start, NULL); + event_dispatch(); + evutil_gettimeofday(&tv_end, NULL); + + evtimer_del(&ev); + + tt_assert(event_base_got_exit(global_base)); + tt_assert(!event_base_got_break(global_base)); + + test_timeval_diff_eq(&tv_start, &tv_end, 300); + + test_ok = 1; +end: + cleanup_test(); +} + +static void +test_loopexit_multiple(void) +{ + struct timeval tv, tv_start, tv_end; + struct event_base *base; + + setup_test("Loop Multiple exit: "); + + base = event_base_new(); + + tv.tv_usec = 200*1000; + tv.tv_sec = 0; + event_base_loopexit(base, &tv); + + tv.tv_usec = 0; + tv.tv_sec = 3; + event_base_loopexit(base, &tv); + + evutil_gettimeofday(&tv_start, NULL); + event_base_dispatch(base); + evutil_gettimeofday(&tv_end, NULL); + + tt_assert(event_base_got_exit(base)); + tt_assert(!event_base_got_break(base)); + + event_base_free(base); + + test_timeval_diff_eq(&tv_start, &tv_end, 200); + + test_ok = 1; + +end: + cleanup_test(); +} + +static void +break_cb(evutil_socket_t fd, short events, void *arg) +{ + test_ok = 1; + event_loopbreak(); +} + +static void +fail_cb(evutil_socket_t fd, short events, void *arg) +{ + test_ok = 0; +} + +static void +test_loopbreak(void) +{ + struct event ev1, ev2; + struct timeval tv; + + setup_test("Loop break: "); + + tv.tv_sec = 0; + tv.tv_usec = 0; + evtimer_set(&ev1, break_cb, NULL); + evtimer_add(&ev1, &tv); + evtimer_set(&ev2, fail_cb, NULL); + evtimer_add(&ev2, &tv); + + event_dispatch(); + + tt_assert(!event_base_got_exit(global_base)); + tt_assert(event_base_got_break(global_base)); + + evtimer_del(&ev1); + evtimer_del(&ev2); + +end: + cleanup_test(); +} + +static struct event *readd_test_event_last_added = NULL; +static void +re_add_read_cb(evutil_socket_t fd, short event, void *arg) +{ + char buf[256]; + struct event *ev_other = arg; + ev_ssize_t n_read; + + readd_test_event_last_added = ev_other; + + n_read = read(fd, buf, sizeof(buf)); + + if (n_read < 0) { + tt_fail_perror("read"); + event_base_loopbreak(event_get_base(ev_other)); + } else { + event_add(ev_other, NULL); + ++test_ok; + } +} +static void +test_nonpersist_readd(void *_data) +{ + struct event ev1, ev2; + struct basic_test_data *data = _data; + + memset(&ev1, 0, sizeof(ev1)); + memset(&ev2, 0, sizeof(ev2)); + + tt_assert(!event_assign(&ev1, data->base, data->pair[0], EV_READ, re_add_read_cb, &ev2)); + tt_assert(!event_assign(&ev2, data->base, data->pair[1], EV_READ, re_add_read_cb, &ev1)); + + tt_int_op(write(data->pair[0], "Hello", 5), ==, 5); + tt_int_op(write(data->pair[1], "Hello", 5), ==, 5); + + tt_int_op(event_add(&ev1, NULL), ==, 0); + tt_int_op(event_add(&ev2, NULL), ==, 0); + tt_int_op(event_base_loop(data->base, EVLOOP_ONCE), ==, 0); + tt_int_op(test_ok, ==, 2); + + /* At this point, we executed both callbacks. Whichever one got + * called first added the second, but the second then immediately got + * deleted before its callback was called. At this point, though, it + * re-added the first. + */ + tt_assert(readd_test_event_last_added); + if (readd_test_event_last_added == &ev1) { + tt_assert(event_pending(&ev1, EV_READ, NULL) && !event_pending(&ev2, EV_READ, NULL)); + } else { + tt_assert(event_pending(&ev2, EV_READ, NULL) && !event_pending(&ev1, EV_READ, NULL)); + } + +end: + if (event_initialized(&ev1)) + event_del(&ev1); + if (event_initialized(&ev2)) + event_del(&ev2); +} + +struct test_pri_event { + struct event ev; + int count; +}; + +static void +test_priorities_cb(evutil_socket_t fd, short what, void *arg) +{ + struct test_pri_event *pri = arg; + struct timeval tv; + + if (pri->count == 3) { + event_loopexit(NULL); + return; + } + + pri->count++; + + evutil_timerclear(&tv); + event_add(&pri->ev, &tv); +} + +static void +test_priorities_impl(int npriorities) +{ + struct test_pri_event one, two; + struct timeval tv; + + TT_BLATHER(("Testing Priorities %d: ", npriorities)); + + event_base_priority_init(global_base, npriorities); + + memset(&one, 0, sizeof(one)); + memset(&two, 0, sizeof(two)); + + timeout_set(&one.ev, test_priorities_cb, &one); + if (event_priority_set(&one.ev, 0) == -1) { + fprintf(stderr, "%s: failed to set priority", __func__); + exit(1); + } + + timeout_set(&two.ev, test_priorities_cb, &two); + if (event_priority_set(&two.ev, npriorities - 1) == -1) { + fprintf(stderr, "%s: failed to set priority", __func__); + exit(1); + } + + evutil_timerclear(&tv); + + if (event_add(&one.ev, &tv) == -1) + exit(1); + if (event_add(&two.ev, &tv) == -1) + exit(1); + + event_dispatch(); + + event_del(&one.ev); + event_del(&two.ev); + + if (npriorities == 1) { + if (one.count == 3 && two.count == 3) + test_ok = 1; + } else if (npriorities == 2) { + /* Two is called once because event_loopexit is priority 1 */ + if (one.count == 3 && two.count == 1) + test_ok = 1; + } else { + if (one.count == 3 && two.count == 0) + test_ok = 1; + } +} + +static void +test_priorities(void) +{ + test_priorities_impl(1); + if (test_ok) + test_priorities_impl(2); + if (test_ok) + test_priorities_impl(3); +} + +/* priority-active-inversion: activate a higher-priority event, and make sure + * it keeps us from running a lower-priority event first. */ +static int n_pai_calls = 0; +static struct event pai_events[3]; + +static void +prio_active_inversion_cb(evutil_socket_t fd, short what, void *arg) +{ + int *call_order = arg; + *call_order = n_pai_calls++; + if (n_pai_calls == 1) { + /* This should activate later, even though it shares a + priority with us. */ + event_active(&pai_events[1], EV_READ, 1); + /* This should activate next, since its priority is higher, + even though we activated it second. */ + event_active(&pai_events[2], EV_TIMEOUT, 1); + } +} + +static void +test_priority_active_inversion(void *data_) +{ + struct basic_test_data *data = data_; + struct event_base *base = data->base; + int call_order[3]; + int i; + tt_int_op(event_base_priority_init(base, 8), ==, 0); + + n_pai_calls = 0; + memset(call_order, 0, sizeof(call_order)); + + for (i=0;i<3;++i) { + event_assign(&pai_events[i], data->base, -1, 0, + prio_active_inversion_cb, &call_order[i]); + } + + event_priority_set(&pai_events[0], 4); + event_priority_set(&pai_events[1], 4); + event_priority_set(&pai_events[2], 0); + + event_active(&pai_events[0], EV_WRITE, 1); + + event_base_dispatch(base); + tt_int_op(n_pai_calls, ==, 3); + tt_int_op(call_order[0], ==, 0); + tt_int_op(call_order[1], ==, 2); + tt_int_op(call_order[2], ==, 1); +end: + ; +} + + +static void +test_multiple_cb(evutil_socket_t fd, short event, void *arg) +{ + if (event & EV_READ) + test_ok |= 1; + else if (event & EV_WRITE) + test_ok |= 2; +} + +static void +test_multiple_events_for_same_fd(void) +{ + struct event e1, e2; + + setup_test("Multiple events for same fd: "); + + event_set(&e1, pair[0], EV_READ, test_multiple_cb, NULL); + event_add(&e1, NULL); + event_set(&e2, pair[0], EV_WRITE, test_multiple_cb, NULL); + event_add(&e2, NULL); + event_loop(EVLOOP_ONCE); + event_del(&e2); + + if (write(pair[1], TEST1, strlen(TEST1)+1) < 0) { + tt_fail_perror("write"); + } + + event_loop(EVLOOP_ONCE); + event_del(&e1); + + if (test_ok != 3) + test_ok = 0; + + cleanup_test(); +} + +int evtag_decode_int(ev_uint32_t *pnumber, struct evbuffer *evbuf); +int evtag_decode_int64(ev_uint64_t *pnumber, struct evbuffer *evbuf); +int evtag_encode_tag(struct evbuffer *evbuf, ev_uint32_t number); +int evtag_decode_tag(ev_uint32_t *pnumber, struct evbuffer *evbuf); + +static void +read_once_cb(evutil_socket_t fd, short event, void *arg) +{ + char buf[256]; + int len; + + len = read(fd, buf, sizeof(buf)); + + if (called) { + test_ok = 0; + } else if (len) { + /* Assumes global pair[0] can be used for writing */ + if (write(pair[0], TEST1, strlen(TEST1)+1) < 0) { + tt_fail_perror("write"); + test_ok = 0; + } else { + test_ok = 1; + } + } + + called++; +} + +static void +test_want_only_once(void) +{ + struct event ev; + struct timeval tv; + + /* Very simple read test */ + setup_test("Want read only once: "); + + if (write(pair[0], TEST1, strlen(TEST1)+1) < 0) { + tt_fail_perror("write"); + } + + /* Setup the loop termination */ + evutil_timerclear(&tv); + tv.tv_usec = 300*1000; + event_loopexit(&tv); + + event_set(&ev, pair[1], EV_READ, read_once_cb, &ev); + if (event_add(&ev, NULL) == -1) + exit(1); + event_dispatch(); + + cleanup_test(); +} + +#define TEST_MAX_INT 6 + +static void +evtag_int_test(void *ptr) +{ + struct evbuffer *tmp = evbuffer_new(); + ev_uint32_t integers[TEST_MAX_INT] = { + 0xaf0, 0x1000, 0x1, 0xdeadbeef, 0x00, 0xbef000 + }; + ev_uint32_t integer; + ev_uint64_t big_int; + int i; + + evtag_init(); + + for (i = 0; i < TEST_MAX_INT; i++) { + int oldlen, newlen; + oldlen = (int)EVBUFFER_LENGTH(tmp); + evtag_encode_int(tmp, integers[i]); + newlen = (int)EVBUFFER_LENGTH(tmp); + TT_BLATHER(("encoded 0x%08x with %d bytes", + (unsigned)integers[i], newlen - oldlen)); + big_int = integers[i]; + big_int *= 1000000000; /* 1 billion */ + evtag_encode_int64(tmp, big_int); + } + + for (i = 0; i < TEST_MAX_INT; i++) { + tt_int_op(evtag_decode_int(&integer, tmp), !=, -1); + tt_uint_op(integer, ==, integers[i]); + tt_int_op(evtag_decode_int64(&big_int, tmp), !=, -1); + tt_assert((big_int / 1000000000) == integers[i]); + } + + tt_uint_op(EVBUFFER_LENGTH(tmp), ==, 0); +end: + evbuffer_free(tmp); +} + +static void +evtag_fuzz(void *ptr) +{ + unsigned char buffer[4096]; + struct evbuffer *tmp = evbuffer_new(); + struct timeval tv; + int i, j; + + int not_failed = 0; + + evtag_init(); + + for (j = 0; j < 100; j++) { + for (i = 0; i < (int)sizeof(buffer); i++) + buffer[i] = test_weakrand(); + evbuffer_drain(tmp, -1); + evbuffer_add(tmp, buffer, sizeof(buffer)); + + if (evtag_unmarshal_timeval(tmp, 0, &tv) != -1) + not_failed++; + } + + /* The majority of decodes should fail */ + tt_int_op(not_failed, <, 10); + + /* Now insert some corruption into the tag length field */ + evbuffer_drain(tmp, -1); + evutil_timerclear(&tv); + tv.tv_sec = 1; + evtag_marshal_timeval(tmp, 0, &tv); + evbuffer_add(tmp, buffer, sizeof(buffer)); + + ((char *)EVBUFFER_DATA(tmp))[1] = '\xff'; + if (evtag_unmarshal_timeval(tmp, 0, &tv) != -1) { + tt_abort_msg("evtag_unmarshal_timeval should have failed"); + } + +end: + evbuffer_free(tmp); +} + +static void +evtag_tag_encoding(void *ptr) +{ + struct evbuffer *tmp = evbuffer_new(); + ev_uint32_t integers[TEST_MAX_INT] = { + 0xaf0, 0x1000, 0x1, 0xdeadbeef, 0x00, 0xbef000 + }; + ev_uint32_t integer; + int i; + + evtag_init(); + + for (i = 0; i < TEST_MAX_INT; i++) { + int oldlen, newlen; + oldlen = (int)EVBUFFER_LENGTH(tmp); + evtag_encode_tag(tmp, integers[i]); + newlen = (int)EVBUFFER_LENGTH(tmp); + TT_BLATHER(("encoded 0x%08x with %d bytes", + (unsigned)integers[i], newlen - oldlen)); + } + + for (i = 0; i < TEST_MAX_INT; i++) { + tt_int_op(evtag_decode_tag(&integer, tmp), !=, -1); + tt_uint_op(integer, ==, integers[i]); + } + + tt_uint_op(EVBUFFER_LENGTH(tmp), ==, 0); + +end: + evbuffer_free(tmp); +} + +static void +evtag_test_peek(void *ptr) +{ + struct evbuffer *tmp = evbuffer_new(); + ev_uint32_t u32; + + evtag_marshal_int(tmp, 30, 0); + evtag_marshal_string(tmp, 40, "Hello world"); + + tt_int_op(evtag_peek(tmp, &u32), ==, 1); + tt_int_op(u32, ==, 30); + tt_int_op(evtag_peek_length(tmp, &u32), ==, 0); + tt_int_op(u32, ==, 1+1+1); + tt_int_op(evtag_consume(tmp), ==, 0); + + tt_int_op(evtag_peek(tmp, &u32), ==, 1); + tt_int_op(u32, ==, 40); + tt_int_op(evtag_peek_length(tmp, &u32), ==, 0); + tt_int_op(u32, ==, 1+1+11); + tt_int_op(evtag_payload_length(tmp, &u32), ==, 0); + tt_int_op(u32, ==, 11); + +end: + evbuffer_free(tmp); +} + + +static void +test_methods(void *ptr) +{ + const char **methods = event_get_supported_methods(); + struct event_config *cfg = NULL; + struct event_base *base = NULL; + const char *backend; + int n_methods = 0; + + tt_assert(methods); + + backend = methods[0]; + while (*methods != NULL) { + TT_BLATHER(("Support method: %s", *methods)); + ++methods; + ++n_methods; + } + + cfg = event_config_new(); + assert(cfg != NULL); + + tt_int_op(event_config_avoid_method(cfg, backend), ==, 0); + event_config_set_flag(cfg, EVENT_BASE_FLAG_IGNORE_ENV); + + base = event_base_new_with_config(cfg); + if (n_methods > 1) { + tt_assert(base); + tt_str_op(backend, !=, event_base_get_method(base)); + } else { + tt_assert(base == NULL); + } + +end: + if (base) + event_base_free(base); + if (cfg) + event_config_free(cfg); +} + +static void +test_version(void *arg) +{ + const char *vstr; + ev_uint32_t vint; + int major, minor, patch, n; + + vstr = event_get_version(); + vint = event_get_version_number(); + + tt_assert(vstr); + tt_assert(vint); + + tt_str_op(vstr, ==, LIBEVENT_VERSION); + tt_int_op(vint, ==, LIBEVENT_VERSION_NUMBER); + + n = sscanf(vstr, "%d.%d.%d", &major, &minor, &patch); + tt_assert(3 == n); + tt_int_op((vint&0xffffff00), ==, ((major<<24)|(minor<<16)|(patch<<8))); +end: + ; +} + +static void +test_base_features(void *arg) +{ + struct event_base *base = NULL; + struct event_config *cfg = NULL; + + cfg = event_config_new(); + + tt_assert(0 == event_config_require_features(cfg, EV_FEATURE_ET)); + + base = event_base_new_with_config(cfg); + if (base) { + tt_int_op(EV_FEATURE_ET, ==, + event_base_get_features(base) & EV_FEATURE_ET); + } else { + base = event_base_new(); + tt_int_op(0, ==, event_base_get_features(base) & EV_FEATURE_ET); + } + +end: + if (base) + event_base_free(base); + if (cfg) + event_config_free(cfg); +} + +#ifdef EVENT__HAVE_SETENV +#define SETENV_OK +#elif !defined(EVENT__HAVE_SETENV) && defined(EVENT__HAVE_PUTENV) +static void setenv(const char *k, const char *v, int o_) +{ + char b[256]; + evutil_snprintf(b, sizeof(b), "%s=%s",k,v); + putenv(b); +} +#define SETENV_OK +#endif + +#ifdef EVENT__HAVE_UNSETENV +#define UNSETENV_OK +#elif !defined(EVENT__HAVE_UNSETENV) && defined(EVENT__HAVE_PUTENV) +static void unsetenv(const char *k) +{ + char b[256]; + evutil_snprintf(b, sizeof(b), "%s=",k); + putenv(b); +} +#define UNSETENV_OK +#endif + +#if defined(SETENV_OK) && defined(UNSETENV_OK) +static void +methodname_to_envvar(const char *mname, char *buf, size_t buflen) +{ + char *cp; + evutil_snprintf(buf, buflen, "EVENT_NO%s", mname); + for (cp = buf; *cp; ++cp) { + *cp = EVUTIL_TOUPPER_(*cp); + } +} +#endif + +static void +test_base_environ(void *arg) +{ + struct event_base *base = NULL; + struct event_config *cfg = NULL; + +#if defined(SETENV_OK) && defined(UNSETENV_OK) + const char **basenames; + int i, n_methods=0; + char varbuf[128]; + const char *defaultname, *ignoreenvname; + + /* See if unsetenv works before we rely on it. */ + setenv("EVENT_NOWAFFLES", "1", 1); + unsetenv("EVENT_NOWAFFLES"); + if (getenv("EVENT_NOWAFFLES") != NULL) { +#ifndef EVENT__HAVE_UNSETENV + TT_DECLARE("NOTE", ("Can't fake unsetenv; skipping test")); +#else + TT_DECLARE("NOTE", ("unsetenv doesn't work; skipping test")); +#endif + tt_skip(); + } + + basenames = event_get_supported_methods(); + for (i = 0; basenames[i]; ++i) { + methodname_to_envvar(basenames[i], varbuf, sizeof(varbuf)); + unsetenv(varbuf); + ++n_methods; + } + + base = event_base_new(); + tt_assert(base); + + defaultname = event_base_get_method(base); + TT_BLATHER(("default is <%s>", defaultname)); + event_base_free(base); + base = NULL; + + /* Can we disable the method with EVENT_NOfoo ? */ + if (!strcmp(defaultname, "epoll (with changelist)")) { + setenv("EVENT_NOEPOLL", "1", 1); + ignoreenvname = "epoll"; + } else { + methodname_to_envvar(defaultname, varbuf, sizeof(varbuf)); + setenv(varbuf, "1", 1); + ignoreenvname = defaultname; + } + + /* Use an empty cfg rather than NULL so a failure doesn't exit() */ + cfg = event_config_new(); + base = event_base_new_with_config(cfg); + event_config_free(cfg); + cfg = NULL; + if (n_methods == 1) { + tt_assert(!base); + } else { + tt_assert(base); + tt_str_op(defaultname, !=, event_base_get_method(base)); + event_base_free(base); + base = NULL; + } + + /* Can we disable looking at the environment with IGNORE_ENV ? */ + cfg = event_config_new(); + event_config_set_flag(cfg, EVENT_BASE_FLAG_IGNORE_ENV); + base = event_base_new_with_config(cfg); + tt_assert(base); + tt_str_op(ignoreenvname, ==, event_base_get_method(base)); +#else + tt_skip(); +#endif + +end: + if (base) + event_base_free(base); + if (cfg) + event_config_free(cfg); +} + +static void +read_called_once_cb(evutil_socket_t fd, short event, void *arg) +{ + tt_int_op(event, ==, EV_READ); + called += 1; +end: + ; +} + +static void +timeout_called_once_cb(evutil_socket_t fd, short event, void *arg) +{ + tt_int_op(event, ==, EV_TIMEOUT); + called += 100; +end: + ; +} + +static void +immediate_called_twice_cb(evutil_socket_t fd, short event, void *arg) +{ + tt_int_op(event, ==, EV_TIMEOUT); + called += 1000; +end: + ; +} + +static void +test_event_once(void *ptr) +{ + struct basic_test_data *data = ptr; + struct timeval tv; + int r; + + tv.tv_sec = 0; + tv.tv_usec = 50*1000; + called = 0; + r = event_base_once(data->base, data->pair[0], EV_READ, + read_called_once_cb, NULL, NULL); + tt_int_op(r, ==, 0); + r = event_base_once(data->base, -1, EV_TIMEOUT, + timeout_called_once_cb, NULL, &tv); + tt_int_op(r, ==, 0); + r = event_base_once(data->base, -1, 0, NULL, NULL, NULL); + tt_int_op(r, <, 0); + r = event_base_once(data->base, -1, EV_TIMEOUT, + immediate_called_twice_cb, NULL, NULL); + tt_int_op(r, ==, 0); + tv.tv_sec = 0; + tv.tv_usec = 0; + r = event_base_once(data->base, -1, EV_TIMEOUT, + immediate_called_twice_cb, NULL, &tv); + tt_int_op(r, ==, 0); + + if (write(data->pair[1], TEST1, strlen(TEST1)+1) < 0) { + tt_fail_perror("write"); + } + + shutdown(data->pair[1], EVUTIL_SHUT_WR); + + event_base_dispatch(data->base); + + tt_int_op(called, ==, 2101); +end: + ; +} + +static void +test_event_once_never(void *ptr) +{ + struct basic_test_data *data = ptr; + struct timeval tv; + + /* Have one trigger in 10 seconds (don't worry, because) */ + tv.tv_sec = 10; + tv.tv_usec = 0; + called = 0; + event_base_once(data->base, -1, EV_TIMEOUT, + timeout_called_once_cb, NULL, &tv); + + /* But shut down the base in 75 msec. */ + tv.tv_sec = 0; + tv.tv_usec = 75*1000; + event_base_loopexit(data->base, &tv); + + event_base_dispatch(data->base); + + tt_int_op(called, ==, 0); +end: + ; +} + +static void +test_event_pending(void *ptr) +{ + struct basic_test_data *data = ptr; + struct event *r=NULL, *w=NULL, *t=NULL; + struct timeval tv, now, tv2; + + tv.tv_sec = 0; + tv.tv_usec = 500 * 1000; + r = event_new(data->base, data->pair[0], EV_READ, simple_read_cb, + NULL); + w = event_new(data->base, data->pair[1], EV_WRITE, simple_write_cb, + NULL); + t = evtimer_new(data->base, timeout_cb, NULL); + + tt_assert(r); + tt_assert(w); + tt_assert(t); + + evutil_gettimeofday(&now, NULL); + event_add(r, NULL); + event_add(t, &tv); + + tt_assert( event_pending(r, EV_READ, NULL)); + tt_assert(!event_pending(w, EV_WRITE, NULL)); + tt_assert(!event_pending(r, EV_WRITE, NULL)); + tt_assert( event_pending(r, EV_READ|EV_WRITE, NULL)); + tt_assert(!event_pending(r, EV_TIMEOUT, NULL)); + tt_assert( event_pending(t, EV_TIMEOUT, NULL)); + tt_assert( event_pending(t, EV_TIMEOUT, &tv2)); + + tt_assert(evutil_timercmp(&tv2, &now, >)); + + test_timeval_diff_eq(&now, &tv2, 500); + +end: + if (r) { + event_del(r); + event_free(r); + } + if (w) { + event_del(w); + event_free(w); + } + if (t) { + event_del(t); + event_free(t); + } +} + +static void +dfd_cb(evutil_socket_t fd, short e, void *data) +{ + *(int*)data = (int)e; +} + +static void +test_event_closed_fd_poll(void *arg) +{ + struct timeval tv; + struct event *e; + struct basic_test_data *data = (struct basic_test_data *)arg; + int i = 0; + + if (strcmp(event_base_get_method(data->base), "poll")) { + tinytest_set_test_skipped_(); + return; + } + + e = event_new(data->base, data->pair[0], EV_READ, dfd_cb, &i); + tt_assert(e); + + tv.tv_sec = 0; + tv.tv_usec = 500 * 1000; + event_add(e, &tv); + tt_assert(event_pending(e, EV_READ, NULL)); + close(data->pair[0]); + data->pair[0] = -1; /** avoids double-close */ + event_base_loop(data->base, EVLOOP_ONCE); + tt_int_op(i, ==, EV_READ); + +end: + if (e) { + event_del(e); + event_free(e); + } +} + +#ifndef _WIN32 +/* You can't do this test on windows, since dup2 doesn't work on sockets */ + +/* Regression test for our workaround for a fun epoll/linux related bug + * where fd2 = dup(fd1); add(fd2); close(fd2); dup2(fd1,fd2); add(fd2) + * will get you an EEXIST */ +static void +test_dup_fd(void *arg) +{ + struct basic_test_data *data = arg; + struct event_base *base = data->base; + struct event *ev1=NULL, *ev2=NULL; + int fd, dfd=-1; + int ev1_got, ev2_got; + + tt_int_op(write(data->pair[0], "Hello world", + strlen("Hello world")), >, 0); + fd = data->pair[1]; + + dfd = dup(fd); + tt_int_op(dfd, >=, 0); + + ev1 = event_new(base, fd, EV_READ|EV_PERSIST, dfd_cb, &ev1_got); + ev2 = event_new(base, dfd, EV_READ|EV_PERSIST, dfd_cb, &ev2_got); + ev1_got = ev2_got = 0; + event_add(ev1, NULL); + event_add(ev2, NULL); + event_base_loop(base, EVLOOP_ONCE); + tt_int_op(ev1_got, ==, EV_READ); + tt_int_op(ev2_got, ==, EV_READ); + + /* Now close and delete dfd then dispatch. We need to do the + * dispatch here so that when we add it later, we think there + * was an intermediate delete. */ + close(dfd); + event_del(ev2); + ev1_got = ev2_got = 0; + event_base_loop(base, EVLOOP_ONCE); + tt_want_int_op(ev1_got, ==, EV_READ); + tt_int_op(ev2_got, ==, 0); + + /* Re-duplicate the fd. We need to get the same duplicated + * value that we closed to provoke the epoll quirk. Also, we + * need to change the events to write, or else the old lingering + * read event will make the test pass whether the change was + * successful or not. */ + tt_int_op(dup2(fd, dfd), ==, dfd); + event_free(ev2); + ev2 = event_new(base, dfd, EV_WRITE|EV_PERSIST, dfd_cb, &ev2_got); + event_add(ev2, NULL); + ev1_got = ev2_got = 0; + event_base_loop(base, EVLOOP_ONCE); + tt_want_int_op(ev1_got, ==, EV_READ); + tt_int_op(ev2_got, ==, EV_WRITE); + +end: + if (ev1) + event_free(ev1); + if (ev2) + event_free(ev2); + if (dfd >= 0) + close(dfd); +} +#endif + +#ifdef EVENT__DISABLE_MM_REPLACEMENT +static void +test_mm_functions(void *arg) +{ + tinytest_set_test_skipped_(); +} +#else +static int +check_dummy_mem_ok(void *mem_) +{ + char *mem = mem_; + mem -= 16; + return !memcmp(mem, "{[]}", 16); +} + +static void * +dummy_malloc(size_t len) +{ + char *mem = malloc(len+16); + memcpy(mem, "{[]}", 16); + return mem+16; +} + +static void * +dummy_realloc(void *mem_, size_t len) +{ + char *mem = mem_; + if (!mem) + return dummy_malloc(len); + tt_want(check_dummy_mem_ok(mem_)); + mem -= 16; + mem = realloc(mem, len+16); + return mem+16; +} + +static void +dummy_free(void *mem_) +{ + char *mem = mem_; + tt_want(check_dummy_mem_ok(mem_)); + mem -= 16; + free(mem); +} + +static void +test_mm_functions(void *arg) +{ + struct event_base *b = NULL; + struct event_config *cfg = NULL; + event_set_mem_functions(dummy_malloc, dummy_realloc, dummy_free); + cfg = event_config_new(); + event_config_avoid_method(cfg, "Nonesuch"); + b = event_base_new_with_config(cfg); + tt_assert(b); + tt_assert(check_dummy_mem_ok(b)); +end: + if (cfg) + event_config_free(cfg); + if (b) + event_base_free(b); +} +#endif + +static void +many_event_cb(evutil_socket_t fd, short event, void *arg) +{ + int *calledp = arg; + *calledp += 1; +} + +static void +test_many_events(void *arg) +{ + /* Try 70 events that should all be ready at once. This will + * exercise the "resize" code on most of the backends, and will make + * sure that we can get past the 64-handle limit of some windows + * functions. */ +#define MANY 70 + + struct basic_test_data *data = arg; + struct event_base *base = data->base; + int one_at_a_time = data->setup_data != NULL; + evutil_socket_t sock[MANY]; + struct event *ev[MANY]; + int called[MANY]; + int i; + int loopflags = EVLOOP_NONBLOCK, evflags=0; + if (one_at_a_time) { + loopflags |= EVLOOP_ONCE; + evflags = EV_PERSIST; + } + + memset(sock, 0xff, sizeof(sock)); + memset(ev, 0, sizeof(ev)); + memset(called, 0, sizeof(called)); + + for (i = 0; i < MANY; ++i) { + /* We need an event that will hit the backend, and that will + * be ready immediately. "Send a datagram" is an easy + * instance of that. */ + sock[i] = socket(AF_INET, SOCK_DGRAM, 0); + tt_assert(sock[i] >= 0); + tt_assert(!evutil_make_socket_nonblocking(sock[i])); + called[i] = 0; + ev[i] = event_new(base, sock[i], EV_WRITE|evflags, + many_event_cb, &called[i]); + event_add(ev[i], NULL); + if (one_at_a_time) + event_base_loop(base, EVLOOP_NONBLOCK|EVLOOP_ONCE); + } + + event_base_loop(base, loopflags); + + for (i = 0; i < MANY; ++i) { + if (one_at_a_time) + tt_int_op(called[i], ==, MANY - i + 1); + else + tt_int_op(called[i], ==, 1); + } + +end: + for (i = 0; i < MANY; ++i) { + if (ev[i]) + event_free(ev[i]); + if (sock[i] >= 0) + evutil_closesocket(sock[i]); + } +#undef MANY +} + +static void +test_struct_event_size(void *arg) +{ + tt_int_op(event_get_struct_event_size(), <=, sizeof(struct event)); +end: + ; +} + +static void +test_get_assignment(void *arg) +{ + struct basic_test_data *data = arg; + struct event_base *base = data->base; + struct event *ev1 = NULL; + const char *str = "foo"; + + struct event_base *b; + evutil_socket_t s; + short what; + event_callback_fn cb; + void *cb_arg; + + ev1 = event_new(base, data->pair[1], EV_READ, dummy_read_cb, (void*)str); + event_get_assignment(ev1, &b, &s, &what, &cb, &cb_arg); + + tt_ptr_op(b, ==, base); + tt_fd_op(s, ==, data->pair[1]); + tt_int_op(what, ==, EV_READ); + tt_ptr_op(cb, ==, dummy_read_cb); + tt_ptr_op(cb_arg, ==, str); + + /* Now make sure this doesn't crash. */ + event_get_assignment(ev1, NULL, NULL, NULL, NULL, NULL); + +end: + if (ev1) + event_free(ev1); +} + +struct foreach_helper { + int count; + const struct event *ev; +}; + +static int +foreach_count_cb(const struct event_base *base, const struct event *ev, void *arg) +{ + struct foreach_helper *h = event_get_callback_arg(ev); + struct timeval *tv = arg; + if (event_get_callback(ev) != timeout_cb) + return 0; + tt_ptr_op(event_get_base(ev), ==, base); + tt_int_op(tv->tv_sec, ==, 10); + h->ev = ev; + h->count++; + return 0; +end: + return -1; +} + +static int +foreach_find_cb(const struct event_base *base, const struct event *ev, void *arg) +{ + const struct event **ev_out = arg; + struct foreach_helper *h = event_get_callback_arg(ev); + if (event_get_callback(ev) != timeout_cb) + return 0; + if (h->count == 99) { + *ev_out = ev; + return 101; + } + return 0; +} + +static void +test_event_foreach(void *arg) +{ + struct basic_test_data *data = arg; + struct event_base *base = data->base; + struct event *ev[5]; + struct foreach_helper visited[5]; + int i; + struct timeval ten_sec = {10,0}; + const struct event *ev_found = NULL; + + for (i = 0; i < 5; ++i) { + visited[i].count = 0; + visited[i].ev = NULL; + ev[i] = event_new(base, -1, 0, timeout_cb, &visited[i]); + } + + tt_int_op(-1, ==, event_base_foreach_event(NULL, foreach_count_cb, NULL)); + tt_int_op(-1, ==, event_base_foreach_event(base, NULL, NULL)); + + event_add(ev[0], &ten_sec); + event_add(ev[1], &ten_sec); + event_active(ev[1], EV_TIMEOUT, 1); + event_active(ev[2], EV_TIMEOUT, 1); + event_add(ev[3], &ten_sec); + /* Don't touch ev[4]. */ + + tt_int_op(0, ==, event_base_foreach_event(base, foreach_count_cb, + &ten_sec)); + tt_int_op(1, ==, visited[0].count); + tt_int_op(1, ==, visited[1].count); + tt_int_op(1, ==, visited[2].count); + tt_int_op(1, ==, visited[3].count); + tt_ptr_op(ev[0], ==, visited[0].ev); + tt_ptr_op(ev[1], ==, visited[1].ev); + tt_ptr_op(ev[2], ==, visited[2].ev); + tt_ptr_op(ev[3], ==, visited[3].ev); + + visited[2].count = 99; + tt_int_op(101, ==, event_base_foreach_event(base, foreach_find_cb, + &ev_found)); + tt_ptr_op(ev_found, ==, ev[2]); + +end: + for (i=0; i<5; ++i) { + event_free(ev[i]); + } +} + +static struct event_base *cached_time_base = NULL; +static int cached_time_reset = 0; +static int cached_time_sleep = 0; +static void +cache_time_cb(evutil_socket_t fd, short what, void *arg) +{ + struct timeval *tv = arg; + tt_int_op(0, ==, event_base_gettimeofday_cached(cached_time_base, tv)); + if (cached_time_sleep) { + struct timeval delay = { 0, 30*1000 }; + evutil_usleep_(&delay); + } + if (cached_time_reset) { + event_base_update_cache_time(cached_time_base); + } +end: + ; +} + +static void +test_gettimeofday_cached(void *arg) +{ + struct basic_test_data *data = arg; + struct event_config *cfg = NULL; + struct event_base *base = NULL; + struct timeval tv1, tv2, tv3, now; + struct event *ev1=NULL, *ev2=NULL, *ev3=NULL; + int cached_time_disable = strstr(data->setup_data, "disable") != NULL; + + cfg = event_config_new(); + if (cached_time_disable) { + event_config_set_flag(cfg, EVENT_BASE_FLAG_NO_CACHE_TIME); + } + cached_time_base = base = event_base_new_with_config(cfg); + tt_assert(base); + + /* Try gettimeofday_cached outside of an event loop. */ + evutil_gettimeofday(&now, NULL); + tt_int_op(0, ==, event_base_gettimeofday_cached(NULL, &tv1)); + tt_int_op(0, ==, event_base_gettimeofday_cached(base, &tv2)); + tt_int_op(timeval_msec_diff(&tv1, &tv2), <, 10); + tt_int_op(timeval_msec_diff(&tv1, &now), <, 10); + + cached_time_reset = strstr(data->setup_data, "reset") != NULL; + cached_time_sleep = strstr(data->setup_data, "sleep") != NULL; + + ev1 = event_new(base, -1, 0, cache_time_cb, &tv1); + ev2 = event_new(base, -1, 0, cache_time_cb, &tv2); + ev3 = event_new(base, -1, 0, cache_time_cb, &tv3); + + event_active(ev1, EV_TIMEOUT, 1); + event_active(ev2, EV_TIMEOUT, 1); + event_active(ev3, EV_TIMEOUT, 1); + + event_base_dispatch(base); + + if (cached_time_reset && cached_time_sleep) { + tt_int_op(labs(timeval_msec_diff(&tv1,&tv2)), >, 10); + tt_int_op(labs(timeval_msec_diff(&tv2,&tv3)), >, 10); + } else if (cached_time_disable && cached_time_sleep) { + tt_int_op(labs(timeval_msec_diff(&tv1,&tv2)), >, 10); + tt_int_op(labs(timeval_msec_diff(&tv2,&tv3)), >, 10); + } else if (! cached_time_disable) { + tt_assert(evutil_timercmp(&tv1, &tv2, ==)); + tt_assert(evutil_timercmp(&tv2, &tv3, ==)); + } + +end: + if (ev1) + event_free(ev1); + if (ev2) + event_free(ev2); + if (ev3) + event_free(ev3); + if (base) + event_base_free(base); + if (cfg) + event_config_free(cfg); +} + +static void +tabf_cb(evutil_socket_t fd, short what, void *arg) +{ + int *ptr = arg; + *ptr = what; + *ptr += 0x10000; +} + +static void +test_evmap_invalid_slots(void *arg) +{ + struct basic_test_data *data = arg; + struct event_base *base = data->base; + struct event *ev1 = NULL, *ev2 = NULL; + int e1, e2; +#ifndef _WIN32 + struct event *ev3 = NULL, *ev4 = NULL; + int e3, e4; +#endif + + ev1 = evsignal_new(base, -1, dummy_read_cb, (void *)base); + ev2 = evsignal_new(base, NSIG, dummy_read_cb, (void *)base); + tt_assert(ev1); + tt_assert(ev2); + e1 = event_add(ev1, NULL); + e2 = event_add(ev2, NULL); + tt_int_op(e1, !=, 0); + tt_int_op(e2, !=, 0); +#ifndef _WIN32 + ev3 = event_new(base, INT_MAX, EV_READ, dummy_read_cb, (void *)base); + ev4 = event_new(base, INT_MAX / 2, EV_READ, dummy_read_cb, (void *)base); + tt_assert(ev3); + tt_assert(ev4); + e3 = event_add(ev3, NULL); + e4 = event_add(ev4, NULL); + tt_int_op(e3, !=, 0); + tt_int_op(e4, !=, 0); +#endif + +end: + event_free(ev1); + event_free(ev2); +#ifndef _WIN32 + event_free(ev3); + event_free(ev4); +#endif +} + +static void +test_active_by_fd(void *arg) +{ + struct basic_test_data *data = arg; + struct event_base *base = data->base; + struct event *ev1 = NULL, *ev2 = NULL, *ev3 = NULL, *ev4 = NULL; + int e1,e2,e3,e4; +#ifndef _WIN32 + struct event *evsig = NULL; + int es; +#endif + struct timeval tenmin = { 600, 0 }; + + /* Ensure no crash on nonexistent FD. */ + event_base_active_by_fd(base, 1000, EV_READ); + + /* Ensure no crash on bogus FD. */ + event_base_active_by_fd(base, -1, EV_READ); + + /* Ensure no crash on nonexistent/bogus signal. */ + event_base_active_by_signal(base, 1000); + event_base_active_by_signal(base, -1); + + event_base_assert_ok_(base); + + e1 = e2 = e3 = e4 = 0; + ev1 = event_new(base, data->pair[0], EV_READ, tabf_cb, &e1); + ev2 = event_new(base, data->pair[0], EV_WRITE, tabf_cb, &e2); + ev3 = event_new(base, data->pair[1], EV_READ, tabf_cb, &e3); + ev4 = event_new(base, data->pair[1], EV_READ, tabf_cb, &e4); + tt_assert(ev1); + tt_assert(ev2); + tt_assert(ev3); + tt_assert(ev4); +#ifndef _WIN32 + evsig = event_new(base, SIGHUP, EV_SIGNAL, tabf_cb, &es); + tt_assert(evsig); + event_add(evsig, &tenmin); +#endif + + event_add(ev1, &tenmin); + event_add(ev2, NULL); + event_add(ev3, NULL); + event_add(ev4, &tenmin); + + + event_base_assert_ok_(base); + + /* Trigger 2, 3, 4 */ + event_base_active_by_fd(base, data->pair[0], EV_WRITE); + event_base_active_by_fd(base, data->pair[1], EV_READ); + event_base_active_by_fd(base, data->pair[1], EV_TIMEOUT); +#ifndef _WIN32 + event_base_active_by_signal(base, SIGHUP); +#endif + + event_base_assert_ok_(base); + + event_base_loop(base, EVLOOP_ONCE); + + tt_int_op(e1, ==, 0); + tt_int_op(e2, ==, EV_WRITE | 0x10000); + tt_int_op(e3, ==, EV_READ | 0x10000); + /* Mask out EV_WRITE here, since it could be genuinely writeable. */ + tt_int_op((e4 & ~EV_WRITE), ==, EV_READ | EV_TIMEOUT | 0x10000); +#ifndef _WIN32 + tt_int_op(es, ==, EV_SIGNAL | 0x10000); +#endif + +end: + if (ev1) + event_free(ev1); + if (ev2) + event_free(ev2); + if (ev3) + event_free(ev3); + if (ev4) + event_free(ev4); +#ifndef _WIN32 + if (evsig) + event_free(evsig); +#endif +} + +struct testcase_t main_testcases[] = { + /* Some converted-over tests */ + { "methods", test_methods, TT_FORK, NULL, NULL }, + { "version", test_version, 0, NULL, NULL }, + BASIC(base_features, TT_FORK|TT_NO_LOGS), + { "base_environ", test_base_environ, TT_FORK, NULL, NULL }, + + BASIC(event_base_new, TT_FORK|TT_NEED_SOCKETPAIR), + BASIC(free_active_base, TT_FORK|TT_NEED_SOCKETPAIR), + + BASIC(manipulate_active_events, TT_FORK|TT_NEED_BASE), + BASIC(event_new_selfarg, TT_FORK|TT_NEED_BASE), + BASIC(event_assign_selfarg, TT_FORK|TT_NEED_BASE), + BASIC(event_base_get_num_events, TT_FORK|TT_NEED_BASE), + BASIC(event_base_get_max_events, TT_FORK|TT_NEED_BASE), + BASIC(evmap_invalid_slots, TT_FORK|TT_NEED_BASE), + + BASIC(bad_assign, TT_FORK|TT_NEED_BASE|TT_NO_LOGS), + BASIC(bad_reentrant, TT_FORK|TT_NEED_BASE|TT_NO_LOGS), + BASIC(active_later, TT_FORK|TT_NEED_BASE|TT_NEED_SOCKETPAIR|TT_RETRIABLE), + BASIC(event_remove_timeout, TT_FORK|TT_NEED_BASE|TT_NEED_SOCKETPAIR), + + /* These are still using the old API */ + LEGACY(persistent_timeout, TT_FORK|TT_NEED_BASE), + { "persistent_timeout_jump", test_persistent_timeout_jump, TT_FORK|TT_NEED_BASE, &basic_setup, NULL }, + { "persistent_active_timeout", test_persistent_active_timeout, + TT_FORK|TT_NEED_BASE|TT_RETRIABLE, &basic_setup, NULL }, + LEGACY(priorities, TT_FORK|TT_NEED_BASE), + BASIC(priority_active_inversion, TT_FORK|TT_NEED_BASE), + { "common_timeout", test_common_timeout, TT_FORK|TT_NEED_BASE, + &basic_setup, NULL }, + + /* These legacy tests may not all need all of these flags. */ + LEGACY(simpleread, TT_ISOLATED), + LEGACY(simpleread_multiple, TT_ISOLATED), + LEGACY(simplewrite, TT_ISOLATED), + { "simpleclose_rw", test_simpleclose_rw, TT_FORK, &basic_setup, NULL }, + /* simpleclose */ + { "simpleclose_close", test_simpleclose, + TT_FORK|TT_NEED_SOCKETPAIR|TT_NEED_BASE, + &basic_setup, (void *)"close" }, + { "simpleclose_shutdown", test_simpleclose, + TT_FORK|TT_NEED_SOCKETPAIR|TT_NEED_BASE, + &basic_setup, (void *)"shutdown" }, + /* simpleclose_*_persist */ + { "simpleclose_close_persist", test_simpleclose, + TT_FORK|TT_NEED_SOCKETPAIR|TT_NEED_BASE, + &basic_setup, (void *)"close_persist" }, + { "simpleclose_shutdown_persist", test_simpleclose, + TT_FORK|TT_NEED_SOCKETPAIR|TT_NEED_BASE, + &basic_setup, (void *)"shutdown_persist" }, + /* simpleclose_*_et */ + { "simpleclose_close_et", test_simpleclose, + TT_FORK|TT_NEED_SOCKETPAIR|TT_NEED_BASE, + &basic_setup, (void *)"close_ET" }, + { "simpleclose_shutdown_et", test_simpleclose, + TT_FORK|TT_NEED_SOCKETPAIR|TT_NEED_BASE, + &basic_setup, (void *)"shutdown_ET" }, + /* simpleclose_*_persist_et */ + { "simpleclose_close_persist_et", test_simpleclose, + TT_FORK|TT_NEED_SOCKETPAIR|TT_NEED_BASE, + &basic_setup, (void *)"close_persist_ET" }, + { "simpleclose_shutdown_persist_et", test_simpleclose, + TT_FORK|TT_NEED_SOCKETPAIR|TT_NEED_BASE, + &basic_setup, (void *)"shutdown_persist_ET" }, + LEGACY(multiple, TT_ISOLATED), + LEGACY(persistent, TT_ISOLATED), + LEGACY(combined, TT_ISOLATED), + LEGACY(simpletimeout, TT_ISOLATED), + LEGACY(loopbreak, TT_ISOLATED), + LEGACY(loopexit, TT_ISOLATED), + LEGACY(loopexit_multiple, TT_ISOLATED), + { "nonpersist_readd", test_nonpersist_readd, TT_FORK|TT_NEED_SOCKETPAIR|TT_NEED_BASE, &basic_setup, NULL }, + LEGACY(multiple_events_for_same_fd, TT_ISOLATED), + LEGACY(want_only_once, TT_ISOLATED), + { "event_once", test_event_once, TT_ISOLATED, &basic_setup, NULL }, + { "event_once_never", test_event_once_never, TT_ISOLATED, &basic_setup, NULL }, + { "event_pending", test_event_pending, TT_ISOLATED, &basic_setup, + NULL }, + { "event_closed_fd_poll", test_event_closed_fd_poll, TT_ISOLATED, &basic_setup, + NULL }, + +#ifndef _WIN32 + { "dup_fd", test_dup_fd, TT_ISOLATED, &basic_setup, NULL }, +#endif + { "mm_functions", test_mm_functions, TT_FORK, NULL, NULL }, + { "many_events", test_many_events, TT_ISOLATED, &basic_setup, NULL }, + { "many_events_slow_add", test_many_events, TT_ISOLATED, &basic_setup, (void*)1 }, + + { "struct_event_size", test_struct_event_size, 0, NULL, NULL }, + BASIC(get_assignment, TT_FORK|TT_NEED_BASE|TT_NEED_SOCKETPAIR), + + BASIC(event_foreach, TT_FORK|TT_NEED_BASE), + { "gettimeofday_cached", test_gettimeofday_cached, TT_FORK, &basic_setup, (void*)"" }, + { "gettimeofday_cached_sleep", test_gettimeofday_cached, TT_FORK, &basic_setup, (void*)"sleep" }, + { "gettimeofday_cached_reset", test_gettimeofday_cached, TT_FORK, &basic_setup, (void*)"sleep reset" }, + { "gettimeofday_cached_disabled", test_gettimeofday_cached, TT_FORK, &basic_setup, (void*)"sleep disable" }, + { "gettimeofday_cached_disabled_nosleep", test_gettimeofday_cached, TT_FORK, &basic_setup, (void*)"disable" }, + + BASIC(active_by_fd, TT_FORK|TT_NEED_BASE|TT_NEED_SOCKETPAIR), + +#ifndef _WIN32 + LEGACY(fork, TT_ISOLATED), +#endif + +#ifdef EVTHREAD_USE_PTHREADS_IMPLEMENTED + LEGACY(del_wait, TT_ISOLATED|TT_NEED_THREADS|TT_RETRIABLE), + LEGACY(del_notify, TT_ISOLATED|TT_NEED_THREADS), +#endif + + END_OF_TESTCASES +}; + +struct testcase_t evtag_testcases[] = { + { "int", evtag_int_test, TT_FORK, NULL, NULL }, + { "fuzz", evtag_fuzz, TT_FORK, NULL, NULL }, + { "encoding", evtag_tag_encoding, TT_FORK, NULL, NULL }, + { "peek", evtag_test_peek, 0, NULL, NULL }, + + END_OF_TESTCASES +}; + +struct testcase_t signal_testcases[] = { +#ifndef _WIN32 + LEGACY(simplestsignal, TT_ISOLATED), + LEGACY(simplesignal, TT_ISOLATED), + LEGACY(multiplesignal, TT_ISOLATED), + LEGACY(immediatesignal, TT_ISOLATED), + LEGACY(signal_dealloc, TT_ISOLATED), + LEGACY(signal_pipeloss, TT_ISOLATED), + LEGACY(signal_switchbase, TT_ISOLATED|TT_NO_LOGS), + LEGACY(signal_restore, TT_ISOLATED), + LEGACY(signal_assert, TT_ISOLATED), + LEGACY(signal_while_processing, TT_ISOLATED), +#endif + END_OF_TESTCASES +}; + diff --git a/ipc/chromium/src/third_party/libevent/test/regress.h b/ipc/chromium/src/third_party/libevent/test/regress.h new file mode 100644 index 0000000000..43cb4eaf1e --- /dev/null +++ b/ipc/chromium/src/third_party/libevent/test/regress.h @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2000-2007 Niels Provos + * Copyright (c) 2007-2012 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef REGRESS_H_INCLUDED_ +#define REGRESS_H_INCLUDED_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "tinytest.h" +#include "tinytest_macros.h" + +extern struct testcase_t main_testcases[]; +extern struct testcase_t evtag_testcases[]; +extern struct testcase_t evbuffer_testcases[]; +extern struct testcase_t finalize_testcases[]; +extern struct testcase_t bufferevent_testcases[]; +extern struct testcase_t bufferevent_iocp_testcases[]; +extern struct testcase_t util_testcases[]; +extern struct testcase_t signal_testcases[]; +extern struct testcase_t http_testcases[]; +extern struct testcase_t http_iocp_testcases[]; +extern struct testcase_t dns_testcases[]; +extern struct testcase_t rpc_testcases[]; +extern struct testcase_t edgetriggered_testcases[]; +extern struct testcase_t minheap_testcases[]; +extern struct testcase_t iocp_testcases[]; +extern struct testcase_t ssl_testcases[]; +extern struct testcase_t listener_testcases[]; +extern struct testcase_t listener_iocp_testcases[]; +extern struct testcase_t thread_testcases[]; + +extern struct evutil_weakrand_state test_weakrand_state; + +#define test_weakrand() (evutil_weakrand_(&test_weakrand_state)) + +void regress_threads(void *); +void test_bufferevent_zlib(void *); + +/* Helpers to wrap old testcases */ +extern evutil_socket_t pair[2]; +extern int test_ok; +extern int called; +extern struct event_base *global_base; +extern int in_legacy_test_wrapper; + +int regress_make_tmpfile(const void *data, size_t datalen, char **filename_out); + +struct basic_test_data { + struct event_base *base; + evutil_socket_t pair[2]; + + void (*legacy_test_fn)(void); + + void *setup_data; +}; +extern const struct testcase_setup_t basic_setup; + + +extern const struct testcase_setup_t legacy_setup; +void run_legacy_test_fn(void *ptr); + +extern int libevent_tests_running_in_debug_mode; + +/* A couple of flags that basic/legacy_setup can support. */ +#define TT_NEED_SOCKETPAIR TT_FIRST_USER_FLAG +#define TT_NEED_BASE (TT_FIRST_USER_FLAG<<1) +#define TT_NEED_DNS (TT_FIRST_USER_FLAG<<2) +#define TT_LEGACY (TT_FIRST_USER_FLAG<<3) +#define TT_NEED_THREADS (TT_FIRST_USER_FLAG<<4) +#define TT_NO_LOGS (TT_FIRST_USER_FLAG<<5) +#define TT_ENABLE_IOCP_FLAG (TT_FIRST_USER_FLAG<<6) +#define TT_ENABLE_IOCP (TT_ENABLE_IOCP_FLAG|TT_NEED_THREADS) +#define TT_ENABLE_DEBUG_MODE (TT_ENABLE_IOCP_FLAG<<7) + +/* All the flags that a legacy test needs. */ +#define TT_ISOLATED TT_FORK|TT_NEED_SOCKETPAIR|TT_NEED_BASE + + +#define BASIC(name,flags) \ + { #name, test_## name, flags, &basic_setup, NULL } + +#define LEGACY(name,flags) \ + { #name, run_legacy_test_fn, flags|TT_LEGACY, &legacy_setup, \ + test_## name } + +struct evutil_addrinfo; +struct evutil_addrinfo *ai_find_by_family(struct evutil_addrinfo *ai, int f); +struct evutil_addrinfo *ai_find_by_protocol(struct evutil_addrinfo *ai, int p); +int test_ai_eq_(const struct evutil_addrinfo *ai, const char *sockaddr_port, + int socktype, int protocol, int line); + +#define test_ai_eq(ai, str, s, p) do { \ + if (test_ai_eq_((ai), (str), (s), (p), __LINE__)<0) \ + goto end; \ + } while (0) + +#define test_timeval_diff_leq(tv1, tv2, diff, tolerance) \ + tt_int_op(labs(timeval_msec_diff((tv1), (tv2)) - diff), <=, tolerance) + +#define test_timeval_diff_eq(tv1, tv2, diff) \ + test_timeval_diff_leq((tv1), (tv2), (diff), 50) + +long timeval_msec_diff(const struct timeval *start, const struct timeval *end); + +#ifndef _WIN32 +pid_t regress_fork(void); +#endif + +#ifdef EVENT__HAVE_OPENSSL +#include +EVP_PKEY *ssl_getkey(void); +X509 *ssl_getcert(EVP_PKEY *key); +SSL_CTX *get_ssl_ctx(void); +void init_ssl(void); +#endif + +void * basic_test_setup(const struct testcase_t *testcase); +int basic_test_cleanup(const struct testcase_t *testcase, void *ptr); + +#ifdef __cplusplus +} +#endif + +#endif /* REGRESS_H_INCLUDED_ */ diff --git a/ipc/chromium/src/third_party/libevent/test/regress.rpc b/ipc/chromium/src/third_party/libevent/test/regress.rpc new file mode 100644 index 0000000000..0ee904e913 --- /dev/null +++ b/ipc/chromium/src/third_party/libevent/test/regress.rpc @@ -0,0 +1,25 @@ +/* tests data packing and unpacking */ + +struct msg { + string /* sender */ from_name = 1; /* be verbose */ + string to_name = 2; + optional struct[kill] attack = 3; + array struct[run] run = 4; +} + +struct kill { + string weapon = 0x10121; + string action = 2; + array int how_often = 3; +} + +struct run { + string how = 1; + optional bytes some_bytes = 2; + + bytes fixed_bytes[24] = 3; + array string notes = 4; + + optional int64 large_number = 5; + array int other_numbers = 6; +} diff --git a/ipc/chromium/src/third_party/libevent/test/regress_buffer.c b/ipc/chromium/src/third_party/libevent/test/regress_buffer.c new file mode 100644 index 0000000000..f259b924bf --- /dev/null +++ b/ipc/chromium/src/third_party/libevent/test/regress_buffer.c @@ -0,0 +1,2870 @@ +/* + * Copyright (c) 2003-2007 Niels Provos + * Copyright (c) 2007-2012 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "util-internal.h" + +#ifdef _WIN32 +#include +#include +#endif + +#include "event2/event-config.h" + +#include +#include +#ifdef EVENT__HAVE_SYS_TIME_H +#include +#endif +#include +#ifndef _WIN32 +#include +#include +#include +#include +#include +#endif +#include +#include +#include +#include +#include + +#include "event2/event.h" +#include "event2/buffer.h" +#include "event2/buffer_compat.h" +#include "event2/util.h" + +#include "defer-internal.h" +#include "evbuffer-internal.h" +#include "log-internal.h" + +#include "regress.h" + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) + +/* Validates that an evbuffer is good. Returns false if it isn't, true if it + * is*/ +static int +evbuffer_validate_(struct evbuffer *buf) +{ + struct evbuffer_chain *chain; + size_t sum = 0; + int found_last_with_datap = 0; + + if (buf->first == NULL) { + tt_assert(buf->last == NULL); + tt_assert(buf->total_len == 0); + } + + chain = buf->first; + + tt_assert(buf->last_with_datap); + if (buf->last_with_datap == &buf->first) + found_last_with_datap = 1; + + while (chain != NULL) { + if (&chain->next == buf->last_with_datap) + found_last_with_datap = 1; + sum += chain->off; + if (chain->next == NULL) { + tt_assert(buf->last == chain); + } + tt_assert(chain->buffer_len >= chain->misalign + chain->off); + chain = chain->next; + } + + if (buf->first) + tt_assert(*buf->last_with_datap); + + if (*buf->last_with_datap) { + chain = *buf->last_with_datap; + if (chain->off == 0 || buf->total_len == 0) { + tt_assert(chain->off == 0) + tt_assert(chain == buf->first); + tt_assert(buf->total_len == 0); + } + chain = chain->next; + while (chain != NULL) { + tt_assert(chain->off == 0); + chain = chain->next; + } + } else { + tt_assert(buf->last_with_datap == &buf->first); + } + tt_assert(found_last_with_datap); + + tt_assert(sum == buf->total_len); + return 1; + end: + return 0; +} + +static void +evbuffer_get_waste(struct evbuffer *buf, size_t *allocatedp, size_t *wastedp, size_t *usedp) +{ + struct evbuffer_chain *chain; + size_t a, w, u; + int n = 0; + u = a = w = 0; + + chain = buf->first; + /* skip empty at start */ + while (chain && chain->off==0) { + ++n; + a += chain->buffer_len; + chain = chain->next; + } + /* first nonempty chain: stuff at the end only is wasted. */ + if (chain) { + ++n; + a += chain->buffer_len; + u += chain->off; + if (chain->next && chain->next->off) + w += (size_t)(chain->buffer_len - (chain->misalign + chain->off)); + chain = chain->next; + } + /* subsequent nonempty chains */ + while (chain && chain->off) { + ++n; + a += chain->buffer_len; + w += (size_t)chain->misalign; + u += chain->off; + if (chain->next && chain->next->off) + w += (size_t) (chain->buffer_len - (chain->misalign + chain->off)); + chain = chain->next; + } + /* subsequent empty chains */ + while (chain) { + ++n; + a += chain->buffer_len; + } + *allocatedp = a; + *wastedp = w; + *usedp = u; +} + +#define evbuffer_validate(buf) \ + TT_STMT_BEGIN if (!evbuffer_validate_(buf)) TT_DIE(("Buffer format invalid")); TT_STMT_END + +static void +test_evbuffer(void *ptr) +{ + static char buffer[512], *tmp; + struct evbuffer *evb = evbuffer_new(); + struct evbuffer *evb_two = evbuffer_new(); + size_t sz_tmp; + int i; + + evbuffer_validate(evb); + evbuffer_add_printf(evb, "%s/%d", "hello", 1); + evbuffer_validate(evb); + + tt_assert(evbuffer_get_length(evb) == 7); + tt_assert(!memcmp((char*)EVBUFFER_DATA(evb), "hello/1", 1)); + + evbuffer_add_buffer(evb, evb_two); + evbuffer_validate(evb); + + evbuffer_drain(evb, strlen("hello/")); + evbuffer_validate(evb); + tt_assert(evbuffer_get_length(evb) == 1); + tt_assert(!memcmp((char*)EVBUFFER_DATA(evb), "1", 1)); + + evbuffer_add_printf(evb_two, "%s", "/hello"); + evbuffer_validate(evb); + evbuffer_add_buffer(evb, evb_two); + evbuffer_validate(evb); + + tt_assert(evbuffer_get_length(evb_two) == 0); + tt_assert(evbuffer_get_length(evb) == 7); + tt_assert(!memcmp((char*)EVBUFFER_DATA(evb), "1/hello", 7)); + + memset(buffer, 0, sizeof(buffer)); + evbuffer_add(evb, buffer, sizeof(buffer)); + evbuffer_validate(evb); + tt_assert(evbuffer_get_length(evb) == 7 + 512); + + tmp = (char *)evbuffer_pullup(evb, 7 + 512); + tt_assert(tmp); + tt_assert(!strncmp(tmp, "1/hello", 7)); + tt_assert(!memcmp(tmp + 7, buffer, sizeof(buffer))); + evbuffer_validate(evb); + + evbuffer_prepend(evb, "something", 9); + evbuffer_validate(evb); + evbuffer_prepend(evb, "else", 4); + evbuffer_validate(evb); + + tmp = (char *)evbuffer_pullup(evb, 4 + 9 + 7); + tt_assert(!strncmp(tmp, "elsesomething1/hello", 4 + 9 + 7)); + evbuffer_validate(evb); + + evbuffer_drain(evb, -1); + evbuffer_validate(evb); + evbuffer_drain(evb_two, -1); + evbuffer_validate(evb); + + for (i = 0; i < 3; ++i) { + evbuffer_add(evb_two, buffer, sizeof(buffer)); + evbuffer_validate(evb_two); + evbuffer_add_buffer(evb, evb_two); + evbuffer_validate(evb); + evbuffer_validate(evb_two); + } + + tt_assert(evbuffer_get_length(evb_two) == 0); + tt_assert(evbuffer_get_length(evb) == i * sizeof(buffer)); + + /* test remove buffer */ + sz_tmp = (size_t)(sizeof(buffer)*2.5); + evbuffer_remove_buffer(evb, evb_two, sz_tmp); + tt_assert(evbuffer_get_length(evb_two) == sz_tmp); + tt_assert(evbuffer_get_length(evb) == sizeof(buffer) / 2); + evbuffer_validate(evb); + + if (memcmp(evbuffer_pullup( + evb, -1), buffer, sizeof(buffer) / 2) != 0 || + memcmp(evbuffer_pullup( + evb_two, -1), buffer, sizeof(buffer)) != 0) + tt_abort_msg("Pullup did not preserve content"); + + evbuffer_validate(evb); + + + /* testing one-vector reserve and commit */ + { + struct evbuffer_iovec v[1]; + char *buf; + int i, j, r; + + for (i = 0; i < 3; ++i) { + r = evbuffer_reserve_space(evb, 10000, v, 1); + tt_int_op(r, ==, 1); + tt_assert(v[0].iov_len >= 10000); + tt_assert(v[0].iov_base != NULL); + + evbuffer_validate(evb); + buf = v[0].iov_base; + for (j = 0; j < 10000; ++j) { + buf[j] = j; + } + evbuffer_validate(evb); + + tt_int_op(evbuffer_commit_space(evb, v, 1), ==, 0); + evbuffer_validate(evb); + + tt_assert(evbuffer_get_length(evb) >= 10000); + + evbuffer_drain(evb, j * 5000); + evbuffer_validate(evb); + } + } + + end: + evbuffer_free(evb); + evbuffer_free(evb_two); +} + +static void +no_cleanup(const void *data, size_t datalen, void *extra) +{ +} + +static void +test_evbuffer_remove_buffer_with_empty(void *ptr) +{ + struct evbuffer *src = evbuffer_new(); + struct evbuffer *dst = evbuffer_new(); + char buf[2] = { 'A', 'A' }; + + evbuffer_validate(src); + evbuffer_validate(dst); + + /* setup the buffers */ + /* we need more data in src than we will move later */ + evbuffer_add_reference(src, buf, sizeof(buf), no_cleanup, NULL); + evbuffer_add_reference(src, buf, sizeof(buf), no_cleanup, NULL); + /* we need one buffer in dst and one empty buffer at the end */ + evbuffer_add(dst, buf, sizeof(buf)); + evbuffer_add_reference(dst, buf, 0, no_cleanup, NULL); + + evbuffer_validate(src); + evbuffer_validate(dst); + + tt_mem_op(evbuffer_pullup(src, -1), ==, "AAAA", 4); + tt_mem_op(evbuffer_pullup(dst, -1), ==, "AA", 2); + + /* move three bytes over */ + evbuffer_remove_buffer(src, dst, 3); + + evbuffer_validate(src); + evbuffer_validate(dst); + + tt_mem_op(evbuffer_pullup(src, -1), ==, "A", 1); + tt_mem_op(evbuffer_pullup(dst, -1), ==, "AAAAA", 5); + + end: + evbuffer_free(src); + evbuffer_free(dst); +} + +static void +test_evbuffer_remove_buffer_with_empty2(void *ptr) +{ + struct evbuffer *src = evbuffer_new(); + struct evbuffer *dst = evbuffer_new(); + struct evbuffer *buf = evbuffer_new(); + + evbuffer_add(buf, "foo", 3); + evbuffer_add_reference(buf, "foo", 3, NULL, NULL); + + evbuffer_add_reference(src, "foo", 3, NULL, NULL); + evbuffer_add_reference(src, NULL, 0, NULL, NULL); + evbuffer_add_buffer(src, buf); + + evbuffer_add(buf, "foo", 3); + evbuffer_add_reference(buf, "foo", 3, NULL, NULL); + + evbuffer_add_reference(dst, "foo", 3, NULL, NULL); + evbuffer_add_reference(dst, NULL, 0, NULL, NULL); + evbuffer_add_buffer(dst, buf); + + tt_int_op(evbuffer_get_length(src), ==, 9); + tt_int_op(evbuffer_get_length(dst), ==, 9); + + evbuffer_validate(src); + evbuffer_validate(dst); + + tt_mem_op(evbuffer_pullup(src, -1), ==, "foofoofoo", 9); + tt_mem_op(evbuffer_pullup(dst, -1), ==, "foofoofoo", 9); + + evbuffer_remove_buffer(src, dst, 8); + + evbuffer_validate(src); + evbuffer_validate(dst); + + tt_int_op(evbuffer_get_length(src), ==, 1); + tt_int_op(evbuffer_get_length(dst), ==, 17); + + tt_mem_op(evbuffer_pullup(src, -1), ==, "o", 1); + tt_mem_op(evbuffer_pullup(dst, -1), ==, "foofoofoofoofoofo", 17); + + end: + evbuffer_free(src); + evbuffer_free(dst); + evbuffer_free(buf); +} + +static void +test_evbuffer_remove_buffer_with_empty3(void *ptr) +{ + struct evbuffer *src = evbuffer_new(); + struct evbuffer *dst = evbuffer_new(); + struct evbuffer *buf = evbuffer_new(); + + evbuffer_add(buf, "foo", 3); + evbuffer_add_reference(buf, NULL, 0, NULL, NULL); + + evbuffer_add_reference(src, "foo", 3, NULL, NULL); + evbuffer_add_reference(src, NULL, 0, NULL, NULL); + evbuffer_prepend_buffer(src, buf); + + evbuffer_add(buf, "foo", 3); + evbuffer_add_reference(buf, NULL, 0, NULL, NULL); + + evbuffer_add_reference(dst, "foo", 3, NULL, NULL); + evbuffer_add_reference(dst, NULL, 0, NULL, NULL); + evbuffer_prepend_buffer(dst, buf); + + tt_int_op(evbuffer_get_length(src), ==, 6); + tt_int_op(evbuffer_get_length(dst), ==, 6); + + evbuffer_validate(src); + evbuffer_validate(dst); + + tt_mem_op(evbuffer_pullup(src, -1), ==, "foofoo", 6); + tt_mem_op(evbuffer_pullup(dst, -1), ==, "foofoo", 6); + + evbuffer_remove_buffer(src, dst, 5); + + evbuffer_validate(src); + evbuffer_validate(dst); + + tt_int_op(evbuffer_get_length(src), ==, 1); + tt_int_op(evbuffer_get_length(dst), ==, 11); + + tt_mem_op(evbuffer_pullup(src, -1), ==, "o", 1); + tt_mem_op(evbuffer_pullup(dst, -1), ==, "foofoofoofo", 11); + + end: + evbuffer_free(src); + evbuffer_free(dst); + evbuffer_free(buf); +} + +static void +test_evbuffer_pullup_with_empty(void *ptr) +{ + struct evbuffer *buf = NULL; + + buf = evbuffer_new(); + evbuffer_add(buf, "foo", 3); + evbuffer_add_reference(buf, NULL, 0, NULL, NULL); + evbuffer_validate(buf); + tt_int_op(evbuffer_get_length(buf), ==, 3); + tt_mem_op(evbuffer_pullup(buf, -1), ==, "foo", 3); + + evbuffer_free(buf); + buf = evbuffer_new(); + evbuffer_validate(buf); + tt_int_op(evbuffer_get_length(buf), ==, 0); + tt_int_op(evbuffer_pullup(buf, -1), ==, NULL); + + evbuffer_free(buf); + buf = evbuffer_new(); + evbuffer_add(buf, "foo", 3); + evbuffer_add_reference(buf, NULL, 0, NULL, NULL); + evbuffer_validate(buf); + tt_mem_op(evbuffer_pullup(buf, 3), ==, "foo", 3); + + end: + if (buf) + evbuffer_free(buf); +} + +static void +test_evbuffer_remove_buffer_with_empty_front(void *ptr) +{ + struct evbuffer *buf1 = NULL, *buf2 = NULL; + + buf1 = evbuffer_new(); + tt_assert(buf1); + + buf2 = evbuffer_new(); + tt_assert(buf2); + + tt_int_op(evbuffer_add_reference(buf1, "foo", 3, NULL, NULL), ==, 0); + tt_int_op(evbuffer_prepend(buf1, "", 0), ==, 0); + tt_int_op(evbuffer_remove_buffer(buf1, buf2, 1), ==, 1); + tt_int_op(evbuffer_add(buf1, "bar", 3), ==, 0); + tt_mem_op(evbuffer_pullup(buf1, -1), ==, "oobar", 5); + + evbuffer_validate(buf1); + evbuffer_validate(buf2); + + end: + if (buf1) + evbuffer_free(buf1); + if (buf2) + evbuffer_free(buf2); +} + +static void +test_evbuffer_remove_buffer_adjust_last_with_datap_with_empty(void *ptr) +{ + struct evbuffer *buf1 = NULL, *buf2 = NULL; + + buf1 = evbuffer_new(); + tt_assert(buf1); + + buf2 = evbuffer_new(); + tt_assert(buf2); + + tt_int_op(evbuffer_add(buf1, "aaaaaa", 6), ==, 0); + + // buf1: aaaaaab + // buf2: + { + struct evbuffer_iovec iovecs[2]; + /** we want two chains, to leave one chain empty */ + tt_int_op(evbuffer_reserve_space(buf1, 971, iovecs, 2), ==, 2); + tt_int_op(iovecs[0].iov_len, >=, 1); + tt_int_op(iovecs[1].iov_len, >=, 1); + tt_assert(*(char *)(iovecs[0].iov_base) = 'b'); + tt_assert(iovecs[0].iov_len = 1); + tt_int_op(evbuffer_commit_space(buf1, iovecs, 1), ==, 0); + } + + // buf1: aaaaaab + // buf2: dddcc + tt_int_op(evbuffer_add(buf2, "cc", 2), ==, 0); + tt_int_op(evbuffer_prepend(buf2, "ddd", 3), ==, 0); + + // buf1: + // buf2: aaaaaabdddcc + tt_int_op(evbuffer_prepend_buffer(buf2, buf1), ==, 0); + + // buf1: aaaaaabdddcc + // buf2: + tt_int_op(evbuffer_add_buffer(buf1, buf2), ==, 0); + + // buf1: c + // buf2: aaaaaabdddc + tt_int_op(evbuffer_remove_buffer(buf1, buf2, 11), ==, 11); + + // This fails today, we observe "aaaaaabcddd" instead! + tt_mem_op(evbuffer_pullup(buf2, -1), ==, "aaaaaabdddc", 11); + + evbuffer_validate(buf1); + evbuffer_validate(buf2); + + end: + if (buf1) + evbuffer_free(buf1); + if (buf2) + evbuffer_free(buf2); +} + +static void +test_evbuffer_add_buffer_with_empty(void *ptr) +{ + struct evbuffer *src = evbuffer_new(); + struct evbuffer *dst = evbuffer_new(); + struct evbuffer *buf = evbuffer_new(); + + evbuffer_add(buf, "foo", 3); + + evbuffer_add_reference(src, "foo", 3, NULL, NULL); + evbuffer_add_reference(src, NULL, 0, NULL, NULL); + evbuffer_add_buffer(src, buf); + + evbuffer_add(buf, "foo", 3); + + evbuffer_add_reference(dst, "foo", 3, NULL, NULL); + evbuffer_add_reference(dst, NULL, 0, NULL, NULL); + evbuffer_add_buffer(dst, buf); + + tt_int_op(evbuffer_get_length(src), ==, 6); + tt_int_op(evbuffer_get_length(dst), ==, 6); + + evbuffer_validate(src); + evbuffer_validate(dst); + + end: + evbuffer_free(src); + evbuffer_free(dst); + evbuffer_free(buf); +} + +static void +test_evbuffer_add_buffer_with_empty2(void *ptr) +{ + struct evbuffer *src = evbuffer_new(); + struct evbuffer *dst = evbuffer_new(); + struct evbuffer *buf = evbuffer_new(); + + evbuffer_add(buf, "foo", 3); + + evbuffer_add_reference(src, NULL, 0, NULL, NULL); + evbuffer_add_buffer(src, buf); + + evbuffer_add(buf, "foo", 3); + + evbuffer_add_reference(dst, NULL, 0, NULL, NULL); + evbuffer_add_buffer(dst, buf); + + tt_int_op(evbuffer_get_length(src), ==, 3); + tt_int_op(evbuffer_get_length(dst), ==, 3); + + evbuffer_validate(src); + evbuffer_validate(dst); + + end: + evbuffer_free(src); + evbuffer_free(dst); + evbuffer_free(buf); +} + +static void +test_evbuffer_reserve2(void *ptr) +{ + /* Test the two-vector cases of reserve/commit. */ + struct evbuffer *buf = evbuffer_new(); + int n, i; + struct evbuffer_iovec v[2]; + size_t remaining; + char *cp, *cp2; + + /* First chunk will necessarily be one chunk. Use 512 bytes of it.*/ + n = evbuffer_reserve_space(buf, 1024, v, 2); + tt_int_op(n, ==, 1); + tt_int_op(evbuffer_get_length(buf), ==, 0); + tt_assert(v[0].iov_base != NULL); + tt_int_op(v[0].iov_len, >=, 1024); + memset(v[0].iov_base, 'X', 512); + cp = v[0].iov_base; + remaining = v[0].iov_len - 512; + v[0].iov_len = 512; + evbuffer_validate(buf); + tt_int_op(0, ==, evbuffer_commit_space(buf, v, 1)); + tt_int_op(evbuffer_get_length(buf), ==, 512); + evbuffer_validate(buf); + + /* Ask for another same-chunk request, in an existing chunk. Use 8 + * bytes of it. */ + n = evbuffer_reserve_space(buf, 32, v, 2); + tt_int_op(n, ==, 1); + tt_assert(cp + 512 == v[0].iov_base); + tt_int_op(remaining, ==, v[0].iov_len); + memset(v[0].iov_base, 'Y', 8); + v[0].iov_len = 8; + tt_int_op(0, ==, evbuffer_commit_space(buf, v, 1)); + tt_int_op(evbuffer_get_length(buf), ==, 520); + remaining -= 8; + evbuffer_validate(buf); + + /* Now ask for a request that will be split. Use only one byte of it, + though. */ + n = evbuffer_reserve_space(buf, remaining+64, v, 2); + tt_int_op(n, ==, 2); + tt_assert(cp + 520 == v[0].iov_base); + tt_int_op(remaining, ==, v[0].iov_len); + tt_assert(v[1].iov_base); + tt_assert(v[1].iov_len >= 64); + cp2 = v[1].iov_base; + memset(v[0].iov_base, 'Z', 1); + v[0].iov_len = 1; + tt_int_op(0, ==, evbuffer_commit_space(buf, v, 1)); + tt_int_op(evbuffer_get_length(buf), ==, 521); + remaining -= 1; + evbuffer_validate(buf); + + /* Now ask for a request that will be split. Use some of the first + * part and some of the second. */ + n = evbuffer_reserve_space(buf, remaining+64, v, 2); + evbuffer_validate(buf); + tt_int_op(n, ==, 2); + tt_assert(cp + 521 == v[0].iov_base); + tt_int_op(remaining, ==, v[0].iov_len); + tt_assert(v[1].iov_base == cp2); + tt_assert(v[1].iov_len >= 64); + memset(v[0].iov_base, 'W', 400); + v[0].iov_len = 400; + memset(v[1].iov_base, 'x', 60); + v[1].iov_len = 60; + tt_int_op(0, ==, evbuffer_commit_space(buf, v, 2)); + tt_int_op(evbuffer_get_length(buf), ==, 981); + evbuffer_validate(buf); + + /* Now peek to make sure stuff got made how we like. */ + memset(v,0,sizeof(v)); + n = evbuffer_peek(buf, -1, NULL, v, 2); + tt_int_op(n, ==, 2); + tt_int_op(v[0].iov_len, ==, 921); + tt_int_op(v[1].iov_len, ==, 60); + + cp = v[0].iov_base; + for (i=0; i<512; ++i) + tt_int_op(cp[i], ==, 'X'); + for (i=512; i<520; ++i) + tt_int_op(cp[i], ==, 'Y'); + for (i=520; i<521; ++i) + tt_int_op(cp[i], ==, 'Z'); + for (i=521; i<921; ++i) + tt_int_op(cp[i], ==, 'W'); + + cp = v[1].iov_base; + for (i=0; i<60; ++i) + tt_int_op(cp[i], ==, 'x'); + +end: + evbuffer_free(buf); +} + +static void +test_evbuffer_reserve_many(void *ptr) +{ + /* This is a glass-box test to handle expanding a buffer with more + * chunks and reallocating chunks as needed */ + struct evbuffer *buf = evbuffer_new(); + struct evbuffer_iovec v[8]; + int n; + size_t sz; + int add_data = ptr && !strcmp(ptr, "add"); + int fill_first = ptr && !strcmp(ptr, "fill"); + char *cp1, *cp2; + + /* When reserving the the first chunk, we just allocate it */ + n = evbuffer_reserve_space(buf, 128, v, 2); + evbuffer_validate(buf); + tt_int_op(n, ==, 1); + tt_assert(v[0].iov_len >= 128); + sz = v[0].iov_len; + cp1 = v[0].iov_base; + if (add_data) { + *(char*)v[0].iov_base = 'X'; + v[0].iov_len = 1; + n = evbuffer_commit_space(buf, v, 1); + tt_int_op(n, ==, 0); + } else if (fill_first) { + memset(v[0].iov_base, 'X', v[0].iov_len); + n = evbuffer_commit_space(buf, v, 1); + tt_int_op(n, ==, 0); + n = evbuffer_reserve_space(buf, 128, v, 2); + tt_int_op(n, ==, 1); + sz = v[0].iov_len; + tt_assert(v[0].iov_base != cp1); + cp1 = v[0].iov_base; + } + + /* Make another chunk get added. */ + n = evbuffer_reserve_space(buf, sz+128, v, 2); + evbuffer_validate(buf); + tt_int_op(n, ==, 2); + sz = v[0].iov_len + v[1].iov_len; + tt_int_op(sz, >=, v[0].iov_len+128); + if (add_data) { + tt_assert(v[0].iov_base == cp1 + 1); + } else { + tt_assert(v[0].iov_base == cp1); + } + cp1 = v[0].iov_base; + cp2 = v[1].iov_base; + + /* And a third chunk. */ + n = evbuffer_reserve_space(buf, sz+128, v, 3); + evbuffer_validate(buf); + tt_int_op(n, ==, 3); + tt_assert(cp1 == v[0].iov_base); + tt_assert(cp2 == v[1].iov_base); + sz = v[0].iov_len + v[1].iov_len + v[2].iov_len; + + /* Now force a reallocation by asking for more space in only 2 + * buffers. */ + n = evbuffer_reserve_space(buf, sz+128, v, 2); + evbuffer_validate(buf); + if (add_data) { + tt_int_op(n, ==, 2); + tt_assert(cp1 == v[0].iov_base); + } else { + tt_int_op(n, ==, 1); + } + +end: + evbuffer_free(buf); +} + +static void +test_evbuffer_reserve_with_empty(void *ptr) +{ + struct evbuffer *buf; + struct evbuffer_iovec v[2]; + + tt_assert(buf = evbuffer_new()); + evbuffer_add(buf, "a", 1); + tt_int_op(evbuffer_reserve_space(buf, 1<<12, v, 2), ==, 2); + v[0].iov_len = 1; + *(char *)v[0].iov_base = 'b'; + tt_int_op(evbuffer_commit_space(buf, v, 1), ==, 0); + evbuffer_add(buf, "c", 1); + tt_mem_op(evbuffer_pullup(buf, -1), ==, "abc", 2); + + evbuffer_validate(buf); + + end: + if (buf) + evbuffer_free(buf); +} + +/* regression for evbuffer_expand_fast_() with invalid last_with_datap that has + * been left after evbuffer_prepend() with empty chain in it */ +static void +test_evbuffer_reserve_invalid_last_with_datap(void *ptr) +{ + struct evbuffer *buf = NULL; + struct evbuffer_iovec vec[2]; + const int nvec = ARRAY_SIZE(vec); + int i, avec; + + buf = evbuffer_new(); + tt_assert(buf); + + /* prepend with an empty chain */ + evbuffer_add_reference(buf, "", 0, NULL, NULL); + evbuffer_prepend(buf, "foo", 3); + /* after invalid last_with_datap will create new chain */ + evbuffer_add(buf, "", 0); + /* we need to create at least 2 "used" (in evbuffer_expand_fast_()) chains */ + tt_int_op(avec = evbuffer_reserve_space(buf, 1<<12, vec, nvec), >=, 1); + for (i = 0; i < avec; ++i) + vec[i].iov_len = 0; + tt_int_op(evbuffer_commit_space(buf, vec, avec), ==, 0); + + /* and an actual problem, that triggers an assert(chain == buf->first) in + * evbuffer_expand_fast_() */ + tt_int_op(evbuffer_reserve_space(buf, 1<<13, vec, nvec), >=, 1); + + evbuffer_validate(buf); + +end: + if (buf) + evbuffer_free(buf); +} + +static void +test_evbuffer_expand(void *ptr) +{ + char data[4096]; + struct evbuffer *buf; + size_t a,w,u; + void *buffer; + + memset(data, 'X', sizeof(data)); + + /* Make sure that expand() works on an empty buffer */ + buf = evbuffer_new(); + tt_int_op(evbuffer_expand(buf, 20000), ==, 0); + evbuffer_validate(buf); + a=w=u=0; + evbuffer_get_waste(buf, &a,&w,&u); + tt_assert(w == 0); + tt_assert(u == 0); + tt_assert(a >= 20000); + tt_assert(buf->first); + tt_assert(buf->first == buf->last); + tt_assert(buf->first->off == 0); + tt_assert(buf->first->buffer_len >= 20000); + + /* Make sure that expand() works as a no-op when there's enough + * contiguous space already. */ + buffer = buf->first->buffer; + evbuffer_add(buf, data, 1024); + tt_int_op(evbuffer_expand(buf, 1024), ==, 0); + tt_assert(buf->first->buffer == buffer); + evbuffer_validate(buf); + evbuffer_free(buf); + + /* Make sure that expand() can work by moving misaligned data + * when it makes sense to do so. */ + buf = evbuffer_new(); + evbuffer_add(buf, data, 400); + { + int n = (int)(buf->first->buffer_len - buf->first->off - 1); + tt_assert(n < (int)sizeof(data)); + evbuffer_add(buf, data, n); + } + tt_assert(buf->first == buf->last); + tt_assert(buf->first->off == buf->first->buffer_len - 1); + evbuffer_drain(buf, buf->first->off - 1); + tt_assert(1 == evbuffer_get_length(buf)); + tt_assert(buf->first->misalign > 0); + tt_assert(buf->first->off == 1); + buffer = buf->first->buffer; + tt_assert(evbuffer_expand(buf, 40) == 0); + tt_assert(buf->first == buf->last); + tt_assert(buf->first->off == 1); + tt_assert(buf->first->buffer == buffer); + tt_assert(buf->first->misalign == 0); + evbuffer_validate(buf); + evbuffer_free(buf); + + /* add, expand, pull-up: This used to crash libevent. */ + buf = evbuffer_new(); + + evbuffer_add(buf, data, sizeof(data)); + evbuffer_add(buf, data, sizeof(data)); + evbuffer_add(buf, data, sizeof(data)); + + evbuffer_validate(buf); + evbuffer_expand(buf, 1024); + evbuffer_validate(buf); + evbuffer_pullup(buf, -1); + evbuffer_validate(buf); + +end: + evbuffer_free(buf); +} + +static void +test_evbuffer_expand_overflow(void *ptr) +{ + struct evbuffer *buf; + + buf = evbuffer_new(); + evbuffer_add(buf, "1", 1); + evbuffer_expand(buf, EVBUFFER_CHAIN_MAX); + evbuffer_validate(buf); + + evbuffer_expand(buf, EV_SIZE_MAX); + evbuffer_validate(buf); + +end: + evbuffer_free(buf); +} + +static void +test_evbuffer_add1(void *ptr) +{ + struct evbuffer *buf; + char *str; + + buf = evbuffer_new(); + evbuffer_add(buf, "1", 1); + evbuffer_validate(buf); + evbuffer_expand(buf, 2048); + evbuffer_validate(buf); + evbuffer_add(buf, "2", 1); + evbuffer_validate(buf); + evbuffer_add_printf(buf, "3"); + evbuffer_validate(buf); + + tt_assert(evbuffer_get_length(buf) == 3); + str = (char *)evbuffer_pullup(buf, -1); + tt_assert(str[0] == '1'); + tt_assert(str[1] == '2'); + tt_assert(str[2] == '3'); +end: + evbuffer_free(buf); +} + +static void +test_evbuffer_add2(void *ptr) +{ + struct evbuffer *buf; + static char data[4096]; + int data_len = MIN_BUFFER_SIZE-EVBUFFER_CHAIN_SIZE-10; + char *str; + int len; + + memset(data, 'P', sizeof(data)); + buf = evbuffer_new(); + evbuffer_add(buf, data, data_len); + evbuffer_validate(buf); + evbuffer_expand(buf, 100); + evbuffer_validate(buf); + evbuffer_add(buf, "2", 1); + evbuffer_validate(buf); + evbuffer_add_printf(buf, "3"); + evbuffer_validate(buf); + + len = evbuffer_get_length(buf); + tt_assert(len == data_len+2); + str = (char *)evbuffer_pullup(buf, -1); + tt_assert(str[len-3] == 'P'); + tt_assert(str[len-2] == '2'); + tt_assert(str[len-1] == '3'); +end: + evbuffer_free(buf); +} + +static int reference_cb_called; +static void +reference_cb(const void *data, size_t len, void *extra) +{ + tt_str_op(data, ==, "this is what we add as read-only memory."); + tt_int_op(len, ==, strlen(data)); + tt_want(extra == (void *)0xdeadaffe); + ++reference_cb_called; +end: + ; +} + +static void +test_evbuffer_reference(void *ptr) +{ + struct evbuffer *src = evbuffer_new(); + struct evbuffer *dst = evbuffer_new(); + struct evbuffer_iovec v[1]; + const char *data = "this is what we add as read-only memory."; + reference_cb_called = 0; + + tt_assert(evbuffer_add_reference(src, data, strlen(data), + reference_cb, (void *)0xdeadaffe) != -1); + + evbuffer_reserve_space(dst, strlen(data), v, 1); + tt_assert(evbuffer_remove(src, v[0].iov_base, 10) != -1); + + evbuffer_validate(src); + evbuffer_validate(dst); + + /* make sure that we don't write data at the beginning */ + evbuffer_prepend(src, "aaaaa", 5); + evbuffer_validate(src); + evbuffer_drain(src, 5); + + tt_assert(evbuffer_remove(src, ((char*)(v[0].iov_base)) + 10, + strlen(data) - 10) != -1); + + v[0].iov_len = strlen(data); + + evbuffer_commit_space(dst, v, 1); + evbuffer_validate(src); + evbuffer_validate(dst); + + tt_int_op(reference_cb_called, ==, 1); + + tt_assert(!memcmp(evbuffer_pullup(dst, strlen(data)), + data, strlen(data))); + evbuffer_validate(dst); + + end: + evbuffer_free(dst); + evbuffer_free(src); +} + +static void +test_evbuffer_reference2(void *ptr) +{ + struct evbuffer *buf; + static char data[4096]; + int data_len = MIN_BUFFER_SIZE-EVBUFFER_CHAIN_SIZE-10; + char *str; + int len; + + memset(data, 'P', sizeof(data)); + buf = evbuffer_new(); + evbuffer_add(buf, data, data_len); + evbuffer_validate(buf); + evbuffer_expand(buf, 100); + evbuffer_validate(buf); + evbuffer_add_reference(buf, "2", 1, no_cleanup, NULL); + evbuffer_validate(buf); + evbuffer_add_printf(buf, "3"); + evbuffer_validate(buf); + + len = evbuffer_get_length(buf); + tt_assert(len == data_len+2); + str = (char *)evbuffer_pullup(buf, -1); + tt_assert(str[len-3] == 'P'); + tt_assert(str[len-2] == '2'); + tt_assert(str[len-1] == '3'); +end: + evbuffer_free(buf); +} + +static struct event_base *addfile_test_event_base; +static int addfile_test_done_writing; +static int addfile_test_total_written; +static int addfile_test_total_read; + +static void +addfile_test_writecb(evutil_socket_t fd, short what, void *arg) +{ + struct evbuffer *b = arg; + int r; + evbuffer_validate(b); + while (evbuffer_get_length(b)) { + r = evbuffer_write(b, fd); + if (r > 0) { + addfile_test_total_written += r; + TT_BLATHER(("Wrote %d/%d bytes", r, addfile_test_total_written)); + } else { + int e = evutil_socket_geterror(fd); + if (EVUTIL_ERR_RW_RETRIABLE(e)) + return; + tt_fail_perror("write"); + event_base_loopexit(addfile_test_event_base,NULL); + } + evbuffer_validate(b); + } + addfile_test_done_writing = 1; + return; +end: + event_base_loopexit(addfile_test_event_base,NULL); +} + +static void +addfile_test_readcb(evutil_socket_t fd, short what, void *arg) +{ + struct evbuffer *b = arg; + int e, r = 0; + do { + r = evbuffer_read(b, fd, 1024); + if (r > 0) { + addfile_test_total_read += r; + TT_BLATHER(("Read %d/%d bytes", r, addfile_test_total_read)); + } + } while (r > 0); + if (r < 0) { + e = evutil_socket_geterror(fd); + if (! EVUTIL_ERR_RW_RETRIABLE(e)) { + tt_fail_perror("read"); + event_base_loopexit(addfile_test_event_base,NULL); + } + } + if (addfile_test_done_writing && + addfile_test_total_read >= addfile_test_total_written) { + event_base_loopexit(addfile_test_event_base,NULL); + } +} + +static void +test_evbuffer_add_file(void *ptr) +{ + struct basic_test_data *testdata = ptr; + const char *impl = testdata->setup_data; + struct evbuffer *src = evbuffer_new(), *dest = evbuffer_new(); + char *tmpfilename = NULL; + char *data = NULL; + const char *expect_data; + size_t datalen, expect_len; + const char *compare; + int fd = -1; + int want_ismapping = -1, want_cansendfile = -1; + unsigned flags = 0; + int use_segment = 1, use_bigfile = 0, map_from_offset = 0, + view_from_offset = 0; + struct evbuffer_file_segment *seg = NULL; + ev_off_t starting_offset = 0, mapping_len = -1; + ev_off_t segment_offset = 0, segment_len = -1; + struct event *rev=NULL, *wev=NULL; + struct event_base *base = testdata->base; + evutil_socket_t pair[2] = {-1, -1}; + struct evutil_weakrand_state seed = { 123456789U }; + + /* This test is highly parameterized based on substrings of its + * argument. The strings are: */ + tt_assert(impl); + if (strstr(impl, "nosegment")) { + /* If nosegment is set, use the older evbuffer_add_file + * interface */ + use_segment = 0; + } + if (strstr(impl, "bigfile")) { + /* If bigfile is set, use a 512K file. Else use a smaller + * one. */ + use_bigfile = 1; + } + if (strstr(impl, "map_offset")) { + /* If map_offset is set, we build the file segment starting + * from a point other than byte 0 and ending somewhere other + * than the last byte. Otherwise we map the whole thing */ + map_from_offset = 1; + } + if (strstr(impl, "offset_in_segment")) { + /* If offset_in_segment is set, we add a subsection of the + * file semgment starting from a point other than byte 0 of + * the segment. */ + view_from_offset = 1; + } + if (strstr(impl, "sendfile")) { + /* If sendfile is set, we try to use a sendfile/splice style + * backend. */ + flags = EVBUF_FS_DISABLE_MMAP; + want_cansendfile = 1; + want_ismapping = 0; + } else if (strstr(impl, "mmap")) { + /* If sendfile is set, we try to use a mmap/CreateFileMapping + * style backend. */ + flags = EVBUF_FS_DISABLE_SENDFILE; + want_ismapping = 1; + want_cansendfile = 0; + } else if (strstr(impl, "linear")) { + /* If linear is set, we try to use a read-the-whole-thing + * backend. */ + flags = EVBUF_FS_DISABLE_SENDFILE|EVBUF_FS_DISABLE_MMAP; + want_ismapping = 0; + want_cansendfile = 0; + } else if (strstr(impl, "default")) { + /* The caller doesn't care which backend we use. */ + ; + } else { + /* The caller must choose a backend. */ + TT_DIE(("Didn't recognize the implementation")); + } + + if (use_bigfile) { + unsigned int i; + datalen = 1024*512; + data = malloc(1024*512); + tt_assert(data); + for (i = 0; i < datalen; ++i) + data[i] = (char)evutil_weakrand_(&seed); + } else { + data = strdup("here is a relatively small string."); + tt_assert(data); + datalen = strlen(data); + } + + fd = regress_make_tmpfile(data, datalen, &tmpfilename); + + if (map_from_offset) { + starting_offset = datalen/4 + 1; + mapping_len = datalen / 2 - 1; + expect_data = data + starting_offset; + expect_len = mapping_len; + } else { + expect_data = data; + expect_len = datalen; + } + if (view_from_offset) { + tt_assert(use_segment); /* Can't do this with add_file*/ + segment_offset = expect_len / 3; + segment_len = expect_len / 2; + expect_data = expect_data + segment_offset; + expect_len = segment_len; + } + + if (use_segment) { + seg = evbuffer_file_segment_new(fd, starting_offset, + mapping_len, flags); + tt_assert(seg); + if (want_ismapping >= 0) { + if (seg->is_mapping != (unsigned)want_ismapping) + tt_skip(); + } + if (want_cansendfile >= 0) { + if (seg->can_sendfile != (unsigned)want_cansendfile) + tt_skip(); + } + } + + /* Say that it drains to a fd so that we can use sendfile. */ + evbuffer_set_flags(src, EVBUFFER_FLAG_DRAINS_TO_FD); + +#if defined(EVENT__HAVE_SENDFILE) && defined(__sun__) && defined(__svr4__) + /* We need to use a pair of AF_INET sockets, since Solaris + doesn't support sendfile() over AF_UNIX. */ + if (evutil_ersatz_socketpair_(AF_INET, SOCK_STREAM, 0, pair) == -1) + tt_abort_msg("ersatz_socketpair failed"); +#else + if (evutil_socketpair(AF_UNIX, SOCK_STREAM, 0, pair) == -1) + tt_abort_msg("socketpair failed"); +#endif + evutil_make_socket_nonblocking(pair[0]); + evutil_make_socket_nonblocking(pair[1]); + + tt_assert(fd != -1); + + if (use_segment) { + tt_assert(evbuffer_add_file_segment(src, seg, + segment_offset, segment_len)!=-1); + } else { + tt_assert(evbuffer_add_file(src, fd, starting_offset, + mapping_len) != -1); + } + + evbuffer_validate(src); + + addfile_test_event_base = base; + addfile_test_done_writing = 0; + addfile_test_total_written = 0; + addfile_test_total_read = 0; + + wev = event_new(base, pair[0], EV_WRITE|EV_PERSIST, + addfile_test_writecb, src); + rev = event_new(base, pair[1], EV_READ|EV_PERSIST, + addfile_test_readcb, dest); + + event_add(wev, NULL); + event_add(rev, NULL); + event_base_dispatch(base); + + evbuffer_validate(src); + evbuffer_validate(dest); + + tt_assert(addfile_test_done_writing); + tt_int_op(addfile_test_total_written, ==, expect_len); + tt_int_op(addfile_test_total_read, ==, expect_len); + + compare = (char *)evbuffer_pullup(dest, expect_len); + tt_assert(compare != NULL); + if (memcmp(compare, expect_data, expect_len)) { + tt_abort_msg("Data from add_file differs."); + } + + evbuffer_validate(dest); + end: + if (data) + free(data); + if (seg) + evbuffer_file_segment_free(seg); + if (src) + evbuffer_free(src); + if (dest) + evbuffer_free(dest); + if (pair[0] >= 0) + evutil_closesocket(pair[0]); + if (pair[1] >= 0) + evutil_closesocket(pair[1]); + if (wev) + event_free(wev); + if (rev) + event_free(rev); + if (tmpfilename) { + unlink(tmpfilename); + free(tmpfilename); + } +} + +static int file_segment_cleanup_cb_called_count = 0; +static struct evbuffer_file_segment const* file_segment_cleanup_cb_called_with = NULL; +static int file_segment_cleanup_cb_called_with_flags = 0; +static void* file_segment_cleanup_cb_called_with_arg = NULL; +static void +file_segment_cleanup_cp(struct evbuffer_file_segment const* seg, int flags, void* arg) +{ + ++file_segment_cleanup_cb_called_count; + file_segment_cleanup_cb_called_with = seg; + file_segment_cleanup_cb_called_with_flags = flags; + file_segment_cleanup_cb_called_with_arg = arg; +} + +static void +test_evbuffer_file_segment_add_cleanup_cb(void* ptr) +{ + char *tmpfilename = NULL; + int fd = -1; + struct evbuffer *evb = NULL; + struct evbuffer_file_segment *seg = NULL, *segptr; + char const* arg = "token"; + + fd = regress_make_tmpfile("file_segment_test_file", 22, &tmpfilename); + tt_int_op(fd, >=, 0); + + evb = evbuffer_new(); + tt_assert(evb); + + segptr = seg = evbuffer_file_segment_new(fd, 0, -1, 0); + tt_assert(seg); + + evbuffer_file_segment_add_cleanup_cb( + seg, &file_segment_cleanup_cp, (void*)arg); + + tt_assert(fd != -1); + + tt_assert(evbuffer_add_file_segment(evb, seg, 0, -1)!=-1); + + evbuffer_validate(evb); + + tt_int_op(file_segment_cleanup_cb_called_count, ==, 0); + evbuffer_file_segment_free(seg); + seg = NULL; /* Prevent double-free. */ + + tt_int_op(file_segment_cleanup_cb_called_count, ==, 0); + evbuffer_free(evb); + evb = NULL; /* pevent double-free */ + + tt_int_op(file_segment_cleanup_cb_called_count, ==, 1); + tt_assert(file_segment_cleanup_cb_called_with == segptr); + tt_assert(file_segment_cleanup_cb_called_with_flags == 0); + tt_assert(file_segment_cleanup_cb_called_with_arg == (void*)arg); + +end: + if (evb) + evbuffer_free(evb); + if (seg) + evbuffer_file_segment_free(seg); + if (tmpfilename) { + unlink(tmpfilename); + free(tmpfilename); + } +} + +#ifndef EVENT__DISABLE_MM_REPLACEMENT +static void * +failing_malloc(size_t how_much) +{ + errno = ENOMEM; + return NULL; +} +#endif + +static void +test_evbuffer_readln(void *ptr) +{ + struct evbuffer *evb = evbuffer_new(); + struct evbuffer *evb_tmp = evbuffer_new(); + const char *s; + char *cp = NULL; + size_t sz; + +#define tt_line_eq(content) \ + TT_STMT_BEGIN \ + if (!cp || sz != strlen(content) || strcmp(cp, content)) { \ + TT_DIE(("Wanted %s; got %s [%d]", content, cp, (int)sz)); \ + } \ + TT_STMT_END + + /* Test EOL_ANY. */ + s = "complex silly newline\r\n\n\r\n\n\rmore\0\n"; + evbuffer_add(evb, s, strlen(s)+2); + evbuffer_validate(evb); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_ANY); + tt_line_eq("complex silly newline"); + free(cp); + evbuffer_validate(evb); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_ANY); + if (!cp || sz != 5 || memcmp(cp, "more\0\0", 6)) + tt_abort_msg("Not as expected"); + tt_uint_op(evbuffer_get_length(evb), ==, 0); + evbuffer_validate(evb); + s = "\nno newline"; + evbuffer_add(evb, s, strlen(s)); + free(cp); + evbuffer_validate(evb); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_ANY); + tt_line_eq(""); + free(cp); + evbuffer_validate(evb); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_ANY); + tt_assert(!cp); + evbuffer_validate(evb); + evbuffer_drain(evb, evbuffer_get_length(evb)); + tt_assert(evbuffer_get_length(evb) == 0); + evbuffer_validate(evb); + + /* Test EOL_CRLF */ + s = "Line with\rin the middle\nLine with good crlf\r\n\nfinal\n"; + evbuffer_add(evb, s, strlen(s)); + evbuffer_validate(evb); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF); + tt_line_eq("Line with\rin the middle"); + free(cp); + evbuffer_validate(evb); + + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF); + tt_line_eq("Line with good crlf"); + free(cp); + evbuffer_validate(evb); + + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF); + tt_line_eq(""); + free(cp); + evbuffer_validate(evb); + + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF); + tt_line_eq("final"); + s = "x"; + evbuffer_validate(evb); + evbuffer_add(evb, s, 1); + evbuffer_validate(evb); + free(cp); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF); + tt_assert(!cp); + evbuffer_validate(evb); + + /* Test CRLF_STRICT */ + s = " and a bad crlf\nand a good one\r\n\r\nMore\r"; + evbuffer_add(evb, s, strlen(s)); + evbuffer_validate(evb); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF_STRICT); + tt_line_eq("x and a bad crlf\nand a good one"); + free(cp); + evbuffer_validate(evb); + + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF_STRICT); + tt_line_eq(""); + free(cp); + evbuffer_validate(evb); + + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF_STRICT); + tt_assert(!cp); + evbuffer_validate(evb); + evbuffer_add(evb, "\n", 1); + evbuffer_validate(evb); + + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF_STRICT); + tt_line_eq("More"); + free(cp); + tt_assert(evbuffer_get_length(evb) == 0); + evbuffer_validate(evb); + + s = "An internal CR\r is not an eol\r\nNor is a lack of one"; + evbuffer_add(evb, s, strlen(s)); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF_STRICT); + tt_line_eq("An internal CR\r is not an eol"); + free(cp); + evbuffer_validate(evb); + + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF_STRICT); + tt_assert(!cp); + evbuffer_validate(evb); + + evbuffer_add(evb, "\r\n", 2); + evbuffer_validate(evb); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF_STRICT); + tt_line_eq("Nor is a lack of one"); + free(cp); + tt_assert(evbuffer_get_length(evb) == 0); + evbuffer_validate(evb); + + /* Test LF */ + s = "An\rand a nl\n\nText"; + evbuffer_add(evb, s, strlen(s)); + evbuffer_validate(evb); + + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_LF); + tt_line_eq("An\rand a nl"); + free(cp); + evbuffer_validate(evb); + + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_LF); + tt_line_eq(""); + free(cp); + evbuffer_validate(evb); + + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_LF); + tt_assert(!cp); + free(cp); + evbuffer_add(evb, "\n", 1); + evbuffer_validate(evb); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_LF); + tt_line_eq("Text"); + free(cp); + evbuffer_validate(evb); + + /* Test NUL */ + tt_int_op(evbuffer_get_length(evb), ==, 0); + { + char x[] = + "NUL\n\0\0" + "The all-zeros character which may serve\0" + "to accomplish time fill\0and media fill"; + /* Add all but the final NUL of x. */ + evbuffer_add(evb, x, sizeof(x)-1); + } + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_NUL); + tt_line_eq("NUL\n"); + free(cp); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_NUL); + tt_line_eq(""); + free(cp); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_NUL); + tt_line_eq("The all-zeros character which may serve"); + free(cp); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_NUL); + tt_line_eq("to accomplish time fill"); + free(cp); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_NUL); + tt_ptr_op(cp, ==, NULL); + evbuffer_drain(evb, -1); + + /* Test CRLF_STRICT - across boundaries*/ + s = " and a bad crlf\nand a good one\r"; + evbuffer_add(evb_tmp, s, strlen(s)); + evbuffer_validate(evb); + evbuffer_add_buffer(evb, evb_tmp); + evbuffer_validate(evb); + s = "\n\r"; + evbuffer_add(evb_tmp, s, strlen(s)); + evbuffer_validate(evb); + evbuffer_add_buffer(evb, evb_tmp); + evbuffer_validate(evb); + s = "\nMore\r"; + evbuffer_add(evb_tmp, s, strlen(s)); + evbuffer_validate(evb); + evbuffer_add_buffer(evb, evb_tmp); + evbuffer_validate(evb); + + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF_STRICT); + tt_line_eq(" and a bad crlf\nand a good one"); + free(cp); + evbuffer_validate(evb); + + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF_STRICT); + tt_line_eq(""); + free(cp); + evbuffer_validate(evb); + + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF_STRICT); + tt_assert(!cp); + free(cp); + evbuffer_validate(evb); + evbuffer_add(evb, "\n", 1); + evbuffer_validate(evb); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF_STRICT); + tt_line_eq("More"); + free(cp); cp = NULL; + evbuffer_validate(evb); + tt_assert(evbuffer_get_length(evb) == 0); + + /* Test memory problem*/ + s = "one line\ntwo line\nblue line"; + evbuffer_add(evb_tmp, s, strlen(s)); + evbuffer_validate(evb); + evbuffer_add_buffer(evb, evb_tmp); + evbuffer_validate(evb); + + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_LF); + tt_line_eq("one line"); + free(cp); cp = NULL; + evbuffer_validate(evb); + + /* the next call to readline should fail */ +#ifndef EVENT__DISABLE_MM_REPLACEMENT + event_set_mem_functions(failing_malloc, realloc, free); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_LF); + tt_assert(cp == NULL); + evbuffer_validate(evb); + + /* now we should get the next line back */ + event_set_mem_functions(malloc, realloc, free); +#endif + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_LF); + tt_line_eq("two line"); + free(cp); cp = NULL; + evbuffer_validate(evb); + + end: + evbuffer_free(evb); + evbuffer_free(evb_tmp); + if (cp) free(cp); +} + +static void +test_evbuffer_search_eol(void *ptr) +{ + struct evbuffer *buf = evbuffer_new(); + struct evbuffer_ptr ptr1, ptr2; + const char *s; + size_t eol_len; + + s = "string! \r\n\r\nx\n"; + evbuffer_add(buf, s, strlen(s)); + eol_len = -1; + ptr1 = evbuffer_search_eol(buf, NULL, &eol_len, EVBUFFER_EOL_CRLF); + tt_int_op(ptr1.pos, ==, 8); + tt_int_op(eol_len, ==, 2); + + eol_len = -1; + ptr2 = evbuffer_search_eol(buf, &ptr1, &eol_len, EVBUFFER_EOL_CRLF); + tt_int_op(ptr2.pos, ==, 8); + tt_int_op(eol_len, ==, 2); + + evbuffer_ptr_set(buf, &ptr1, 1, EVBUFFER_PTR_ADD); + eol_len = -1; + ptr2 = evbuffer_search_eol(buf, &ptr1, &eol_len, EVBUFFER_EOL_CRLF); + tt_int_op(ptr2.pos, ==, 9); + tt_int_op(eol_len, ==, 1); + + eol_len = -1; + ptr2 = evbuffer_search_eol(buf, &ptr1, &eol_len, EVBUFFER_EOL_CRLF_STRICT); + tt_int_op(ptr2.pos, ==, 10); + tt_int_op(eol_len, ==, 2); + + eol_len = -1; + ptr1 = evbuffer_search_eol(buf, NULL, &eol_len, EVBUFFER_EOL_LF); + tt_int_op(ptr1.pos, ==, 9); + tt_int_op(eol_len, ==, 1); + + eol_len = -1; + ptr2 = evbuffer_search_eol(buf, &ptr1, &eol_len, EVBUFFER_EOL_LF); + tt_int_op(ptr2.pos, ==, 9); + tt_int_op(eol_len, ==, 1); + + evbuffer_ptr_set(buf, &ptr1, 1, EVBUFFER_PTR_ADD); + eol_len = -1; + ptr2 = evbuffer_search_eol(buf, &ptr1, &eol_len, EVBUFFER_EOL_LF); + tt_int_op(ptr2.pos, ==, 11); + tt_int_op(eol_len, ==, 1); + + tt_assert(evbuffer_ptr_set(buf, &ptr1, evbuffer_get_length(buf), EVBUFFER_PTR_SET) == 0); + eol_len = -1; + ptr2 = evbuffer_search_eol(buf, &ptr1, &eol_len, EVBUFFER_EOL_LF); + tt_int_op(ptr2.pos, ==, -1); + tt_int_op(eol_len, ==, 0); + +end: + evbuffer_free(buf); +} + +static void +test_evbuffer_iterative(void *ptr) +{ + struct evbuffer *buf = evbuffer_new(); + const char *abc = "abcdefghijklmnopqrstvuwxyzabcdefghijklmnopqrstvuwxyzabcdefghijklmnopqrstvuwxyzabcdefghijklmnopqrstvuwxyz"; + unsigned i, j, sum, n; + + sum = 0; + n = 0; + for (i = 0; i < 1000; ++i) { + for (j = 1; j < strlen(abc); ++j) { + char format[32]; + evutil_snprintf(format, sizeof(format), "%%%u.%us", j, j); + evbuffer_add_printf(buf, format, abc); + + /* Only check for rep violations every so often. + Walking over the whole list of chains can get + pretty expensive as it gets long. + */ + if ((n % 337) == 0) + evbuffer_validate(buf); + + sum += j; + n++; + } + } + evbuffer_validate(buf); + + tt_uint_op(sum, ==, evbuffer_get_length(buf)); + + { + size_t a,w,u; + a=w=u=0; + evbuffer_get_waste(buf, &a, &w, &u); + if (0) + printf("Allocated: %u.\nWasted: %u.\nUsed: %u.", + (unsigned)a, (unsigned)w, (unsigned)u); + tt_assert( ((double)w)/a < .125); + } + end: + evbuffer_free(buf); + +} + +static void +test_evbuffer_find(void *ptr) +{ + unsigned char* p; + const char* test1 = "1234567890\r\n"; + const char* test2 = "1234567890\r"; +#define EVBUFFER_INITIAL_LENGTH 256 + char test3[EVBUFFER_INITIAL_LENGTH]; + unsigned int i; + struct evbuffer * buf = evbuffer_new(); + + tt_assert(buf); + + /* make sure evbuffer_find doesn't match past the end of the buffer */ + evbuffer_add(buf, (unsigned char*)test1, strlen(test1)); + evbuffer_validate(buf); + evbuffer_drain(buf, strlen(test1)); + evbuffer_validate(buf); + evbuffer_add(buf, (unsigned char*)test2, strlen(test2)); + evbuffer_validate(buf); + p = evbuffer_find(buf, (unsigned char*)"\r\n", 2); + tt_want(p == NULL); + + /* + * drain the buffer and do another find; in r309 this would + * read past the allocated buffer causing a valgrind error. + */ + evbuffer_drain(buf, strlen(test2)); + evbuffer_validate(buf); + for (i = 0; i < EVBUFFER_INITIAL_LENGTH; ++i) + test3[i] = 'a'; + test3[EVBUFFER_INITIAL_LENGTH - 1] = 'x'; + evbuffer_add(buf, (unsigned char *)test3, EVBUFFER_INITIAL_LENGTH); + evbuffer_validate(buf); + p = evbuffer_find(buf, (unsigned char *)"xy", 2); + tt_want(p == NULL); + + /* simple test for match at end of allocated buffer */ + p = evbuffer_find(buf, (unsigned char *)"ax", 2); + tt_assert(p != NULL); + tt_want(strncmp((char*)p, "ax", 2) == 0); + +end: + if (buf) + evbuffer_free(buf); +} + +static void +test_evbuffer_ptr_set(void *ptr) +{ + struct evbuffer *buf = evbuffer_new(); + struct evbuffer_ptr pos; + struct evbuffer_iovec v[1]; + + tt_assert(buf); + + tt_int_op(evbuffer_get_length(buf), ==, 0); + + tt_assert(evbuffer_ptr_set(buf, &pos, 0, EVBUFFER_PTR_SET) == 0); + tt_assert(pos.pos == 0); + tt_assert(evbuffer_ptr_set(buf, &pos, 1, EVBUFFER_PTR_ADD) == -1); + tt_assert(pos.pos == -1); + tt_assert(evbuffer_ptr_set(buf, &pos, 1, EVBUFFER_PTR_SET) == -1); + tt_assert(pos.pos == -1); + + /* create some chains */ + evbuffer_reserve_space(buf, 5000, v, 1); + v[0].iov_len = 5000; + memset(v[0].iov_base, 1, v[0].iov_len); + evbuffer_commit_space(buf, v, 1); + evbuffer_validate(buf); + + evbuffer_reserve_space(buf, 4000, v, 1); + v[0].iov_len = 4000; + memset(v[0].iov_base, 2, v[0].iov_len); + evbuffer_commit_space(buf, v, 1); + + evbuffer_reserve_space(buf, 3000, v, 1); + v[0].iov_len = 3000; + memset(v[0].iov_base, 3, v[0].iov_len); + evbuffer_commit_space(buf, v, 1); + evbuffer_validate(buf); + + tt_int_op(evbuffer_get_length(buf), ==, 12000); + + tt_assert(evbuffer_ptr_set(buf, &pos, 13000, EVBUFFER_PTR_SET) == -1); + tt_assert(pos.pos == -1); + tt_assert(evbuffer_ptr_set(buf, &pos, 0, EVBUFFER_PTR_SET) == 0); + tt_assert(pos.pos == 0); + tt_assert(evbuffer_ptr_set(buf, &pos, 13000, EVBUFFER_PTR_ADD) == -1); + + tt_assert(evbuffer_ptr_set(buf, &pos, 0, EVBUFFER_PTR_SET) == 0); + tt_assert(pos.pos == 0); + tt_assert(evbuffer_ptr_set(buf, &pos, 10000, EVBUFFER_PTR_ADD) == 0); + tt_assert(pos.pos == 10000); + tt_assert(evbuffer_ptr_set(buf, &pos, 1000, EVBUFFER_PTR_ADD) == 0); + tt_assert(pos.pos == 11000); + tt_assert(evbuffer_ptr_set(buf, &pos, 1000, EVBUFFER_PTR_ADD) == 0); + tt_assert(pos.pos == 12000); + tt_assert(evbuffer_ptr_set(buf, &pos, 1000, EVBUFFER_PTR_ADD) == -1); + tt_assert(pos.pos == -1); + +end: + if (buf) + evbuffer_free(buf); +} + +static void +test_evbuffer_search(void *ptr) +{ + struct evbuffer *buf = evbuffer_new(); + struct evbuffer *tmp = evbuffer_new(); + struct evbuffer_ptr pos, end; + + tt_assert(buf); + tt_assert(tmp); + + pos = evbuffer_search(buf, "x", 1, NULL); + tt_int_op(pos.pos, ==, -1); + tt_assert(evbuffer_ptr_set(buf, &pos, 0, EVBUFFER_PTR_SET) == 0); + pos = evbuffer_search(buf, "x", 1, &pos); + tt_int_op(pos.pos, ==, -1); + tt_assert(evbuffer_ptr_set(buf, &pos, 0, EVBUFFER_PTR_SET) == 0); + pos = evbuffer_search_range(buf, "x", 1, &pos, &pos); + tt_int_op(pos.pos, ==, -1); + tt_assert(evbuffer_ptr_set(buf, &pos, 0, EVBUFFER_PTR_SET) == 0); + pos = evbuffer_search_range(buf, "x", 1, &pos, NULL); + tt_int_op(pos.pos, ==, -1); + + /* set up our chains */ + evbuffer_add_printf(tmp, "hello"); /* 5 chars */ + evbuffer_add_buffer(buf, tmp); + evbuffer_add_printf(tmp, "foo"); /* 3 chars */ + evbuffer_add_buffer(buf, tmp); + evbuffer_add_printf(tmp, "cat"); /* 3 chars */ + evbuffer_add_buffer(buf, tmp); + evbuffer_add_printf(tmp, "attack"); + evbuffer_add_buffer(buf, tmp); + + pos = evbuffer_search(buf, "attack", 6, NULL); + tt_int_op(pos.pos, ==, 11); + pos = evbuffer_search(buf, "attacker", 8, NULL); + tt_int_op(pos.pos, ==, -1); + + /* test continuing search */ + pos = evbuffer_search(buf, "oc", 2, NULL); + tt_int_op(pos.pos, ==, 7); + pos = evbuffer_search(buf, "cat", 3, &pos); + tt_int_op(pos.pos, ==, 8); + pos = evbuffer_search(buf, "tacking", 7, &pos); + tt_int_op(pos.pos, ==, -1); + + evbuffer_ptr_set(buf, &pos, 5, EVBUFFER_PTR_SET); + pos = evbuffer_search(buf, "foo", 3, &pos); + tt_int_op(pos.pos, ==, 5); + + evbuffer_ptr_set(buf, &pos, 2, EVBUFFER_PTR_ADD); + pos = evbuffer_search(buf, "tat", 3, &pos); + tt_int_op(pos.pos, ==, 10); + + /* test bounded search. */ + /* Set "end" to the first t in "attack". */ + evbuffer_ptr_set(buf, &end, 12, EVBUFFER_PTR_SET); + pos = evbuffer_search_range(buf, "foo", 3, NULL, &end); + tt_int_op(pos.pos, ==, 5); + pos = evbuffer_search_range(buf, "foocata", 7, NULL, &end); + tt_int_op(pos.pos, ==, 5); + pos = evbuffer_search_range(buf, "foocatat", 8, NULL, &end); + tt_int_op(pos.pos, ==, -1); + pos = evbuffer_search_range(buf, "ack", 3, NULL, &end); + tt_int_op(pos.pos, ==, -1); + + /* Set "end" after the last byte in the buffer. */ + tt_assert(evbuffer_ptr_set(buf, &end, 17, EVBUFFER_PTR_SET) == 0); + + pos = evbuffer_search_range(buf, "attack", 6, NULL, &end); + tt_int_op(pos.pos, ==, 11); + tt_assert(evbuffer_ptr_set(buf, &pos, 11, EVBUFFER_PTR_SET) == 0); + pos = evbuffer_search_range(buf, "attack", 6, &pos, &end); + tt_int_op(pos.pos, ==, 11); + tt_assert(evbuffer_ptr_set(buf, &pos, 17, EVBUFFER_PTR_SET) == 0); + pos = evbuffer_search_range(buf, "attack", 6, &pos, &end); + tt_int_op(pos.pos, ==, -1); + tt_assert(evbuffer_ptr_set(buf, &pos, 17, EVBUFFER_PTR_SET) == 0); + pos = evbuffer_search_range(buf, "attack", 6, &pos, NULL); + tt_int_op(pos.pos, ==, -1); + +end: + if (buf) + evbuffer_free(buf); + if (tmp) + evbuffer_free(tmp); +} + +static void +log_change_callback(struct evbuffer *buffer, + const struct evbuffer_cb_info *cbinfo, + void *arg) +{ + + size_t old_len = cbinfo->orig_size; + size_t new_len = old_len + cbinfo->n_added - cbinfo->n_deleted; + struct evbuffer *out = arg; + evbuffer_add_printf(out, "%lu->%lu; ", (unsigned long)old_len, + (unsigned long)new_len); +} +static void +self_draining_callback(struct evbuffer *evbuffer, size_t old_len, + size_t new_len, void *arg) +{ + if (new_len > old_len) + evbuffer_drain(evbuffer, new_len); +} + +static void +test_evbuffer_callbacks(void *ptr) +{ + struct evbuffer *buf = evbuffer_new(); + struct evbuffer *buf_out1 = evbuffer_new(); + struct evbuffer *buf_out2 = evbuffer_new(); + struct evbuffer_cb_entry *cb1, *cb2; + + tt_assert(buf); + tt_assert(buf_out1); + tt_assert(buf_out2); + + cb1 = evbuffer_add_cb(buf, log_change_callback, buf_out1); + cb2 = evbuffer_add_cb(buf, log_change_callback, buf_out2); + + /* Let's run through adding and deleting some stuff from the buffer + * and turning the callbacks on and off and removing them. The callback + * adds a summary of length changes to buf_out1/buf_out2 when called. */ + /* size: 0-> 36. */ + evbuffer_add_printf(buf, "The %d magic words are spotty pudding", 2); + evbuffer_validate(buf); + evbuffer_cb_clear_flags(buf, cb2, EVBUFFER_CB_ENABLED); + evbuffer_drain(buf, 10); /*36->26*/ + evbuffer_validate(buf); + evbuffer_prepend(buf, "Hello", 5);/*26->31*/ + evbuffer_cb_set_flags(buf, cb2, EVBUFFER_CB_ENABLED); + evbuffer_add_reference(buf, "Goodbye", 7, NULL, NULL); /*31->38*/ + evbuffer_remove_cb_entry(buf, cb1); + evbuffer_validate(buf); + evbuffer_drain(buf, evbuffer_get_length(buf)); /*38->0*/; + tt_assert(-1 == evbuffer_remove_cb(buf, log_change_callback, NULL)); + evbuffer_add(buf, "X", 1); /* 0->1 */ + tt_assert(!evbuffer_remove_cb(buf, log_change_callback, buf_out2)); + evbuffer_validate(buf); + + tt_str_op((const char *) evbuffer_pullup(buf_out1, -1), ==, + "0->36; 36->26; 26->31; 31->38; "); + tt_str_op((const char *) evbuffer_pullup(buf_out2, -1), ==, + "0->36; 31->38; 38->0; 0->1; "); + evbuffer_drain(buf_out1, evbuffer_get_length(buf_out1)); + evbuffer_drain(buf_out2, evbuffer_get_length(buf_out2)); + /* Let's test the obsolete buffer_setcb function too. */ + cb1 = evbuffer_add_cb(buf, log_change_callback, buf_out1); + tt_assert(cb1 != NULL); + cb2 = evbuffer_add_cb(buf, log_change_callback, buf_out2); + tt_assert(cb2 != NULL); + tt_int_op(evbuffer_setcb(buf, self_draining_callback, NULL), ==, 0); + evbuffer_add_printf(buf, "This should get drained right away."); + tt_uint_op(evbuffer_get_length(buf), ==, 0); + tt_uint_op(evbuffer_get_length(buf_out1), ==, 0); + tt_uint_op(evbuffer_get_length(buf_out2), ==, 0); + tt_int_op(evbuffer_setcb(buf, NULL, NULL), ==, 0); + evbuffer_add_printf(buf, "This will not."); + tt_str_op((const char *) evbuffer_pullup(buf, -1), ==, "This will not."); + evbuffer_validate(buf); + evbuffer_drain(buf, evbuffer_get_length(buf)); + evbuffer_validate(buf); +#if 0 + /* Now let's try a suspended callback. */ + cb1 = evbuffer_add_cb(buf, log_change_callback, buf_out1); + cb2 = evbuffer_add_cb(buf, log_change_callback, buf_out2); + evbuffer_cb_suspend(buf,cb2); + evbuffer_prepend(buf,"Hello world",11); /*0->11*/ + evbuffer_validate(buf); + evbuffer_cb_suspend(buf,cb1); + evbuffer_add(buf,"more",4); /* 11->15 */ + evbuffer_cb_unsuspend(buf,cb2); + evbuffer_drain(buf, 4); /* 15->11 */ + evbuffer_cb_unsuspend(buf,cb1); + evbuffer_drain(buf, evbuffer_get_length(buf)); /* 11->0 */ + + tt_str_op(evbuffer_pullup(buf_out1, -1), ==, + "0->11; 11->11; 11->0; "); + tt_str_op(evbuffer_pullup(buf_out2, -1), ==, + "0->15; 15->11; 11->0; "); +#endif + + /* the next call to readline should fail */ +#ifndef EVENT__DISABLE_MM_REPLACEMENT + event_set_mem_functions(failing_malloc, realloc, free); + tt_int_op(evbuffer_setcb(buf, self_draining_callback, NULL), ==, -1); + evbuffer_validate(buf); + event_set_mem_functions(malloc, realloc, free); +#endif + + end: + if (buf) + evbuffer_free(buf); + if (buf_out1) + evbuffer_free(buf_out1); + if (buf_out2) + evbuffer_free(buf_out2); +} + +static int ref_done_cb_called_count = 0; +static void *ref_done_cb_called_with = NULL; +static const void *ref_done_cb_called_with_data = NULL; +static size_t ref_done_cb_called_with_len = 0; +static void ref_done_cb(const void *data, size_t len, void *info) +{ + ++ref_done_cb_called_count; + ref_done_cb_called_with = info; + ref_done_cb_called_with_data = data; + ref_done_cb_called_with_len = len; +} + +static void +test_evbuffer_add_reference(void *ptr) +{ + const char chunk1[] = "If you have found the answer to such a problem"; + const char chunk2[] = "you ought to write it up for publication"; + /* -- Knuth's "Notes on the Exercises" from TAOCP */ + char tmp[16]; + size_t len1 = strlen(chunk1), len2=strlen(chunk2); + + struct evbuffer *buf1 = NULL, *buf2 = NULL; + + buf1 = evbuffer_new(); + tt_assert(buf1); + + evbuffer_add_reference(buf1, chunk1, len1, ref_done_cb, (void*)111); + evbuffer_add(buf1, ", ", 2); + evbuffer_add_reference(buf1, chunk2, len2, ref_done_cb, (void*)222); + tt_int_op(evbuffer_get_length(buf1), ==, len1+len2+2); + + /* Make sure we can drain a little from a reference. */ + tt_int_op(evbuffer_remove(buf1, tmp, 6), ==, 6); + tt_int_op(memcmp(tmp, "If you", 6), ==, 0); + tt_int_op(evbuffer_remove(buf1, tmp, 5), ==, 5); + tt_int_op(memcmp(tmp, " have", 5), ==, 0); + + /* Make sure that prepending does not meddle with immutable data */ + tt_int_op(evbuffer_prepend(buf1, "I have ", 7), ==, 0); + tt_int_op(memcmp(chunk1, "If you", 6), ==, 0); + evbuffer_validate(buf1); + + /* Make sure that when the chunk is over, the callback is invoked. */ + evbuffer_drain(buf1, 7); /* Remove prepended stuff. */ + evbuffer_drain(buf1, len1-11-1); /* remove all but one byte of chunk1 */ + tt_int_op(ref_done_cb_called_count, ==, 0); + evbuffer_remove(buf1, tmp, 1); + tt_int_op(tmp[0], ==, 'm'); + tt_assert(ref_done_cb_called_with == (void*)111); + tt_assert(ref_done_cb_called_with_data == chunk1); + tt_assert(ref_done_cb_called_with_len == len1); + tt_int_op(ref_done_cb_called_count, ==, 1); + evbuffer_validate(buf1); + + /* Drain some of the remaining chunk, then add it to another buffer */ + evbuffer_drain(buf1, 6); /* Remove the ", you ". */ + buf2 = evbuffer_new(); + tt_assert(buf2); + tt_int_op(ref_done_cb_called_count, ==, 1); + evbuffer_add(buf2, "I ", 2); + + evbuffer_add_buffer(buf2, buf1); + tt_int_op(ref_done_cb_called_count, ==, 1); + evbuffer_remove(buf2, tmp, 16); + tt_int_op(memcmp("I ought to write", tmp, 16), ==, 0); + evbuffer_drain(buf2, evbuffer_get_length(buf2)); + tt_int_op(ref_done_cb_called_count, ==, 2); + tt_assert(ref_done_cb_called_with == (void*)222); + evbuffer_validate(buf2); + + /* Now add more stuff to buf1 and make sure that it gets removed on + * free. */ + evbuffer_add(buf1, "You shake and shake the ", 24); + evbuffer_add_reference(buf1, "ketchup bottle", 14, ref_done_cb, + (void*)3333); + evbuffer_add(buf1, ". Nothing comes and then a lot'll.", 35); + evbuffer_free(buf1); + buf1 = NULL; + tt_int_op(ref_done_cb_called_count, ==, 3); + tt_assert(ref_done_cb_called_with == (void*)3333); + +end: + if (buf1) + evbuffer_free(buf1); + if (buf2) + evbuffer_free(buf2); +} + +static void +test_evbuffer_multicast(void *ptr) +{ + const char chunk1[] = "If you have found the answer to such a problem"; + const char chunk2[] = "you ought to write it up for publication"; + /* -- Knuth's "Notes on the Exercises" from TAOCP */ + char tmp[16]; + size_t len1 = strlen(chunk1), len2=strlen(chunk2); + + struct evbuffer *buf1 = NULL, *buf2 = NULL; + + buf1 = evbuffer_new(); + tt_assert(buf1); + + evbuffer_add(buf1, chunk1, len1); + evbuffer_add(buf1, ", ", 2); + evbuffer_add(buf1, chunk2, len2); + tt_int_op(evbuffer_get_length(buf1), ==, len1+len2+2); + + buf2 = evbuffer_new(); + tt_assert(buf2); + + tt_int_op(evbuffer_add_buffer_reference(buf2, buf1), ==, 0); + /* nested references are not allowed */ + tt_int_op(evbuffer_add_buffer_reference(buf2, buf2), ==, -1); + tt_int_op(evbuffer_add_buffer_reference(buf1, buf2), ==, -1); + + /* both buffers contain the same amount of data */ + tt_int_op(evbuffer_get_length(buf1), ==, evbuffer_get_length(buf1)); + + /* Make sure we can drain a little from the first buffer. */ + tt_int_op(evbuffer_remove(buf1, tmp, 6), ==, 6); + tt_int_op(memcmp(tmp, "If you", 6), ==, 0); + tt_int_op(evbuffer_remove(buf1, tmp, 5), ==, 5); + tt_int_op(memcmp(tmp, " have", 5), ==, 0); + + /* Make sure that prepending does not meddle with immutable data */ + tt_int_op(evbuffer_prepend(buf1, "I have ", 7), ==, 0); + tt_int_op(memcmp(chunk1, "If you", 6), ==, 0); + evbuffer_validate(buf1); + + /* Make sure we can drain a little from the second buffer. */ + tt_int_op(evbuffer_remove(buf2, tmp, 6), ==, 6); + tt_int_op(memcmp(tmp, "If you", 6), ==, 0); + tt_int_op(evbuffer_remove(buf2, tmp, 5), ==, 5); + tt_int_op(memcmp(tmp, " have", 5), ==, 0); + + /* Make sure that prepending does not meddle with immutable data */ + tt_int_op(evbuffer_prepend(buf2, "I have ", 7), ==, 0); + tt_int_op(memcmp(chunk1, "If you", 6), ==, 0); + evbuffer_validate(buf2); + + /* Make sure the data can be read from the second buffer when the first is freed */ + evbuffer_free(buf1); + buf1 = NULL; + + tt_int_op(evbuffer_remove(buf2, tmp, 6), ==, 6); + tt_int_op(memcmp(tmp, "I have", 6), ==, 0); + + tt_int_op(evbuffer_remove(buf2, tmp, 6), ==, 6); + tt_int_op(memcmp(tmp, " foun", 6), ==, 0); + +end: + if (buf1) + evbuffer_free(buf1); + if (buf2) + evbuffer_free(buf2); +} + +static void +test_evbuffer_multicast_drain(void *ptr) +{ + const char chunk1[] = "If you have found the answer to such a problem"; + const char chunk2[] = "you ought to write it up for publication"; + /* -- Knuth's "Notes on the Exercises" from TAOCP */ + size_t len1 = strlen(chunk1), len2=strlen(chunk2); + + struct evbuffer *buf1 = NULL, *buf2 = NULL; + + buf1 = evbuffer_new(); + tt_assert(buf1); + + evbuffer_add(buf1, chunk1, len1); + evbuffer_add(buf1, ", ", 2); + evbuffer_add(buf1, chunk2, len2); + tt_int_op(evbuffer_get_length(buf1), ==, len1+len2+2); + + buf2 = evbuffer_new(); + tt_assert(buf2); + + tt_int_op(evbuffer_add_buffer_reference(buf2, buf1), ==, 0); + tt_int_op(evbuffer_get_length(buf2), ==, len1+len2+2); + tt_int_op(evbuffer_drain(buf1, evbuffer_get_length(buf1)), ==, 0); + tt_int_op(evbuffer_get_length(buf2), ==, len1+len2+2); + tt_int_op(evbuffer_drain(buf2, evbuffer_get_length(buf2)), ==, 0); + evbuffer_validate(buf1); + evbuffer_validate(buf2); + +end: + if (buf1) + evbuffer_free(buf1); + if (buf2) + evbuffer_free(buf2); +} + +static void +check_prepend(struct evbuffer *buffer, + const struct evbuffer_cb_info *cbinfo, + void *arg) +{ + tt_int_op(cbinfo->orig_size, ==, 3); + tt_int_op(cbinfo->n_added, ==, 8096); + tt_int_op(cbinfo->n_deleted, ==, 0); +end: + ; +} +/* Some cases that we didn't get in test_evbuffer() above, for more coverage. */ +static void +test_evbuffer_prepend(void *ptr) +{ + struct evbuffer *buf1 = NULL, *buf2 = NULL; + char tmp[128], *buffer = malloc(8096); + int n; + + buf1 = evbuffer_new(); + tt_assert(buf1); + + /* Case 0: The evbuffer is entirely empty. */ + evbuffer_prepend(buf1, "This string has 29 characters", 29); + evbuffer_validate(buf1); + + /* Case 1: Prepend goes entirely in new chunk. */ + evbuffer_prepend(buf1, "Short.", 6); + evbuffer_validate(buf1); + + /* Case 2: prepend goes entirely in first chunk. */ + evbuffer_drain(buf1, 6+11); + evbuffer_prepend(buf1, "it", 2); + evbuffer_validate(buf1); + tt_assert(!memcmp(buf1->first->buffer+buf1->first->misalign, + "it has", 6)); + + /* Case 3: prepend is split over multiple chunks. */ + evbuffer_prepend(buf1, "It is no longer true to say ", 28); + evbuffer_validate(buf1); + n = evbuffer_remove(buf1, tmp, sizeof(tmp)-1); + tt_int_op(n, >=, 0); + tmp[n]='\0'; + tt_str_op(tmp,==,"It is no longer true to say it has 29 characters"); + + buf2 = evbuffer_new(); + tt_assert(buf2); + + /* Case 4: prepend a buffer to an empty buffer. */ + n = 999; + evbuffer_add_printf(buf1, "Here is string %d. ", n++); + evbuffer_prepend_buffer(buf2, buf1); + evbuffer_validate(buf2); + + /* Case 5: prepend a buffer to a nonempty buffer. */ + evbuffer_add_printf(buf1, "Here is string %d. ", n++); + evbuffer_prepend_buffer(buf2, buf1); + evbuffer_validate(buf2); + evbuffer_validate(buf1); + n = evbuffer_remove(buf2, tmp, sizeof(tmp)-1); + tt_int_op(n, >=, 0); + tmp[n]='\0'; + tt_str_op(tmp,==,"Here is string 1000. Here is string 999. "); + + /* Case 5: evbuffer_prepend() will need a new buffer, with callbacks */ + memset(buffer, 'A', 8096); + evbuffer_free(buf2); + buf2 = evbuffer_new(); + tt_assert(buf2); + evbuffer_prepend(buf2, "foo", 3); + evbuffer_add_cb(buf2, check_prepend, NULL); + evbuffer_prepend(buf2, buffer, 8096); + evbuffer_remove_cb(buf2, check_prepend, NULL); + evbuffer_validate(buf2); + tt_nstr_op(8096,(char *)evbuffer_pullup(buf2, 8096),==,buffer); + evbuffer_drain(buf2, 8096); + tt_nstr_op(3,(char *)evbuffer_pullup(buf2, 3),==,"foo"); + evbuffer_drain(buf2, 3); + +end: + free(buffer); + if (buf1) + evbuffer_free(buf1); + if (buf2) + evbuffer_free(buf2); + +} + +static void +test_evbuffer_empty_reference_prepend(void *ptr) +{ + struct evbuffer *buf = NULL; + + buf = evbuffer_new(); + tt_assert(buf); + + /** empty chain could leave invalid last_with_datap */ + evbuffer_add_reference(buf, "", 0, NULL, NULL); + evbuffer_validate(buf); + evbuffer_prepend(buf, "foo", 3); + + evbuffer_validate(buf); + tt_assert(!strncmp((char *)evbuffer_pullup(buf, -1), "foo", 3)); + evbuffer_validate(buf); + +end: + if (buf) + evbuffer_free(buf); +} +static void +test_evbuffer_empty_reference_prepend_buffer(void *ptr) +{ + struct evbuffer *buf1 = NULL, *buf2 = NULL; + + buf1 = evbuffer_new(); + tt_assert(buf1); + buf2 = evbuffer_new(); + tt_assert(buf2); + + /** empty chain could leave invalid last_with_datap */ + evbuffer_add_reference(buf1, "", 0, NULL, NULL); + evbuffer_validate(buf1); + evbuffer_add(buf2, "foo", 3); + evbuffer_validate(buf2); + evbuffer_prepend_buffer(buf2, buf1); + evbuffer_validate(buf2); + + tt_assert(!strncmp((char *)evbuffer_pullup(buf2, -1), "foo", 3)); + evbuffer_validate(buf2); + + tt_assert(evbuffer_pullup(buf1, -1) == NULL); + evbuffer_validate(buf2); + +end: + if (buf1) + evbuffer_free(buf1); + if (buf2) + evbuffer_free(buf2); +} + +static void +test_evbuffer_peek_first_gt(void *info) +{ + struct evbuffer *buf = NULL, *tmp_buf = NULL; + struct evbuffer_ptr ptr; + struct evbuffer_iovec v[2]; + + buf = evbuffer_new(); + tmp_buf = evbuffer_new(); + evbuffer_add_printf(tmp_buf, "Contents of chunk 100\n"); + evbuffer_add_buffer(buf, tmp_buf); + evbuffer_add_printf(tmp_buf, "Contents of chunk 1\n"); + evbuffer_add_buffer(buf, tmp_buf); + + evbuffer_ptr_set(buf, &ptr, 0, EVBUFFER_PTR_SET); + + /** The only case that matters*/ + tt_int_op(evbuffer_peek(buf, -1, &ptr, NULL, 0), ==, 2); + /** Just in case */ + tt_int_op(evbuffer_peek(buf, -1, &ptr, v, 2), ==, 2); + + evbuffer_ptr_set(buf, &ptr, 20, EVBUFFER_PTR_ADD); + tt_int_op(evbuffer_peek(buf, -1, &ptr, NULL, 0), ==, 2); + tt_int_op(evbuffer_peek(buf, -1, &ptr, v, 2), ==, 2); + tt_int_op(evbuffer_peek(buf, 2, &ptr, NULL, 0), ==, 1); + tt_int_op(evbuffer_peek(buf, 2, &ptr, v, 2), ==, 1); + tt_int_op(evbuffer_peek(buf, 3, &ptr, NULL, 0), ==, 2); + tt_int_op(evbuffer_peek(buf, 3, &ptr, v, 2), ==, 2); + +end: + if (buf) + evbuffer_free(buf); + if (tmp_buf) + evbuffer_free(tmp_buf); +} + +static void +test_evbuffer_peek(void *info) +{ + struct evbuffer *buf = NULL, *tmp_buf = NULL; + int i; + struct evbuffer_iovec v[20]; + struct evbuffer_ptr ptr; + +#define tt_iov_eq(v, s) \ + tt_int_op((v)->iov_len, ==, strlen(s)); \ + tt_assert(!memcmp((v)->iov_base, (s), strlen(s))) + + /* Let's make a very fragmented buffer. */ + buf = evbuffer_new(); + tmp_buf = evbuffer_new(); + for (i = 0; i < 16; ++i) { + evbuffer_add_printf(tmp_buf, "Contents of chunk [%d]\n", i); + evbuffer_add_buffer(buf, tmp_buf); + } + + /* How many chunks do we need for everything? */ + i = evbuffer_peek(buf, -1, NULL, NULL, 0); + tt_int_op(i, ==, 16); + + /* Simple peek: get everything. */ + i = evbuffer_peek(buf, -1, NULL, v, 20); + tt_int_op(i, ==, 16); /* we used only 16 chunks. */ + tt_iov_eq(&v[0], "Contents of chunk [0]\n"); + tt_iov_eq(&v[3], "Contents of chunk [3]\n"); + tt_iov_eq(&v[12], "Contents of chunk [12]\n"); + tt_iov_eq(&v[15], "Contents of chunk [15]\n"); + + /* Just get one chunk worth. */ + memset(v, 0, sizeof(v)); + i = evbuffer_peek(buf, -1, NULL, v, 1); + tt_int_op(i, ==, 1); + tt_iov_eq(&v[0], "Contents of chunk [0]\n"); + tt_assert(v[1].iov_base == NULL); + + /* Suppose we want at least the first 40 bytes. */ + memset(v, 0, sizeof(v)); + i = evbuffer_peek(buf, 40, NULL, v, 16); + tt_int_op(i, ==, 2); + tt_iov_eq(&v[0], "Contents of chunk [0]\n"); + tt_iov_eq(&v[1], "Contents of chunk [1]\n"); + tt_assert(v[2].iov_base == NULL); + + /* How many chunks do we need for 100 bytes? */ + memset(v, 0, sizeof(v)); + i = evbuffer_peek(buf, 100, NULL, NULL, 0); + tt_int_op(i, ==, 5); + tt_assert(v[0].iov_base == NULL); + + /* Now we ask for more bytes than we provide chunks for */ + memset(v, 0, sizeof(v)); + i = evbuffer_peek(buf, 60, NULL, v, 1); + tt_int_op(i, ==, 3); + tt_iov_eq(&v[0], "Contents of chunk [0]\n"); + tt_assert(v[1].iov_base == NULL); + + /* Now we ask for more bytes than the buffer has. */ + memset(v, 0, sizeof(v)); + i = evbuffer_peek(buf, 65536, NULL, v, 20); + tt_int_op(i, ==, 16); /* we used only 16 chunks. */ + tt_iov_eq(&v[0], "Contents of chunk [0]\n"); + tt_iov_eq(&v[3], "Contents of chunk [3]\n"); + tt_iov_eq(&v[12], "Contents of chunk [12]\n"); + tt_iov_eq(&v[15], "Contents of chunk [15]\n"); + tt_assert(v[16].iov_base == NULL); + + /* What happens if we try an empty buffer? */ + memset(v, 0, sizeof(v)); + i = evbuffer_peek(tmp_buf, -1, NULL, v, 20); + tt_int_op(i, ==, 0); + tt_assert(v[0].iov_base == NULL); + memset(v, 0, sizeof(v)); + i = evbuffer_peek(tmp_buf, 50, NULL, v, 20); + tt_int_op(i, ==, 0); + tt_assert(v[0].iov_base == NULL); + + /* Okay, now time to have fun with pointers. */ + memset(v, 0, sizeof(v)); + evbuffer_ptr_set(buf, &ptr, 30, EVBUFFER_PTR_SET); + i = evbuffer_peek(buf, 50, &ptr, v, 20); + tt_int_op(i, ==, 3); + tt_iov_eq(&v[0], " of chunk [1]\n"); + tt_iov_eq(&v[1], "Contents of chunk [2]\n"); + tt_iov_eq(&v[2], "Contents of chunk [3]\n"); /*more than we asked for*/ + + /* advance to the start of another chain. */ + memset(v, 0, sizeof(v)); + evbuffer_ptr_set(buf, &ptr, 14, EVBUFFER_PTR_ADD); + i = evbuffer_peek(buf, 44, &ptr, v, 20); + tt_int_op(i, ==, 2); + tt_iov_eq(&v[0], "Contents of chunk [2]\n"); + tt_iov_eq(&v[1], "Contents of chunk [3]\n"); /*more than we asked for*/ + + /* peek at the end of the buffer */ + memset(v, 0, sizeof(v)); + tt_assert(evbuffer_ptr_set(buf, &ptr, evbuffer_get_length(buf), EVBUFFER_PTR_SET) == 0); + i = evbuffer_peek(buf, 44, &ptr, v, 20); + tt_int_op(i, ==, 0); + tt_assert(v[0].iov_base == NULL); + +end: + if (buf) + evbuffer_free(buf); + if (tmp_buf) + evbuffer_free(tmp_buf); +} + +/* Check whether evbuffer freezing works right. This is called twice, + once with the argument "start" and once with the argument "end". + When we test "start", we freeze the start of an evbuffer and make sure + that modifying the start of the buffer doesn't work. When we test + "end", we freeze the end of an evbuffer and make sure that modifying + the end of the buffer doesn't work. + */ +static void +test_evbuffer_freeze(void *ptr) +{ + struct basic_test_data *testdata = ptr; + evutil_socket_t *pair = testdata->pair; + struct evbuffer *buf = NULL, *buf_two = NULL, *tmp_buf = NULL; + const char string[] = /* Year's End, Richard Wilbur */ + "I've known the wind by water banks to shake\n" + "The late leaves down, which frozen where they fell\n" + "And held in ice as dancers in a spell\n" + "Fluttered all winter long into a lake..."; + const int start = !strcmp(testdata->setup_data, "start"); + const char tmpfilecontent[] = "file_freeze_test_file"; + char *cp; + char charbuf[128]; + char *tmpfilename = NULL; + int fd = -1; + int r; + size_t orig_length, len; + struct evbuffer_iovec v[1]; + + if (!start) + tt_str_op(testdata->setup_data, ==, "end"); + + buf = evbuffer_new(); + buf_two = evbuffer_new(); + tmp_buf = evbuffer_new(); + tt_assert(tmp_buf); + + evbuffer_add(buf, string, strlen(string)); + evbuffer_add(buf_two, "abc", 3); + evbuffer_add(tmp_buf, "xyz", 3); + evbuffer_freeze(buf, start); /* Freeze the start or the end.*/ + evbuffer_freeze(buf_two, start); + +#define FREEZE_EQ(a, startcase, endcase) \ + do { \ + if (start) { \ + tt_int_op((a), ==, (startcase)); \ + } else { \ + tt_int_op((a), ==, (endcase)); \ + } \ + } while (0) + + + orig_length = evbuffer_get_length(buf); + + /* These functions all manipulate the end of buf. */ + r = evbuffer_add(buf, "abc", 0); + FREEZE_EQ(r, 0, -1); + r = evbuffer_reserve_space(buf, 10, v, 1); + FREEZE_EQ(r, 1, -1); + if (r == 1) { + memset(v[0].iov_base, 'X', 10); + v[0].iov_len = 10; + } + r = evbuffer_commit_space(buf, v, 1); + FREEZE_EQ(r, 0, -1); + r = evbuffer_add_reference(buf, string, 5, NULL, NULL); + FREEZE_EQ(r, 0, -1); + r = evbuffer_add_printf(buf, "Hello %s", "world"); + FREEZE_EQ(r, 11, -1); + + r = evbuffer_add_buffer(buf, tmp_buf); + FREEZE_EQ(r, 0, -1); + len = strlen(tmpfilecontent); + fd = regress_make_tmpfile(tmpfilecontent, len, &tmpfilename); + r = evbuffer_add_file(buf, fd, 0, len); + FREEZE_EQ(r, 0, -1); + + if (start) + evbuffer_add(tmp_buf, "xyz", 3); + + tt_assert(evbuffer_get_length(tmp_buf)); + len = evbuffer_get_length(tmp_buf); + evbuffer_write(tmp_buf, pair[0]); + r = evbuffer_read(buf, pair[1], -1); + FREEZE_EQ(r, len, -1); + + if (!start) + tt_int_op(orig_length, ==, evbuffer_get_length(buf)); + + orig_length = evbuffer_get_length(buf); + + /* These functions all manipulate the start of buf. */ + r = evbuffer_remove(buf, charbuf, 1); + FREEZE_EQ(r, -1, 1); + r = evbuffer_drain(buf, 3); + FREEZE_EQ(r, -1, 0); + r = evbuffer_prepend(buf, "dummy", 5); + FREEZE_EQ(r, -1, 0); + cp = evbuffer_readln(buf, NULL, EVBUFFER_EOL_LF); + FREEZE_EQ(cp==NULL, 1, 0); + if (cp) + free(cp); + + evbuffer_add(tmp_buf, "xyz", 3); + tt_assert(evbuffer_get_length(tmp_buf)); + r = evbuffer_remove_buffer(buf, tmp_buf, 3); + FREEZE_EQ(r, -1, 3); + r = evbuffer_drain(buf, 3); + FREEZE_EQ(r, -1, 0); + r = evbuffer_prepend_buffer(buf, tmp_buf); + FREEZE_EQ(r, -1, 0); + + len = evbuffer_get_length(buf); + r = evbuffer_write(buf, pair[0]); + evbuffer_read(tmp_buf, pair[1], -1); + FREEZE_EQ(r, -1, len); + len = evbuffer_get_length(buf_two); + r = evbuffer_write_atmost(buf_two, pair[0], -1); + evbuffer_read(tmp_buf, pair[1], -1); + FREEZE_EQ(r, -1, len); + + if (start) + tt_int_op(orig_length, ==, evbuffer_get_length(buf)); + +end: + if (buf) + evbuffer_free(buf); + + if (buf_two) + evbuffer_free(buf_two); + + if (tmp_buf) + evbuffer_free(tmp_buf); + + if (tmpfilename) { + unlink(tmpfilename); + free(tmpfilename); + } +} + +static void +test_evbuffer_add_iovec(void * ptr) +{ + struct evbuffer * buf = NULL; + struct evbuffer_iovec vec[4]; + const char * data[] = { + "Guilt resembles a sword with two edges.", + "On the one hand, it cuts for Justice, imposing practical morality upon those who fear it.", + "Conscience does not always adhere to rational judgment.", + "Guilt is always a self-imposed burden, but it is not always rightly imposed." + /* -- R.A. Salvatore, _Sojurn_ */ + }; + size_t expected_length = 0; + size_t returned_length = 0; + int i; + + buf = evbuffer_new(); + + tt_assert(buf); + + for (i = 0; i < 4; i++) { + vec[i].iov_len = strlen(data[i]); + vec[i].iov_base = (char*) data[i]; + expected_length += vec[i].iov_len; + } + + returned_length = evbuffer_add_iovec(buf, vec, 4); + + tt_int_op(returned_length, ==, evbuffer_get_length(buf)); + tt_int_op(evbuffer_get_length(buf), ==, expected_length); + + for (i = 0; i < 4; i++) { + char charbuf[1024]; + + memset(charbuf, 0, 1024); + evbuffer_remove(buf, charbuf, strlen(data[i])); + tt_assert(strcmp(charbuf, data[i]) == 0); + } + + tt_assert(evbuffer_get_length(buf) == 0); +end: + if (buf) { + evbuffer_free(buf); + } +} + +static void +test_evbuffer_copyout(void *dummy) +{ + const char string[] = + "Still they skirmish to and fro, men my messmates on the snow " + "When we headed off the aurochs turn for turn; " + "When the rich Allobrogenses never kept amanuenses, " + "And our only plots were piled in lakes at Berne."; + /* -- Kipling, "In The Neolithic Age" */ + char tmp[1024]; + struct evbuffer_ptr ptr; + struct evbuffer *buf; + + (void)dummy; + + buf = evbuffer_new(); + tt_assert(buf); + + tt_int_op(strlen(string), ==, 206); + + /* Ensure separate chains */ + evbuffer_add_reference(buf, string, 80, no_cleanup, NULL); + evbuffer_add_reference(buf, string+80, 80, no_cleanup, NULL); + evbuffer_add(buf, string+160, strlen(string)-160); + + tt_int_op(206, ==, evbuffer_get_length(buf)); + + /* First, let's test plain old copyout. */ + + /* Copy a little from the beginning. */ + tt_int_op(10, ==, evbuffer_copyout(buf, tmp, 10)); + tt_int_op(0, ==, memcmp(tmp, "Still they", 10)); + + /* Now copy more than a little from the beginning */ + memset(tmp, 0, sizeof(tmp)); + tt_int_op(100, ==, evbuffer_copyout(buf, tmp, 100)); + tt_int_op(0, ==, memcmp(tmp, string, 100)); + + /* Copy too much; ensure truncation. */ + memset(tmp, 0, sizeof(tmp)); + tt_int_op(206, ==, evbuffer_copyout(buf, tmp, 230)); + tt_int_op(0, ==, memcmp(tmp, string, 206)); + + /* That was supposed to be nondestructive, btw */ + tt_int_op(206, ==, evbuffer_get_length(buf)); + + /* Now it's time to test copyout_from! First, let's start in the + * first chain. */ + evbuffer_ptr_set(buf, &ptr, 15, EVBUFFER_PTR_SET); + memset(tmp, 0, sizeof(tmp)); + tt_int_op(10, ==, evbuffer_copyout_from(buf, &ptr, tmp, 10)); + tt_int_op(0, ==, memcmp(tmp, "mish to an", 10)); + + /* Right up to the end of the first chain */ + memset(tmp, 0, sizeof(tmp)); + tt_int_op(65, ==, evbuffer_copyout_from(buf, &ptr, tmp, 65)); + tt_int_op(0, ==, memcmp(tmp, string+15, 65)); + + /* Span into the second chain */ + memset(tmp, 0, sizeof(tmp)); + tt_int_op(90, ==, evbuffer_copyout_from(buf, &ptr, tmp, 90)); + tt_int_op(0, ==, memcmp(tmp, string+15, 90)); + + /* Span into the third chain */ + memset(tmp, 0, sizeof(tmp)); + tt_int_op(160, ==, evbuffer_copyout_from(buf, &ptr, tmp, 160)); + tt_int_op(0, ==, memcmp(tmp, string+15, 160)); + + /* Overrun */ + memset(tmp, 0, sizeof(tmp)); + tt_int_op(206-15, ==, evbuffer_copyout_from(buf, &ptr, tmp, 999)); + tt_int_op(0, ==, memcmp(tmp, string+15, 206-15)); + + /* That was supposed to be nondestructive, too */ + tt_int_op(206, ==, evbuffer_get_length(buf)); + +end: + if (buf) + evbuffer_free(buf); +} + +static void * +setup_passthrough(const struct testcase_t *testcase) +{ + return testcase->setup_data; +} +static int +cleanup_passthrough(const struct testcase_t *testcase, void *ptr) +{ + (void) ptr; + return 1; +} + +static const struct testcase_setup_t nil_setup = { + setup_passthrough, + cleanup_passthrough +}; + +struct testcase_t evbuffer_testcases[] = { + { "evbuffer", test_evbuffer, 0, NULL, NULL }, + { "remove_buffer_with_empty", test_evbuffer_remove_buffer_with_empty, 0, NULL, NULL }, + { "remove_buffer_with_empty2", test_evbuffer_remove_buffer_with_empty2, 0, NULL, NULL }, + { "remove_buffer_with_empty3", test_evbuffer_remove_buffer_with_empty3, 0, NULL, NULL }, + { "remove_buffer_with_empty_front", test_evbuffer_remove_buffer_with_empty_front, 0, NULL, NULL }, + { "remove_buffer_adjust_last_with_datap_with_empty", + test_evbuffer_remove_buffer_adjust_last_with_datap_with_empty, 0, NULL, NULL }, + { "add_buffer_with_empty", test_evbuffer_add_buffer_with_empty, 0, NULL, NULL }, + { "add_buffer_with_empty2", test_evbuffer_add_buffer_with_empty2, 0, NULL, NULL }, + { "reserve2", test_evbuffer_reserve2, 0, NULL, NULL }, + { "reserve_many", test_evbuffer_reserve_many, 0, NULL, NULL }, + { "reserve_many2", test_evbuffer_reserve_many, 0, &nil_setup, (void*)"add" }, + { "reserve_many3", test_evbuffer_reserve_many, 0, &nil_setup, (void*)"fill" }, + { "reserve_with_empty", test_evbuffer_reserve_with_empty, 0, NULL, NULL }, + { "reserve_invalid_last_with_datap", test_evbuffer_reserve_invalid_last_with_datap, TT_FORK, NULL, NULL }, + { "expand", test_evbuffer_expand, 0, NULL, NULL }, + { "expand_overflow", test_evbuffer_expand_overflow, 0, NULL, NULL }, + { "add1", test_evbuffer_add1, 0, NULL, NULL }, + { "add2", test_evbuffer_add2, 0, NULL, NULL }, + { "reference", test_evbuffer_reference, 0, NULL, NULL }, + { "reference2", test_evbuffer_reference2, 0, NULL, NULL }, + { "iterative", test_evbuffer_iterative, 0, NULL, NULL }, + { "readln", test_evbuffer_readln, TT_NO_LOGS, &basic_setup, NULL }, + { "search_eol", test_evbuffer_search_eol, 0, NULL, NULL }, + { "find", test_evbuffer_find, 0, NULL, NULL }, + { "ptr_set", test_evbuffer_ptr_set, 0, NULL, NULL }, + { "search", test_evbuffer_search, 0, NULL, NULL }, + { "callbacks", test_evbuffer_callbacks, 0, NULL, NULL }, + { "add_reference", test_evbuffer_add_reference, 0, NULL, NULL }, + { "multicast", test_evbuffer_multicast, 0, NULL, NULL }, + { "multicast_drain", test_evbuffer_multicast_drain, 0, NULL, NULL }, + { "prepend", test_evbuffer_prepend, TT_FORK, NULL, NULL }, + { "empty_reference_prepend", test_evbuffer_empty_reference_prepend, TT_FORK, NULL, NULL }, + { "empty_reference_prepend_buffer", test_evbuffer_empty_reference_prepend_buffer, TT_FORK, NULL, NULL }, + { "peek", test_evbuffer_peek, 0, NULL, NULL }, + { "peek_first_gt", test_evbuffer_peek_first_gt, 0, NULL, NULL }, + { "freeze_start", test_evbuffer_freeze, TT_NEED_SOCKETPAIR, &basic_setup, (void*)"start" }, + { "freeze_end", test_evbuffer_freeze, TT_NEED_SOCKETPAIR, &basic_setup, (void*)"end" }, + { "add_iovec", test_evbuffer_add_iovec, 0, NULL, NULL}, + { "copyout", test_evbuffer_copyout, 0, NULL, NULL}, + { "file_segment_add_cleanup_cb", test_evbuffer_file_segment_add_cleanup_cb, 0, NULL, NULL }, + { "pullup_with_empty", test_evbuffer_pullup_with_empty, 0, NULL, NULL }, + +#define ADDFILE_TEST(name, parameters) \ + { name, test_evbuffer_add_file, TT_FORK|TT_NEED_BASE, \ + &basic_setup, (void*)(parameters) } + +#define ADDFILE_TEST_GROUP(name, parameters) \ + ADDFILE_TEST(name "_sendfile", "sendfile " parameters), \ + ADDFILE_TEST(name "_mmap", "mmap " parameters), \ + ADDFILE_TEST(name "_linear", "linear " parameters) + + ADDFILE_TEST_GROUP("add_file", ""), + ADDFILE_TEST("add_file_nosegment", "default nosegment"), + + ADDFILE_TEST_GROUP("add_big_file", "bigfile"), + ADDFILE_TEST("add_big_file_nosegment", "default nosegment bigfile"), + + ADDFILE_TEST_GROUP("add_file_offset", "bigfile map_offset"), + ADDFILE_TEST("add_file_offset_nosegment", + "default nosegment bigfile map_offset"), + + ADDFILE_TEST_GROUP("add_file_offset2", "bigfile offset_in_segment"), + + ADDFILE_TEST_GROUP("add_file_offset3", + "bigfile offset_in_segment map_offset"), + + END_OF_TESTCASES +}; diff --git a/ipc/chromium/src/third_party/libevent/test/regress_bufferevent.c b/ipc/chromium/src/third_party/libevent/test/regress_bufferevent.c new file mode 100644 index 0000000000..c276a0e5d1 --- /dev/null +++ b/ipc/chromium/src/third_party/libevent/test/regress_bufferevent.c @@ -0,0 +1,1467 @@ +/* + * Copyright (c) 2003-2007 Niels Provos + * Copyright (c) 2007-2012 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "util-internal.h" + +/* The old tests here need assertions to work. */ +#undef NDEBUG + +/** + * - clang supports __has_feature + * - gcc supports __SANITIZE_ADDRESS__ + * + * Let's set __SANITIZE_ADDRESS__ if __has_feature(address_sanitizer) + */ +#ifndef __has_feature +#define __has_feature(x) 0 +#endif +#if !defined(__SANITIZE_ADDRESS__) && __has_feature(address_sanitizer) +#define __SANITIZE_ADDRESS__ +#endif + +#ifdef _WIN32 +#include +#include +#endif + +#include "event2/event-config.h" + +#include +#include +#ifdef EVENT__HAVE_SYS_TIME_H +#include +#endif +#include +#ifndef _WIN32 +#include +#include +#include +#include +#include +#include +#endif +#include +#include +#include +#include +#include +#include +#include + +#ifdef EVENT__HAVE_ARPA_INET_H +#include +#endif + +#include "event2/event-config.h" +#include "event2/event.h" +#include "event2/event_struct.h" +#include "event2/event_compat.h" +#include "event2/tag.h" +#include "event2/buffer.h" +#include "event2/bufferevent.h" +#include "event2/bufferevent_compat.h" +#include "event2/bufferevent_struct.h" +#include "event2/listener.h" +#include "event2/util.h" + +#include "bufferevent-internal.h" +#include "evthread-internal.h" +#include "util-internal.h" +#ifdef _WIN32 +#include "iocp-internal.h" +#endif + +#include "regress.h" +#include "regress_testutils.h" + +/* + * simple bufferevent test + */ + +static void +readcb(struct bufferevent *bev, void *arg) +{ + if (evbuffer_get_length(bev->input) == 8333) { + struct evbuffer *evbuf = evbuffer_new(); + assert(evbuf != NULL); + + /* gratuitous test of bufferevent_read_buffer */ + bufferevent_read_buffer(bev, evbuf); + + bufferevent_disable(bev, EV_READ); + + if (evbuffer_get_length(evbuf) == 8333) { + test_ok++; + } + + evbuffer_free(evbuf); + } +} + +static void +writecb(struct bufferevent *bev, void *arg) +{ + if (evbuffer_get_length(bev->output) == 0) { + test_ok++; + } +} + +static void +errorcb(struct bufferevent *bev, short what, void *arg) +{ + test_ok = -2; +} + +static void +test_bufferevent_impl(int use_pair, int flush) +{ + struct bufferevent *bev1 = NULL, *bev2 = NULL; + char buffer[8333]; + int i; + int expected = 2; + + if (use_pair) { + struct bufferevent *pair[2]; + tt_assert(0 == bufferevent_pair_new(NULL, 0, pair)); + bev1 = pair[0]; + bev2 = pair[1]; + bufferevent_setcb(bev1, readcb, writecb, errorcb, bev1); + bufferevent_setcb(bev2, readcb, writecb, errorcb, NULL); + tt_fd_op(bufferevent_getfd(bev1), ==, EVUTIL_INVALID_SOCKET); + tt_ptr_op(bufferevent_get_underlying(bev1), ==, NULL); + tt_ptr_op(bufferevent_pair_get_partner(bev1), ==, bev2); + tt_ptr_op(bufferevent_pair_get_partner(bev2), ==, bev1); + } else { + bev1 = bufferevent_new(pair[0], readcb, writecb, errorcb, NULL); + bev2 = bufferevent_new(pair[1], readcb, writecb, errorcb, NULL); + tt_fd_op(bufferevent_getfd(bev1), ==, pair[0]); + tt_ptr_op(bufferevent_get_underlying(bev1), ==, NULL); + tt_ptr_op(bufferevent_pair_get_partner(bev1), ==, NULL); + tt_ptr_op(bufferevent_pair_get_partner(bev2), ==, NULL); + } + + { + /* Test getcb. */ + bufferevent_data_cb r, w; + bufferevent_event_cb e; + void *a; + bufferevent_getcb(bev1, &r, &w, &e, &a); + tt_ptr_op(r, ==, readcb); + tt_ptr_op(w, ==, writecb); + tt_ptr_op(e, ==, errorcb); + tt_ptr_op(a, ==, use_pair ? bev1 : NULL); + } + + bufferevent_disable(bev1, EV_READ); + bufferevent_enable(bev2, EV_READ); + + tt_int_op(bufferevent_get_enabled(bev1), ==, EV_WRITE); + tt_int_op(bufferevent_get_enabled(bev2), ==, EV_WRITE|EV_READ); + + for (i = 0; i < (int)sizeof(buffer); i++) + buffer[i] = i; + + bufferevent_write(bev1, buffer, sizeof(buffer)); + if (flush >= 0) { + tt_int_op(bufferevent_flush(bev1, EV_WRITE, flush), >=, 0); + } + + event_dispatch(); + + bufferevent_free(bev2); + tt_ptr_op(bufferevent_pair_get_partner(bev1), ==, NULL); + bufferevent_free(bev1); + + /** Only pair call errorcb for BEV_FINISHED */ + if (use_pair && flush == BEV_FINISHED) { + expected = -1; + } + if (test_ok != expected) + test_ok = 0; +end: + ; +} + +static void test_bufferevent(void) { test_bufferevent_impl(0, -1); } +static void test_bufferevent_pair(void) { test_bufferevent_impl(1, -1); } + +static void test_bufferevent_flush_normal(void) { test_bufferevent_impl(0, BEV_NORMAL); } +static void test_bufferevent_flush_flush(void) { test_bufferevent_impl(0, BEV_FLUSH); } +static void test_bufferevent_flush_finished(void) { test_bufferevent_impl(0, BEV_FINISHED); } + +static void test_bufferevent_pair_flush_normal(void) { test_bufferevent_impl(1, BEV_NORMAL); } +static void test_bufferevent_pair_flush_flush(void) { test_bufferevent_impl(1, BEV_FLUSH); } +static void test_bufferevent_pair_flush_finished(void) { test_bufferevent_impl(1, BEV_FINISHED); } + +#if defined(EVTHREAD_USE_PTHREADS_IMPLEMENTED) && !defined(__SANITIZE_ADDRESS__) +/** + * Trace lock/unlock/alloc/free for locks. + * (More heavier then evthread_debug*) + */ +typedef struct +{ + void *lock; + enum { + ALLOC, FREE, + } status; + size_t locked /** allow recursive locking */; +} lock_wrapper; +struct lock_unlock_base +{ + /* Original callbacks */ + struct evthread_lock_callbacks cbs; + /* Map of locks */ + lock_wrapper *locks; + size_t nr_locks; +} lu_base = { + .locks = NULL, +}; + +static lock_wrapper *lu_find(void *lock_) +{ + size_t i; + for (i = 0; i < lu_base.nr_locks; ++i) { + lock_wrapper *lock = &lu_base.locks[i]; + if (lock->lock == lock_) + return lock; + } + return NULL; +} + +static void *trace_lock_alloc(unsigned locktype) +{ + void *lock; + ++lu_base.nr_locks; + lu_base.locks = realloc(lu_base.locks, + sizeof(lock_wrapper) * lu_base.nr_locks); + lock = lu_base.cbs.alloc(locktype); + lu_base.locks[lu_base.nr_locks - 1] = (lock_wrapper){ lock, ALLOC, 0 }; + return lock; +} +static void trace_lock_free(void *lock_, unsigned locktype) +{ + lock_wrapper *lock = lu_find(lock_); + if (!lock || lock->status == FREE || lock->locked) { + TT_FAIL(("lock: free error")); + } else { + lock->status = FREE; + lu_base.cbs.free(lock_, locktype); + } +} +static int trace_lock_lock(unsigned mode, void *lock_) +{ + lock_wrapper *lock = lu_find(lock_); + if (!lock || lock->status == FREE) { + TT_FAIL(("lock: lock error")); + return -1; + } else { + ++lock->locked; + return lu_base.cbs.lock(mode, lock_); + } +} +static int trace_lock_unlock(unsigned mode, void *lock_) +{ + lock_wrapper *lock = lu_find(lock_); + if (!lock || lock->status == FREE || !lock->locked) { + TT_FAIL(("lock: unlock error")); + return -1; + } else { + --lock->locked; + return lu_base.cbs.unlock(mode, lock_); + } +} +static void lock_unlock_free_thread_cbs(void) +{ + event_base_free(NULL); + + if (libevent_tests_running_in_debug_mode) + libevent_global_shutdown(); + + /** drop immutable flag */ + evthread_set_lock_callbacks(NULL); + /** avoid calling of event_global_setup_locks_() for new cbs */ + libevent_global_shutdown(); + /** drop immutable flag for non-debug ops (since called after shutdown) */ + evthread_set_lock_callbacks(NULL); +} + +static int use_lock_unlock_profiler(void) +{ + struct evthread_lock_callbacks cbs = { + EVTHREAD_LOCK_API_VERSION, + EVTHREAD_LOCKTYPE_RECURSIVE, + trace_lock_alloc, + trace_lock_free, + trace_lock_lock, + trace_lock_unlock, + }; + memcpy(&lu_base.cbs, evthread_get_lock_callbacks(), + sizeof(lu_base.cbs)); + { + lock_unlock_free_thread_cbs(); + + evthread_set_lock_callbacks(&cbs); + /** re-create debug locks correctly */ + evthread_enable_lock_debugging(); + + event_init(); + } + return 0; +} +static void free_lock_unlock_profiler(struct basic_test_data *data) +{ + /** fix "held_by" for kqueue */ + evthread_set_lock_callbacks(NULL); + + lock_unlock_free_thread_cbs(); + free(lu_base.locks); + data->base = NULL; +} + +static void test_bufferevent_pair_release_lock(void *arg) +{ + struct basic_test_data *data = arg; + use_lock_unlock_profiler(); + { + struct bufferevent *pair[2]; + if (!bufferevent_pair_new(NULL, BEV_OPT_THREADSAFE, pair)) { + bufferevent_free(pair[0]); + bufferevent_free(pair[1]); + } else + tt_abort_perror("bufferevent_pair_new"); + } + free_lock_unlock_profiler(data); +end: + ; +} +#endif + +/* + * test watermarks and bufferevent + */ + +static void +wm_readcb(struct bufferevent *bev, void *arg) +{ + struct evbuffer *evbuf = evbuffer_new(); + int len = (int)evbuffer_get_length(bev->input); + static int nread; + + assert(len >= 10 && len <= 20); + + assert(evbuf != NULL); + + /* gratuitous test of bufferevent_read_buffer */ + bufferevent_read_buffer(bev, evbuf); + + nread += len; + if (nread == 65000) { + bufferevent_disable(bev, EV_READ); + test_ok++; + } + + evbuffer_free(evbuf); +} + +static void +wm_writecb(struct bufferevent *bev, void *arg) +{ + assert(evbuffer_get_length(bev->output) <= 100); + if (evbuffer_get_length(bev->output) == 0) { + evbuffer_drain(bev->output, evbuffer_get_length(bev->output)); + test_ok++; + } +} + +static void +wm_errorcb(struct bufferevent *bev, short what, void *arg) +{ + test_ok = -2; +} + +static void +test_bufferevent_watermarks_impl(int use_pair) +{ + struct bufferevent *bev1 = NULL, *bev2 = NULL; + char buffer[65000]; + size_t low, high; + int i; + test_ok = 0; + + if (use_pair) { + struct bufferevent *pair[2]; + tt_assert(0 == bufferevent_pair_new(NULL, 0, pair)); + bev1 = pair[0]; + bev2 = pair[1]; + bufferevent_setcb(bev1, NULL, wm_writecb, errorcb, NULL); + bufferevent_setcb(bev2, wm_readcb, NULL, errorcb, NULL); + } else { + bev1 = bufferevent_new(pair[0], NULL, wm_writecb, wm_errorcb, NULL); + bev2 = bufferevent_new(pair[1], wm_readcb, NULL, wm_errorcb, NULL); + } + tt_assert(bev1); + tt_assert(bev2); + bufferevent_disable(bev1, EV_READ); + bufferevent_enable(bev2, EV_READ); + + /* By default, low watermarks are set to 0 */ + bufferevent_getwatermark(bev1, EV_READ, &low, NULL); + tt_int_op(low, ==, 0); + bufferevent_getwatermark(bev2, EV_WRITE, &low, NULL); + tt_int_op(low, ==, 0); + + for (i = 0; i < (int)sizeof(buffer); i++) + buffer[i] = (char)i; + + /* limit the reading on the receiving bufferevent */ + bufferevent_setwatermark(bev2, EV_READ, 10, 20); + + bufferevent_getwatermark(bev2, EV_READ, &low, &high); + tt_int_op(low, ==, 10); + tt_int_op(high, ==, 20); + + /* Tell the sending bufferevent not to notify us till it's down to + 100 bytes. */ + bufferevent_setwatermark(bev1, EV_WRITE, 100, 2000); + + bufferevent_getwatermark(bev1, EV_WRITE, &low, &high); + tt_int_op(low, ==, 100); + tt_int_op(high, ==, 2000); + + { + int r = bufferevent_getwatermark(bev1, EV_WRITE | EV_READ, &low, &high); + tt_int_op(r, !=, 0); + } + + bufferevent_write(bev1, buffer, sizeof(buffer)); + + event_dispatch(); + + tt_int_op(test_ok, ==, 2); + + /* The write callback drained all the data from outbuf, so we + * should have removed the write event... */ + tt_assert(!event_pending(&bev2->ev_write, EV_WRITE, NULL)); + +end: + if (bev1) + bufferevent_free(bev1); + if (bev2) + bufferevent_free(bev2); +} + +static void +test_bufferevent_watermarks(void) +{ + test_bufferevent_watermarks_impl(0); +} + +static void +test_bufferevent_pair_watermarks(void) +{ + test_bufferevent_watermarks_impl(1); +} + +/* + * Test bufferevent filters + */ + +/* strip an 'x' from each byte */ + +static enum bufferevent_filter_result +bufferevent_input_filter(struct evbuffer *src, struct evbuffer *dst, + ev_ssize_t lim, enum bufferevent_flush_mode state, void *ctx) +{ + const unsigned char *buffer; + unsigned i; + + buffer = evbuffer_pullup(src, evbuffer_get_length(src)); + for (i = 0; i < evbuffer_get_length(src); i += 2) { + if (buffer[i] == '-') + continue; + + assert(buffer[i] == 'x'); + evbuffer_add(dst, buffer + i + 1, 1); + } + + evbuffer_drain(src, i); + return (BEV_OK); +} + +/* add an 'x' before each byte */ + +static enum bufferevent_filter_result +bufferevent_output_filter(struct evbuffer *src, struct evbuffer *dst, + ev_ssize_t lim, enum bufferevent_flush_mode state, void *ctx) +{ + const unsigned char *buffer; + unsigned i; + struct bufferevent **bevp = ctx; + + ++test_ok; + + if (test_ok == 1) { + buffer = evbuffer_pullup(src, evbuffer_get_length(src)); + for (i = 0; i < evbuffer_get_length(src); ++i) { + evbuffer_add(dst, "x", 1); + evbuffer_add(dst, buffer + i, 1); + } + evbuffer_drain(src, evbuffer_get_length(src)); + } else { + return BEV_ERROR; + } + + if (bevp && test_ok == 1) { + int prev = ++test_ok; + bufferevent_write(*bevp, "-", 1); + /* check that during this bufferevent_write() + * bufferevent_output_filter() will not be called again */ + assert(test_ok == prev); + --test_ok; + } + + return (BEV_OK); +} + +static void +test_bufferevent_filters_impl(int use_pair, int disable) +{ + struct bufferevent *bev1 = NULL, *bev2 = NULL; + struct bufferevent *bev1_base = NULL, *bev2_base = NULL; + char buffer[8333]; + int i; + + test_ok = 0; + + if (use_pair) { + struct bufferevent *pair[2]; + tt_assert(0 == bufferevent_pair_new(NULL, 0, pair)); + bev1 = pair[0]; + bev2 = pair[1]; + } else { + bev1 = bufferevent_socket_new(NULL, pair[0], 0); + bev2 = bufferevent_socket_new(NULL, pair[1], 0); + } + bev1_base = bev1; + bev2_base = bev2; + + for (i = 0; i < (int)sizeof(buffer); i++) + buffer[i] = i; + + bev1 = bufferevent_filter_new(bev1, NULL, bufferevent_output_filter, + BEV_OPT_CLOSE_ON_FREE, NULL, + disable ? &bev1 : NULL); + + bev2 = bufferevent_filter_new(bev2, bufferevent_input_filter, + NULL, BEV_OPT_CLOSE_ON_FREE, NULL, NULL); + bufferevent_setcb(bev1, NULL, writecb, errorcb, NULL); + bufferevent_setcb(bev2, readcb, NULL, errorcb, NULL); + + tt_ptr_op(bufferevent_get_underlying(bev1), ==, bev1_base); + tt_ptr_op(bufferevent_get_underlying(bev2), ==, bev2_base); + tt_fd_op(bufferevent_getfd(bev1), ==, bufferevent_getfd(bev1_base)); + tt_fd_op(bufferevent_getfd(bev2), ==, bufferevent_getfd(bev2_base)); + + bufferevent_disable(bev1, EV_READ); + bufferevent_enable(bev2, EV_READ); + /* insert some filters */ + bufferevent_write(bev1, buffer, sizeof(buffer)); + + event_dispatch(); + + if (test_ok != 3 + !!disable) + test_ok = 0; + +end: + if (bev1) + bufferevent_free(bev1); + if (bev2) + bufferevent_free(bev2); + +} + +static void test_bufferevent_filters(void) +{ test_bufferevent_filters_impl(0, 0); } +static void test_bufferevent_pair_filters(void) +{ test_bufferevent_filters_impl(1, 0); } +static void test_bufferevent_filters_disable(void) +{ test_bufferevent_filters_impl(0, 1); } +static void test_bufferevent_pair_filters_disable(void) +{ test_bufferevent_filters_impl(1, 1); } + + +static void +sender_writecb(struct bufferevent *bev, void *ctx) +{ + if (evbuffer_get_length(bufferevent_get_output(bev)) == 0) { + bufferevent_disable(bev,EV_READ|EV_WRITE); + TT_BLATHER(("Flushed %d: freeing it.", (int)bufferevent_getfd(bev))); + bufferevent_free(bev); + } +} + +static void +sender_errorcb(struct bufferevent *bev, short what, void *ctx) +{ + TT_FAIL(("Got sender error %d",(int)what)); +} + +static int bufferevent_connect_test_flags = 0; +static int bufferevent_trigger_test_flags = 0; +static int n_strings_read = 0; +static int n_reads_invoked = 0; +static int n_events_invoked = 0; + +#define TEST_STR "Now is the time for all good events to signal for " \ + "the good of their protocol" +static void +listen_cb(struct evconnlistener *listener, evutil_socket_t fd, + struct sockaddr *sa, int socklen, void *arg) +{ + struct event_base *base = arg; + struct bufferevent *bev; + const char s[] = TEST_STR; + TT_BLATHER(("Got a request on socket %d", (int)fd )); + bev = bufferevent_socket_new(base, fd, bufferevent_connect_test_flags); + tt_assert(bev); + bufferevent_setcb(bev, NULL, sender_writecb, sender_errorcb, NULL); + bufferevent_write(bev, s, sizeof(s)); +end: + ; +} + +static evutil_socket_t +fake_listener_create(struct sockaddr_in *localhost) +{ + struct sockaddr *sa = (struct sockaddr *)localhost; + evutil_socket_t fd = -1; + ev_socklen_t slen = sizeof(*localhost); + + memset(localhost, 0, sizeof(*localhost)); + localhost->sin_port = 0; /* have the kernel pick a port */ + localhost->sin_addr.s_addr = htonl(0x7f000001L); + localhost->sin_family = AF_INET; + + /* bind, but don't listen or accept. should trigger + "Connection refused" reliably on most platforms. */ + fd = socket(localhost->sin_family, SOCK_STREAM, 0); + tt_assert(fd >= 0); + tt_assert(bind(fd, sa, slen) == 0); + tt_assert(getsockname(fd, sa, &slen) == 0); + + return fd; + +end: + return -1; +} + +static void +reader_eventcb(struct bufferevent *bev, short what, void *ctx) +{ + struct event_base *base = ctx; + if (what & BEV_EVENT_ERROR) { + perror("foobar"); + TT_FAIL(("got connector error %d", (int)what)); + return; + } + if (what & BEV_EVENT_CONNECTED) { + TT_BLATHER(("connected on %d", (int)bufferevent_getfd(bev))); + bufferevent_enable(bev, EV_READ); + } + if (what & BEV_EVENT_EOF) { + char buf[512]; + size_t n; + n = bufferevent_read(bev, buf, sizeof(buf)-1); + tt_int_op(n, >=, 0); + buf[n] = '\0'; + tt_str_op(buf, ==, TEST_STR); + if (++n_strings_read == 2) + event_base_loopexit(base, NULL); + TT_BLATHER(("EOF on %d: %d strings read.", + (int)bufferevent_getfd(bev), n_strings_read)); + } +end: + ; +} + +static void +reader_eventcb_simple(struct bufferevent *bev, short what, void *ctx) +{ + TT_BLATHER(("Read eventcb simple invoked on %d.", + (int)bufferevent_getfd(bev))); + n_events_invoked++; +} + +static void +reader_readcb(struct bufferevent *bev, void *ctx) +{ + TT_BLATHER(("Read invoked on %d.", (int)bufferevent_getfd(bev))); + n_reads_invoked++; +} + +static void +test_bufferevent_connect(void *arg) +{ + struct basic_test_data *data = arg; + struct evconnlistener *lev=NULL; + struct bufferevent *bev1=NULL, *bev2=NULL; + struct sockaddr_in localhost; + struct sockaddr_storage ss; + struct sockaddr *sa; + ev_socklen_t slen; + + int be_flags=BEV_OPT_CLOSE_ON_FREE; + + if (strstr((char*)data->setup_data, "defer")) { + be_flags |= BEV_OPT_DEFER_CALLBACKS; + } + if (strstr((char*)data->setup_data, "unlocked")) { + be_flags |= BEV_OPT_UNLOCK_CALLBACKS; + } + if (strstr((char*)data->setup_data, "lock")) { + be_flags |= BEV_OPT_THREADSAFE; + } + bufferevent_connect_test_flags = be_flags; +#ifdef _WIN32 + if (!strcmp((char*)data->setup_data, "unset_connectex")) { + struct win32_extension_fns *ext = + (struct win32_extension_fns *) + event_get_win32_extension_fns_(); + ext->ConnectEx = NULL; + } +#endif + + memset(&localhost, 0, sizeof(localhost)); + + localhost.sin_port = 0; /* pick-a-port */ + localhost.sin_addr.s_addr = htonl(0x7f000001L); + localhost.sin_family = AF_INET; + sa = (struct sockaddr *)&localhost; + lev = evconnlistener_new_bind(data->base, listen_cb, data->base, + LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE, + 16, sa, sizeof(localhost)); + tt_assert(lev); + + sa = (struct sockaddr *)&ss; + slen = sizeof(ss); + if (regress_get_listener_addr(lev, sa, &slen) < 0) { + tt_abort_perror("getsockname"); + } + + tt_assert(!evconnlistener_enable(lev)); + bev1 = bufferevent_socket_new(data->base, -1, be_flags); + bev2 = bufferevent_socket_new(data->base, -1, be_flags); + tt_assert(bev1); + tt_assert(bev2); + bufferevent_setcb(bev1, reader_readcb,NULL, reader_eventcb, data->base); + bufferevent_setcb(bev2, reader_readcb,NULL, reader_eventcb, data->base); + + bufferevent_enable(bev1, EV_READ); + bufferevent_enable(bev2, EV_READ); + + tt_want(!bufferevent_socket_connect(bev1, sa, sizeof(localhost))); + tt_want(!bufferevent_socket_connect(bev2, sa, sizeof(localhost))); + + event_base_dispatch(data->base); + + tt_int_op(n_strings_read, ==, 2); + tt_int_op(n_reads_invoked, >=, 2); +end: + if (lev) + evconnlistener_free(lev); + + if (bev1) + bufferevent_free(bev1); + + if (bev2) + bufferevent_free(bev2); +} + +static void +close_socket_cb(evutil_socket_t fd, short what, void *arg) +{ + evutil_socket_t *fdp = arg; + if (*fdp >= 0) { + evutil_closesocket(*fdp); + *fdp = -1; + } +} + +static void +test_bufferevent_connect_fail_eventcb(void *arg) +{ + struct basic_test_data *data = arg; + int flags = BEV_OPT_CLOSE_ON_FREE | (long)data->setup_data; + struct event close_listener_event; + struct bufferevent *bev = NULL; + struct evconnlistener *lev = NULL; + struct sockaddr_in localhost; + struct timeval close_timeout = { 0, 300000 }; + ev_socklen_t slen = sizeof(localhost); + evutil_socket_t fake_listener = -1; + int r; + + fake_listener = fake_listener_create(&localhost); + + tt_int_op(n_events_invoked, ==, 0); + + bev = bufferevent_socket_new(data->base, -1, flags); + tt_assert(bev); + bufferevent_setcb(bev, reader_readcb, reader_readcb, + reader_eventcb_simple, data->base); + bufferevent_enable(bev, EV_READ|EV_WRITE); + tt_int_op(n_events_invoked, ==, 0); + tt_int_op(n_reads_invoked, ==, 0); + + /** @see also test_bufferevent_connect_fail() */ + r = bufferevent_socket_connect(bev, (struct sockaddr *)&localhost, slen); + /* XXXX we'd like to test the '0' case everywhere, but FreeBSD tells + * detects the error immediately, which is not really wrong of it. */ + tt_want(r == 0 || r == -1); + + tt_int_op(n_events_invoked, ==, 0); + tt_int_op(n_reads_invoked, ==, 0); + + /* Close the listener socket after a delay. This should trigger + "connection refused" on some other platforms, including OSX. */ + evtimer_assign(&close_listener_event, data->base, close_socket_cb, + &fake_listener); + event_add(&close_listener_event, &close_timeout); + + event_base_dispatch(data->base); + tt_int_op(n_events_invoked, ==, 1); + tt_int_op(n_reads_invoked, ==, 0); + +end: + if (lev) + evconnlistener_free(lev); + if (bev) + bufferevent_free(bev); + if (fake_listener >= 0) + evutil_closesocket(fake_listener); +} + +static void +want_fail_eventcb(struct bufferevent *bev, short what, void *ctx) +{ + struct event_base *base = ctx; + const char *err; + evutil_socket_t s; + + if (what & BEV_EVENT_ERROR) { + s = bufferevent_getfd(bev); + err = evutil_socket_error_to_string(evutil_socket_geterror(s)); + TT_BLATHER(("connection failure on "EV_SOCK_FMT": %s", + EV_SOCK_ARG(s), err)); + test_ok = 1; + } else { + TT_FAIL(("didn't fail? what %hd", what)); + } + + event_base_loopexit(base, NULL); +} + +static void +test_bufferevent_connect_fail(void *arg) +{ + struct basic_test_data *data = (struct basic_test_data *)arg; + struct bufferevent *bev=NULL; + struct event close_listener_event; + int close_listener_event_added = 0; + struct timeval close_timeout = { 0, 300000 }; + struct sockaddr_in localhost; + ev_socklen_t slen = sizeof(localhost); + evutil_socket_t fake_listener = -1; + int r; + + test_ok = 0; + + fake_listener = fake_listener_create(&localhost); + bev = bufferevent_socket_new(data->base, -1, + BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS); + tt_assert(bev); + bufferevent_setcb(bev, NULL, NULL, want_fail_eventcb, data->base); + + r = bufferevent_socket_connect(bev, (struct sockaddr *)&localhost, slen); + /* XXXX we'd like to test the '0' case everywhere, but FreeBSD tells + * detects the error immediately, which is not really wrong of it. */ + tt_want(r == 0 || r == -1); + + /* Close the listener socket after a delay. This should trigger + "connection refused" on some other platforms, including OSX. */ + evtimer_assign(&close_listener_event, data->base, close_socket_cb, + &fake_listener); + event_add(&close_listener_event, &close_timeout); + close_listener_event_added = 1; + + event_base_dispatch(data->base); + + tt_int_op(test_ok, ==, 1); + +end: + if (fake_listener >= 0) + evutil_closesocket(fake_listener); + + if (bev) + bufferevent_free(bev); + + if (close_listener_event_added) + event_del(&close_listener_event); +} + +struct timeout_cb_result { + struct timeval read_timeout_at; + struct timeval write_timeout_at; + struct timeval last_wrote_at; + struct timeval last_read_at; + int n_read_timeouts; + int n_write_timeouts; + int total_calls; +}; + +static void +bev_timeout_read_cb(struct bufferevent *bev, void *arg) +{ + struct timeout_cb_result *res = arg; + evutil_gettimeofday(&res->last_read_at, NULL); +} +static void +bev_timeout_write_cb(struct bufferevent *bev, void *arg) +{ + struct timeout_cb_result *res = arg; + evutil_gettimeofday(&res->last_wrote_at, NULL); +} +static void +bev_timeout_event_cb(struct bufferevent *bev, short what, void *arg) +{ + struct timeout_cb_result *res = arg; + ++res->total_calls; + + if ((what & (BEV_EVENT_READING|BEV_EVENT_TIMEOUT)) + == (BEV_EVENT_READING|BEV_EVENT_TIMEOUT)) { + evutil_gettimeofday(&res->read_timeout_at, NULL); + ++res->n_read_timeouts; + } + if ((what & (BEV_EVENT_WRITING|BEV_EVENT_TIMEOUT)) + == (BEV_EVENT_WRITING|BEV_EVENT_TIMEOUT)) { + evutil_gettimeofday(&res->write_timeout_at, NULL); + ++res->n_write_timeouts; + } +} + +static void +test_bufferevent_timeouts(void *arg) +{ + /* "arg" is a string containing "pair" and/or "filter". */ + struct bufferevent *bev1 = NULL, *bev2 = NULL; + struct basic_test_data *data = arg; + int use_pair = 0, use_filter = 0; + struct timeval tv_w, tv_r, started_at; + struct timeout_cb_result res1, res2; + + memset(&res1, 0, sizeof(res1)); + memset(&res2, 0, sizeof(res2)); + + if (strstr((char*)data->setup_data, "pair")) + use_pair = 1; + if (strstr((char*)data->setup_data, "filter")) + use_filter = 1; + + if (use_pair) { + struct bufferevent *p[2]; + tt_int_op(0, ==, bufferevent_pair_new(data->base, 0, p)); + bev1 = p[0]; + bev2 = p[1]; + } else { + bev1 = bufferevent_socket_new(data->base, data->pair[0], 0); + bev2 = bufferevent_socket_new(data->base, data->pair[1], 0); + } + tt_assert(bev1); + tt_assert(bev2); + + if (use_filter) { + struct bufferevent *bevf1, *bevf2; + bevf1 = bufferevent_filter_new(bev1, NULL, NULL, + BEV_OPT_CLOSE_ON_FREE, NULL, NULL); + bevf2 = bufferevent_filter_new(bev2, NULL, NULL, + BEV_OPT_CLOSE_ON_FREE, NULL, NULL); + tt_assert(bevf1); + tt_assert(bevf2); + bev1 = bevf1; + bev2 = bevf2; + } + + /* Do this nice and early. */ + bufferevent_disable(bev2, EV_READ); + + /* bev1 will try to write and read. Both will time out. */ + evutil_gettimeofday(&started_at, NULL); + tv_w.tv_sec = tv_r.tv_sec = 0; + tv_w.tv_usec = 100*1000; + tv_r.tv_usec = 150*1000; + bufferevent_setcb(bev1, bev_timeout_read_cb, bev_timeout_write_cb, + bev_timeout_event_cb, &res1); + bufferevent_set_timeouts(bev1, &tv_r, &tv_w); + bufferevent_write(bev1, "ABCDEFG", 7); + bufferevent_enable(bev1, EV_READ|EV_WRITE); + + /* bev2 has nothing to say, and isn't listening. */ + bufferevent_setcb(bev2, bev_timeout_read_cb, bev_timeout_write_cb, + bev_timeout_event_cb, &res2); + tv_w.tv_sec = tv_r.tv_sec = 0; + tv_w.tv_usec = 200*1000; + tv_r.tv_usec = 100*1000; + bufferevent_set_timeouts(bev2, &tv_r, &tv_w); + bufferevent_enable(bev2, EV_WRITE); + + tv_r.tv_sec = 0; + tv_r.tv_usec = 350000; + + event_base_loopexit(data->base, &tv_r); + event_base_dispatch(data->base); + + /* XXXX Test that actually reading or writing a little resets the + * timeouts. */ + + tt_want(res1.total_calls == 2); + tt_want(res1.n_read_timeouts == 1); + tt_want(res1.n_write_timeouts == 1); + tt_want(res2.total_calls == !(use_pair && !use_filter)); + tt_want(res2.n_write_timeouts == !(use_pair && !use_filter)); + tt_want(!res2.n_read_timeouts); + + test_timeval_diff_eq(&started_at, &res1.read_timeout_at, 150); + test_timeval_diff_eq(&started_at, &res1.write_timeout_at, 100); + +#define tt_assert_timeval_empty(tv) do { \ + tt_int_op((tv).tv_sec, ==, 0); \ + tt_int_op((tv).tv_usec, ==, 0); \ +} while(0) + tt_assert_timeval_empty(res1.last_read_at); + tt_assert_timeval_empty(res2.last_read_at); + tt_assert_timeval_empty(res2.last_wrote_at); + tt_assert_timeval_empty(res2.last_wrote_at); +#undef tt_assert_timeval_empty + +end: + if (bev1) + bufferevent_free(bev1); + if (bev2) + bufferevent_free(bev2); +} + +static void +trigger_failure_cb(evutil_socket_t fd, short what, void *ctx) +{ + TT_FAIL(("The triggered callback did not fire or the machine is really slow (try increasing timeout).")); +} + +static void +trigger_eventcb(struct bufferevent *bev, short what, void *ctx) +{ + struct event_base *base = ctx; + if (what == ~0) { + TT_BLATHER(("Event successfully triggered.")); + event_base_loopexit(base, NULL); + return; + } + reader_eventcb(bev, what, ctx); +} + +static void +trigger_readcb_triggered(struct bufferevent *bev, void *ctx) +{ + TT_BLATHER(("Read successfully triggered.")); + n_reads_invoked++; + bufferevent_trigger_event(bev, ~0, bufferevent_trigger_test_flags); +} + +static void +trigger_readcb(struct bufferevent *bev, void *ctx) +{ + struct timeval timeout = { 30, 0 }; + struct event_base *base = ctx; + size_t low, high, len; + int expected_reads; + + TT_BLATHER(("Read invoked on %d.", (int)bufferevent_getfd(bev))); + expected_reads = ++n_reads_invoked; + + bufferevent_setcb(bev, trigger_readcb_triggered, NULL, trigger_eventcb, ctx); + + bufferevent_getwatermark(bev, EV_READ, &low, &high); + len = evbuffer_get_length(bufferevent_get_input(bev)); + + bufferevent_setwatermark(bev, EV_READ, len + 1, 0); + bufferevent_trigger(bev, EV_READ, bufferevent_trigger_test_flags); + /* no callback expected */ + tt_int_op(n_reads_invoked, ==, expected_reads); + + if ((bufferevent_trigger_test_flags & BEV_TRIG_DEFER_CALLBACKS) || + (bufferevent_connect_test_flags & BEV_OPT_DEFER_CALLBACKS)) { + /* will be deferred */ + } else { + expected_reads++; + } + + event_base_once(base, -1, EV_TIMEOUT, trigger_failure_cb, NULL, &timeout); + + bufferevent_trigger(bev, EV_READ, + bufferevent_trigger_test_flags | BEV_TRIG_IGNORE_WATERMARKS); + tt_int_op(n_reads_invoked, ==, expected_reads); + + bufferevent_setwatermark(bev, EV_READ, low, high); +end: + ; +} + +static void +test_bufferevent_trigger(void *arg) +{ + struct basic_test_data *data = arg; + struct evconnlistener *lev=NULL; + struct bufferevent *bev=NULL; + struct sockaddr_in localhost; + struct sockaddr_storage ss; + struct sockaddr *sa; + ev_socklen_t slen; + + int be_flags=BEV_OPT_CLOSE_ON_FREE; + int trig_flags=0; + + if (strstr((char*)data->setup_data, "defer")) { + be_flags |= BEV_OPT_DEFER_CALLBACKS; + } + bufferevent_connect_test_flags = be_flags; + + if (strstr((char*)data->setup_data, "postpone")) { + trig_flags |= BEV_TRIG_DEFER_CALLBACKS; + } + bufferevent_trigger_test_flags = trig_flags; + + memset(&localhost, 0, sizeof(localhost)); + + localhost.sin_port = 0; /* pick-a-port */ + localhost.sin_addr.s_addr = htonl(0x7f000001L); + localhost.sin_family = AF_INET; + sa = (struct sockaddr *)&localhost; + lev = evconnlistener_new_bind(data->base, listen_cb, data->base, + LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE, + 16, sa, sizeof(localhost)); + tt_assert(lev); + + sa = (struct sockaddr *)&ss; + slen = sizeof(ss); + if (regress_get_listener_addr(lev, sa, &slen) < 0) { + tt_abort_perror("getsockname"); + } + + tt_assert(!evconnlistener_enable(lev)); + bev = bufferevent_socket_new(data->base, -1, be_flags); + tt_assert(bev); + bufferevent_setcb(bev, trigger_readcb, NULL, trigger_eventcb, data->base); + + bufferevent_enable(bev, EV_READ); + + tt_want(!bufferevent_socket_connect(bev, sa, sizeof(localhost))); + + event_base_dispatch(data->base); + + tt_int_op(n_reads_invoked, ==, 2); +end: + if (lev) + evconnlistener_free(lev); + + if (bev) + bufferevent_free(bev); +} + +static void +test_bufferevent_socket_filter_inactive(void *arg) +{ + struct basic_test_data *data = arg; + struct bufferevent *bev = NULL, *bevf = NULL; + + bev = bufferevent_socket_new(data->base, -1, 0); + tt_assert(bev); + bevf = bufferevent_filter_new(bev, NULL, NULL, 0, NULL, NULL); + tt_assert(bevf); + +end: + if (bevf) + bufferevent_free(bevf); + if (bev) + bufferevent_free(bev); +} + +static void +pair_flush_eventcb(struct bufferevent *bev, short what, void *ctx) +{ + int *callback_what = ctx; + *callback_what = what; +} + +static void +test_bufferevent_pair_flush(void *arg) +{ + struct basic_test_data *data = arg; + struct bufferevent *pair[2]; + struct bufferevent *bev1 = NULL; + struct bufferevent *bev2 = NULL; + int callback_what = 0; + + tt_assert(0 == bufferevent_pair_new(data->base, 0, pair)); + bev1 = pair[0]; + bev2 = pair[1]; + tt_assert(0 == bufferevent_enable(bev1, EV_WRITE)); + tt_assert(0 == bufferevent_enable(bev2, EV_READ)); + + bufferevent_setcb(bev2, NULL, NULL, pair_flush_eventcb, &callback_what); + + bufferevent_flush(bev1, EV_WRITE, BEV_FINISHED); + + event_base_loop(data->base, EVLOOP_ONCE); + + tt_assert(callback_what == (BEV_EVENT_READING | BEV_EVENT_EOF)); + +end: + if (bev1) + bufferevent_free(bev1); + if (bev2) + bufferevent_free(bev2); +} + +struct bufferevent_filter_data_stuck { + size_t header_size; + size_t total_read; +}; + +static void +bufferevent_filter_data_stuck_readcb(struct bufferevent *bev, void *arg) +{ + struct bufferevent_filter_data_stuck *filter_data = arg; + struct evbuffer *input = bufferevent_get_input(bev); + size_t read_size = evbuffer_get_length(input); + evbuffer_drain(input, read_size); + filter_data->total_read += read_size; +} + +/** + * This filter prepends header once before forwarding data. + */ +static enum bufferevent_filter_result +bufferevent_filter_data_stuck_inputcb( + struct evbuffer *src, struct evbuffer *dst, ev_ssize_t dst_limit, + enum bufferevent_flush_mode mode, void *ctx) +{ + struct bufferevent_filter_data_stuck *filter_data = ctx; + static int header_inserted = 0; + size_t payload_size; + size_t header_size = 0; + + if (!header_inserted) { + char *header = calloc(filter_data->header_size, 1); + evbuffer_add(dst, header, filter_data->header_size); + free(header); + header_size = filter_data->header_size; + header_inserted = 1; + } + + payload_size = evbuffer_get_length(src); + if (payload_size > dst_limit - header_size) { + payload_size = dst_limit - header_size; + } + + tt_int_op(payload_size, ==, evbuffer_remove_buffer(src, dst, payload_size)); + +end: + return BEV_OK; +} + +static void +test_bufferevent_filter_data_stuck(void *arg) +{ + const size_t read_high_wm = 4096; + struct bufferevent_filter_data_stuck filter_data; + struct basic_test_data *data = arg; + struct bufferevent *pair[2]; + struct bufferevent *filter = NULL; + + int options = BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS; + + char payload[4096]; + int payload_size = sizeof(payload); + + memset(&filter_data, 0, sizeof(filter_data)); + filter_data.header_size = 20; + + tt_assert(bufferevent_pair_new(data->base, options, pair) == 0); + + bufferevent_setwatermark(pair[0], EV_READ, 0, read_high_wm); + bufferevent_setwatermark(pair[1], EV_READ, 0, read_high_wm); + + tt_assert( + filter = + bufferevent_filter_new(pair[1], + bufferevent_filter_data_stuck_inputcb, + NULL, + options, + NULL, + &filter_data)); + + bufferevent_setcb(filter, + bufferevent_filter_data_stuck_readcb, + NULL, + NULL, + &filter_data); + + tt_assert(bufferevent_enable(filter, EV_READ|EV_WRITE) == 0); + + bufferevent_setwatermark(filter, EV_READ, 0, read_high_wm); + + tt_assert(bufferevent_write(pair[0], payload, sizeof(payload)) == 0); + + event_base_dispatch(data->base); + + tt_int_op(filter_data.total_read, ==, payload_size + filter_data.header_size); +end: + if (pair[0]) + bufferevent_free(pair[0]); + if (filter) + bufferevent_free(filter); +} + +struct testcase_t bufferevent_testcases[] = { + + LEGACY(bufferevent, TT_ISOLATED), + LEGACY(bufferevent_pair, TT_ISOLATED), + LEGACY(bufferevent_flush_normal, TT_ISOLATED), + LEGACY(bufferevent_flush_flush, TT_ISOLATED), + LEGACY(bufferevent_flush_finished, TT_ISOLATED), + LEGACY(bufferevent_pair_flush_normal, TT_ISOLATED), + LEGACY(bufferevent_pair_flush_flush, TT_ISOLATED), + LEGACY(bufferevent_pair_flush_finished, TT_ISOLATED), +#if defined(EVTHREAD_USE_PTHREADS_IMPLEMENTED) && !defined(__SANITIZE_ADDRESS__) + { "bufferevent_pair_release_lock", test_bufferevent_pair_release_lock, + TT_FORK|TT_ISOLATED|TT_NEED_THREADS|TT_NEED_BASE|TT_LEGACY|TT_NO_LOGS, + &basic_setup, NULL }, +#endif + LEGACY(bufferevent_watermarks, TT_ISOLATED), + LEGACY(bufferevent_pair_watermarks, TT_ISOLATED), + LEGACY(bufferevent_filters, TT_ISOLATED), + LEGACY(bufferevent_pair_filters, TT_ISOLATED), + LEGACY(bufferevent_filters_disable, TT_ISOLATED), + LEGACY(bufferevent_pair_filters_disable, TT_ISOLATED), + { "bufferevent_connect", test_bufferevent_connect, TT_FORK|TT_NEED_BASE, + &basic_setup, (void*)"" }, + { "bufferevent_connect_defer", test_bufferevent_connect, + TT_FORK|TT_NEED_BASE, &basic_setup, (void*)"defer" }, + { "bufferevent_connect_lock", test_bufferevent_connect, + TT_FORK|TT_NEED_BASE|TT_NEED_THREADS, &basic_setup, (void*)"lock" }, + { "bufferevent_connect_lock_defer", test_bufferevent_connect, + TT_FORK|TT_NEED_BASE|TT_NEED_THREADS, &basic_setup, + (void*)"defer lock" }, + { "bufferevent_connect_unlocked_cbs", test_bufferevent_connect, + TT_FORK|TT_NEED_BASE|TT_NEED_THREADS, &basic_setup, + (void*)"lock defer unlocked" }, + { "bufferevent_connect_fail", test_bufferevent_connect_fail, + TT_FORK|TT_NEED_BASE, &basic_setup, NULL }, + { "bufferevent_timeout", test_bufferevent_timeouts, + TT_FORK|TT_NEED_BASE, &basic_setup, (void*)"" }, + { "bufferevent_timeout_pair", test_bufferevent_timeouts, + TT_FORK|TT_NEED_BASE, &basic_setup, (void*)"pair" }, + { "bufferevent_timeout_filter", test_bufferevent_timeouts, + TT_FORK|TT_NEED_BASE, &basic_setup, (void*)"filter" }, + { "bufferevent_timeout_filter_pair", test_bufferevent_timeouts, + TT_FORK|TT_NEED_BASE, &basic_setup, (void*)"filter pair" }, + { "bufferevent_trigger", test_bufferevent_trigger, TT_FORK|TT_NEED_BASE, + &basic_setup, (void*)"" }, + { "bufferevent_trigger_defer", test_bufferevent_trigger, + TT_FORK|TT_NEED_BASE, &basic_setup, (void*)"defer" }, + { "bufferevent_trigger_postpone", test_bufferevent_trigger, + TT_FORK|TT_NEED_BASE|TT_NEED_THREADS, &basic_setup, + (void*)"postpone" }, + { "bufferevent_trigger_defer_postpone", test_bufferevent_trigger, + TT_FORK|TT_NEED_BASE|TT_NEED_THREADS, &basic_setup, + (void*)"defer postpone" }, +#ifdef EVENT__HAVE_LIBZ + LEGACY(bufferevent_zlib, TT_ISOLATED), +#else + { "bufferevent_zlib", NULL, TT_SKIP, NULL, NULL }, +#endif + + { "bufferevent_connect_fail_eventcb_defer", + test_bufferevent_connect_fail_eventcb, + TT_FORK|TT_NEED_BASE, &basic_setup, (void*)BEV_OPT_DEFER_CALLBACKS }, + { "bufferevent_connect_fail_eventcb", + test_bufferevent_connect_fail_eventcb, + TT_FORK|TT_NEED_BASE, &basic_setup, NULL }, + + { "bufferevent_socket_filter_inactive", + test_bufferevent_socket_filter_inactive, + TT_FORK|TT_NEED_BASE, &basic_setup, NULL }, + { "bufferevent_pair_flush", + test_bufferevent_pair_flush, + TT_FORK|TT_NEED_BASE, &basic_setup, NULL }, + { "bufferevent_filter_data_stuck", + test_bufferevent_filter_data_stuck, + TT_FORK|TT_NEED_BASE, &basic_setup, NULL }, + + END_OF_TESTCASES, +}; + +#define TT_IOCP (TT_FORK|TT_NEED_BASE|TT_ENABLE_IOCP) +#define TT_IOCP_LEGACY (TT_ISOLATED|TT_ENABLE_IOCP) +struct testcase_t bufferevent_iocp_testcases[] = { + LEGACY(bufferevent, TT_IOCP_LEGACY), + LEGACY(bufferevent_flush_normal, TT_ISOLATED), + LEGACY(bufferevent_flush_flush, TT_ISOLATED), + LEGACY(bufferevent_flush_finished, TT_ISOLATED), + LEGACY(bufferevent_watermarks, TT_IOCP_LEGACY), + LEGACY(bufferevent_filters, TT_IOCP_LEGACY), + LEGACY(bufferevent_filters_disable, TT_IOCP_LEGACY), + + { "bufferevent_connect", test_bufferevent_connect, + TT_IOCP, &basic_setup, (void*)"" }, + { "bufferevent_connect_defer", test_bufferevent_connect, + TT_IOCP, &basic_setup, (void*)"defer" }, + { "bufferevent_connect_lock", test_bufferevent_connect, + TT_IOCP, &basic_setup, (void*)"lock" }, + { "bufferevent_connect_lock_defer", test_bufferevent_connect, + TT_IOCP, &basic_setup, (void*)"defer lock" }, + { "bufferevent_connect_fail", test_bufferevent_connect_fail, + TT_IOCP, &basic_setup, NULL }, + { "bufferevent_connect_nonblocking", test_bufferevent_connect, + TT_IOCP, &basic_setup, (void*)"unset_connectex" }, + + { "bufferevent_connect_fail_eventcb_defer", + test_bufferevent_connect_fail_eventcb, + TT_IOCP, &basic_setup, (void*)BEV_OPT_DEFER_CALLBACKS }, + { "bufferevent_connect_fail_eventcb", + test_bufferevent_connect_fail_eventcb, TT_IOCP, &basic_setup, NULL }, + + END_OF_TESTCASES, +}; diff --git a/ipc/chromium/src/third_party/libevent/test/regress_dns.c b/ipc/chromium/src/third_party/libevent/test/regress_dns.c new file mode 100644 index 0000000000..9a8bff4f15 --- /dev/null +++ b/ipc/chromium/src/third_party/libevent/test/regress_dns.c @@ -0,0 +1,2518 @@ +/* + * Copyright (c) 2003-2007 Niels Provos + * Copyright (c) 2007-2012 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "../util-internal.h" + +#ifdef _WIN32 +#include +#include +#include +#endif + +#include "event2/event-config.h" + +#include +#include +#ifdef EVENT__HAVE_SYS_TIME_H +#include +#endif +#include +#ifndef _WIN32 +#include +#include +#include +#include +#include +#endif +#ifdef EVENT__HAVE_NETINET_IN6_H +#include +#endif +#ifdef HAVE_NETDB_H +#include +#endif +#include +#include +#include +#include +#include + +#ifdef EVENT__HAVE_SYS_RESOURCE_H +#include +#endif + +#include "event2/dns.h" +#include "event2/dns_compat.h" +#include "event2/dns_struct.h" +#include "event2/event.h" +#include "event2/event_compat.h" +#include "event2/event_struct.h" +#include "event2/util.h" +#include "event2/listener.h" +#include "event2/bufferevent.h" +#include +#include "log-internal.h" +#include "evthread-internal.h" +#include "regress.h" +#include "regress_testutils.h" +#include "regress_thread.h" + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) + +static int dns_ok = 0; +static int dns_got_cancel = 0; +static int dns_err = 0; + + +static void +dns_gethostbyname_cb(int result, char type, int count, int ttl, + void *addresses, void *arg) +{ + dns_ok = dns_err = 0; + + if (result == DNS_ERR_TIMEOUT) { + printf("[Timed out] "); + dns_err = result; + goto out; + } + + if (result != DNS_ERR_NONE) { + printf("[Error code %d] ", result); + goto out; + } + + TT_BLATHER(("type: %d, count: %d, ttl: %d: ", type, count, ttl)); + + switch (type) { + case DNS_IPv6_AAAA: { +#if defined(EVENT__HAVE_STRUCT_IN6_ADDR) && defined(EVENT__HAVE_INET_NTOP) && defined(INET6_ADDRSTRLEN) + struct in6_addr *in6_addrs = addresses; + char buf[INET6_ADDRSTRLEN+1]; + int i; + /* a resolution that's not valid does not help */ + if (ttl < 0) + goto out; + for (i = 0; i < count; ++i) { + const char *b = evutil_inet_ntop(AF_INET6, &in6_addrs[i], buf,sizeof(buf)); + if (b) + TT_BLATHER(("%s ", b)); + else + TT_BLATHER(("%s ", strerror(errno))); + } +#endif + break; + } + case DNS_IPv4_A: { + struct in_addr *in_addrs = addresses; + int i; + /* a resolution that's not valid does not help */ + if (ttl < 0) + goto out; + for (i = 0; i < count; ++i) + TT_BLATHER(("%s ", inet_ntoa(in_addrs[i]))); + break; + } + case DNS_PTR: + /* may get at most one PTR */ + if (count != 1) + goto out; + + TT_BLATHER(("%s ", *(char **)addresses)); + break; + default: + goto out; + } + + dns_ok = type; + +out: + if (arg == NULL) + event_loopexit(NULL); + else + event_base_loopexit((struct event_base *)arg, NULL); +} + +static void +dns_gethostbyname(void) +{ + dns_ok = 0; + evdns_resolve_ipv4("www.monkey.org", 0, dns_gethostbyname_cb, NULL); + event_dispatch(); + + tt_int_op(dns_ok, ==, DNS_IPv4_A); + test_ok = dns_ok; +end: + ; +} + +static void +dns_gethostbyname6(void) +{ + dns_ok = 0; + evdns_resolve_ipv6("www.ietf.org", 0, dns_gethostbyname_cb, NULL); + event_dispatch(); + + if (!dns_ok && dns_err == DNS_ERR_TIMEOUT) { + tt_skip(); + } + + tt_int_op(dns_ok, ==, DNS_IPv6_AAAA); + test_ok = 1; +end: + ; +} + +static void +dns_gethostbyaddr(void) +{ + struct in_addr in; + in.s_addr = htonl(0x7f000001ul); /* 127.0.0.1 */ + dns_ok = 0; + evdns_resolve_reverse(&in, 0, dns_gethostbyname_cb, NULL); + event_dispatch(); + + tt_int_op(dns_ok, ==, DNS_PTR); + test_ok = dns_ok; +end: + ; +} + +static void +dns_resolve_reverse(void *ptr) +{ + struct in_addr in; + struct event_base *base = event_base_new(); + struct evdns_base *dns = evdns_base_new(base, EVDNS_BASE_INITIALIZE_NAMESERVERS); + struct evdns_request *req = NULL; + + tt_assert(base); + tt_assert(dns); + in.s_addr = htonl(0x7f000001ul); /* 127.0.0.1 */ + dns_ok = 0; + + req = evdns_base_resolve_reverse( + dns, &in, 0, dns_gethostbyname_cb, base); + tt_assert(req); + + event_base_dispatch(base); + + tt_int_op(dns_ok, ==, DNS_PTR); + +end: + if (dns) + evdns_base_free(dns, 0); + if (base) + event_base_free(base); +} + +static int n_server_responses = 0; + +static void +dns_server_request_cb(struct evdns_server_request *req, void *data) +{ + int i, r; + const char TEST_ARPA[] = "11.11.168.192.in-addr.arpa"; + const char TEST_IN6[] = + "f.e.f.e." "0.0.0.0." "0.0.0.0." "1.1.1.1." + "a.a.a.a." "0.0.0.0." "0.0.0.0." "0.f.f.f.ip6.arpa"; + + for (i = 0; i < req->nquestions; ++i) { + const int qtype = req->questions[i]->type; + const int qclass = req->questions[i]->dns_question_class; + const char *qname = req->questions[i]->name; + + struct in_addr ans; + ans.s_addr = htonl(0xc0a80b0bUL); /* 192.168.11.11 */ + if (qtype == EVDNS_TYPE_A && + qclass == EVDNS_CLASS_INET && + !evutil_ascii_strcasecmp(qname, "zz.example.com")) { + r = evdns_server_request_add_a_reply(req, qname, + 1, &ans.s_addr, 12345); + if (r<0) + dns_ok = 0; + } else if (qtype == EVDNS_TYPE_AAAA && + qclass == EVDNS_CLASS_INET && + !evutil_ascii_strcasecmp(qname, "zz.example.com")) { + char addr6[17] = "abcdefghijklmnop"; + r = evdns_server_request_add_aaaa_reply(req, + qname, 1, addr6, 123); + if (r<0) + dns_ok = 0; + } else if (qtype == EVDNS_TYPE_PTR && + qclass == EVDNS_CLASS_INET && + !evutil_ascii_strcasecmp(qname, TEST_ARPA)) { + r = evdns_server_request_add_ptr_reply(req, NULL, + qname, "ZZ.EXAMPLE.COM", 54321); + if (r<0) + dns_ok = 0; + } else if (qtype == EVDNS_TYPE_PTR && + qclass == EVDNS_CLASS_INET && + !evutil_ascii_strcasecmp(qname, TEST_IN6)){ + r = evdns_server_request_add_ptr_reply(req, NULL, + qname, + "ZZ-INET6.EXAMPLE.COM", 54322); + if (r<0) + dns_ok = 0; + } else if (qtype == EVDNS_TYPE_A && + qclass == EVDNS_CLASS_INET && + !evutil_ascii_strcasecmp(qname, "drop.example.com")) { + if (evdns_server_request_drop(req)<0) + dns_ok = 0; + return; + } else { + printf("Unexpected question %d %d \"%s\" ", + qtype, qclass, qname); + dns_ok = 0; + } + } + r = evdns_server_request_respond(req, 0); + if (r<0) { + printf("Couldn't send reply. "); + dns_ok = 0; + } +} + +static void +dns_server_gethostbyname_cb(int result, char type, int count, int ttl, + void *addresses, void *arg) +{ + if (result == DNS_ERR_CANCEL) { + if (arg != (void*)(char*)90909) { + printf("Unexpected cancelation"); + dns_ok = 0; + } + dns_got_cancel = 1; + goto out; + } + if (result != DNS_ERR_NONE) { + printf("Unexpected result %d. ", result); + dns_ok = 0; + goto out; + } + if (count != 1) { + printf("Unexpected answer count %d. ", count); + dns_ok = 0; + goto out; + } + switch (type) { + case DNS_IPv4_A: { + struct in_addr *in_addrs = addresses; + if (in_addrs[0].s_addr != htonl(0xc0a80b0bUL) || ttl != 12345) { + printf("Bad IPv4 response \"%s\" %d. ", + inet_ntoa(in_addrs[0]), ttl); + dns_ok = 0; + goto out; + } + break; + } + case DNS_IPv6_AAAA: { +#if defined (EVENT__HAVE_STRUCT_IN6_ADDR) && defined(EVENT__HAVE_INET_NTOP) && defined(INET6_ADDRSTRLEN) + struct in6_addr *in6_addrs = addresses; + char buf[INET6_ADDRSTRLEN+1]; + if (memcmp(&in6_addrs[0].s6_addr, "abcdefghijklmnop", 16) + || ttl != 123) { + const char *b = evutil_inet_ntop(AF_INET6, &in6_addrs[0],buf,sizeof(buf)); + printf("Bad IPv6 response \"%s\" %d. ", b, ttl); + dns_ok = 0; + goto out; + } +#endif + break; + } + case DNS_PTR: { + char **addrs = addresses; + if (arg != (void*)6) { + if (strcmp(addrs[0], "ZZ.EXAMPLE.COM") || + ttl != 54321) { + printf("Bad PTR response \"%s\" %d. ", + addrs[0], ttl); + dns_ok = 0; + goto out; + } + } else { + if (strcmp(addrs[0], "ZZ-INET6.EXAMPLE.COM") || + ttl != 54322) { + printf("Bad ipv6 PTR response \"%s\" %d. ", + addrs[0], ttl); + dns_ok = 0; + goto out; + } + } + break; + } + default: + printf("Bad response type %d. ", type); + dns_ok = 0; + } + out: + if (++n_server_responses == 3) { + event_loopexit(NULL); + } +} + +static void +dns_server(void) +{ + evutil_socket_t sock=-1; + struct sockaddr_in my_addr; + struct sockaddr_storage ss; + ev_socklen_t slen; + struct evdns_server_port *port=NULL; + struct in_addr resolve_addr; + struct in6_addr resolve_addr6; + struct evdns_base *base=NULL; + struct evdns_request *req=NULL; + + dns_ok = 1; + + base = evdns_base_new(NULL, 0); + + /* Now configure a nameserver port. */ + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock<0) { + tt_abort_perror("socket"); + } + + evutil_make_socket_nonblocking(sock); + + memset(&my_addr, 0, sizeof(my_addr)); + my_addr.sin_family = AF_INET; + my_addr.sin_port = 0; /* kernel picks */ + my_addr.sin_addr.s_addr = htonl(0x7f000001UL); + if (bind(sock, (struct sockaddr*)&my_addr, sizeof(my_addr)) < 0) { + tt_abort_perror("bind"); + } + slen = sizeof(ss); + if (getsockname(sock, (struct sockaddr*)&ss, &slen) < 0) { + tt_abort_perror("getsockname"); + } + + port = evdns_add_server_port(sock, 0, dns_server_request_cb, NULL); + + /* Add ourself as the only nameserver, and make sure we really are + * the only nameserver. */ + evdns_base_nameserver_sockaddr_add(base, (struct sockaddr*)&ss, slen, 0); + tt_int_op(evdns_base_count_nameservers(base), ==, 1); + { + struct sockaddr_storage ss2; + int slen2; + + memset(&ss2, 0, sizeof(ss2)); + + slen2 = evdns_base_get_nameserver_addr(base, 0, (struct sockaddr *)&ss2, 3); + tt_int_op(slen2, ==, slen); + tt_int_op(ss2.ss_family, ==, 0); + slen2 = evdns_base_get_nameserver_addr(base, 0, (struct sockaddr *)&ss2, sizeof(ss2)); + tt_int_op(slen2, ==, slen); + tt_mem_op(&ss2, ==, &ss, slen); + + slen2 = evdns_base_get_nameserver_addr(base, 1, (struct sockaddr *)&ss2, sizeof(ss2)); + tt_int_op(-1, ==, slen2); + } + + /* Send some queries. */ + evdns_base_resolve_ipv4(base, "zz.example.com", DNS_QUERY_NO_SEARCH, + dns_server_gethostbyname_cb, NULL); + evdns_base_resolve_ipv6(base, "zz.example.com", DNS_QUERY_NO_SEARCH, + dns_server_gethostbyname_cb, NULL); + resolve_addr.s_addr = htonl(0xc0a80b0bUL); /* 192.168.11.11 */ + evdns_base_resolve_reverse(base, &resolve_addr, 0, + dns_server_gethostbyname_cb, NULL); + memcpy(resolve_addr6.s6_addr, + "\xff\xf0\x00\x00\x00\x00\xaa\xaa" + "\x11\x11\x00\x00\x00\x00\xef\xef", 16); + evdns_base_resolve_reverse_ipv6(base, &resolve_addr6, 0, + dns_server_gethostbyname_cb, (void*)6); + + req = evdns_base_resolve_ipv4(base, + "drop.example.com", DNS_QUERY_NO_SEARCH, + dns_server_gethostbyname_cb, (void*)(char*)90909); + + evdns_cancel_request(base, req); + + event_dispatch(); + + tt_assert(dns_got_cancel); + test_ok = dns_ok; + +end: + if (port) + evdns_close_server_port(port); + if (sock >= 0) + evutil_closesocket(sock); + if (base) + evdns_base_free(base, 0); +} + +static int n_replies_left; +static struct event_base *exit_base; +static struct evdns_server_port *exit_port; + +struct generic_dns_callback_result { + int result; + char type; + int count; + int ttl; + size_t addrs_len; + void *addrs; + char addrs_buf[256]; +}; + +static void +generic_dns_callback(int result, char type, int count, int ttl, void *addresses, + void *arg) +{ + size_t len; + struct generic_dns_callback_result *res = arg; + res->result = result; + res->type = type; + res->count = count; + res->ttl = ttl; + + if (type == DNS_IPv4_A) + len = count * 4; + else if (type == DNS_IPv6_AAAA) + len = count * 16; + else if (type == DNS_PTR) + len = strlen(addresses)+1; + else { + res->addrs_len = len = 0; + res->addrs = NULL; + } + if (len) { + res->addrs_len = len; + if (len > 256) + len = 256; + memcpy(res->addrs_buf, addresses, len); + res->addrs = res->addrs_buf; + } + + --n_replies_left; + if (n_replies_left == 0) { + if (exit_port) { + evdns_close_server_port(exit_port); + exit_port = NULL; + } else + event_base_loopexit(exit_base, NULL); + } +} + +static struct regress_dns_server_table search_table[] = { + { "host.a.example.com", "err", "3", 0, 0 }, + { "host.b.example.com", "err", "3", 0, 0 }, + { "host.c.example.com", "A", "11.22.33.44", 0, 0 }, + { "host2.a.example.com", "err", "3", 0, 0 }, + { "host2.b.example.com", "A", "200.100.0.100", 0, 0 }, + { "host2.c.example.com", "err", "3", 0, 0 }, + { "hostn.a.example.com", "errsoa", "0", 0, 0 }, + { "hostn.b.example.com", "errsoa", "3", 0, 0 }, + { "hostn.c.example.com", "err", "0", 0, 0 }, + + { "host", "err", "3", 0, 0 }, + { "host2", "err", "3", 0, 0 }, + { "*", "err", "3", 0, 0 }, + { NULL, NULL, NULL, 0, 0 } +}; +static void +dns_search_test_impl(void *arg, int lower) +{ + struct regress_dns_server_table table[ARRAY_SIZE(search_table)]; + struct basic_test_data *data = arg; + struct event_base *base = data->base; + struct evdns_base *dns = NULL; + ev_uint16_t portnum = 0; + char buf[64]; + + struct generic_dns_callback_result r[8]; + size_t i; + + for (i = 0; i < ARRAY_SIZE(table); ++i) { + table[i] = search_table[i]; + table[i].lower = lower; + } + + tt_assert(regress_dnsserver(base, &portnum, table)); + evutil_snprintf(buf, sizeof(buf), "127.0.0.1:%d", (int)portnum); + + dns = evdns_base_new(base, 0); + tt_assert(!evdns_base_nameserver_ip_add(dns, buf)); + + evdns_base_search_add(dns, "a.example.com"); + evdns_base_search_add(dns, "b.example.com"); + evdns_base_search_add(dns, "c.example.com"); + + n_replies_left = ARRAY_SIZE(r); + exit_base = base; + + evdns_base_resolve_ipv4(dns, "host", 0, generic_dns_callback, &r[0]); + evdns_base_resolve_ipv4(dns, "host2", 0, generic_dns_callback, &r[1]); + evdns_base_resolve_ipv4(dns, "host", DNS_NO_SEARCH, generic_dns_callback, &r[2]); + evdns_base_resolve_ipv4(dns, "host2", DNS_NO_SEARCH, generic_dns_callback, &r[3]); + evdns_base_resolve_ipv4(dns, "host3", 0, generic_dns_callback, &r[4]); + evdns_base_resolve_ipv4(dns, "hostn.a.example.com", DNS_NO_SEARCH, generic_dns_callback, &r[5]); + evdns_base_resolve_ipv4(dns, "hostn.b.example.com", DNS_NO_SEARCH, generic_dns_callback, &r[6]); + evdns_base_resolve_ipv4(dns, "hostn.c.example.com", DNS_NO_SEARCH, generic_dns_callback, &r[7]); + + event_base_dispatch(base); + + tt_int_op(r[0].type, ==, DNS_IPv4_A); + tt_int_op(r[0].count, ==, 1); + tt_int_op(((ev_uint32_t*)r[0].addrs)[0], ==, htonl(0x0b16212c)); + tt_int_op(r[1].type, ==, DNS_IPv4_A); + tt_int_op(r[1].count, ==, 1); + tt_int_op(((ev_uint32_t*)r[1].addrs)[0], ==, htonl(0xc8640064)); + tt_int_op(r[2].result, ==, DNS_ERR_NOTEXIST); + tt_int_op(r[3].result, ==, DNS_ERR_NOTEXIST); + tt_int_op(r[4].result, ==, DNS_ERR_NOTEXIST); + tt_int_op(r[5].result, ==, DNS_ERR_NODATA); + tt_int_op(r[5].ttl, ==, 42); + tt_int_op(r[6].result, ==, DNS_ERR_NOTEXIST); + tt_int_op(r[6].ttl, ==, 42); + tt_int_op(r[7].result, ==, DNS_ERR_NODATA); + tt_int_op(r[7].ttl, ==, 0); + +end: + if (dns) + evdns_base_free(dns, 0); + + regress_clean_dnsserver(); +} +static void +dns_search_empty_test(void *arg) +{ + struct basic_test_data *data = arg; + struct event_base *base = data->base; + struct evdns_base *dns = NULL; + + dns = evdns_base_new(base, 0); + + evdns_base_search_add(dns, "whatever.example.com"); + + n_replies_left = 1; + exit_base = base; + + tt_ptr_op(evdns_base_resolve_ipv4(dns, "", 0, generic_dns_callback, NULL), ==, NULL); + +end: + if (dns) + evdns_base_free(dns, 0); +} +static void dns_search_test(void *arg) { dns_search_test_impl(arg, 0); } +static void dns_search_lower_test(void *arg) { dns_search_test_impl(arg, 1); } + +static int request_count = 0; +static struct evdns_request *current_req = NULL; + +static void +search_cancel_server_cb(struct evdns_server_request *req, void *data) +{ + const char *question; + + if (req->nquestions != 1) + TT_DIE(("Only handling one question at a time; got %d", + req->nquestions)); + + question = req->questions[0]->name; + + TT_BLATHER(("got question, %s", question)); + + tt_assert(request_count > 0); + tt_assert(!evdns_server_request_respond(req, 3)); + + if (!--request_count) + evdns_cancel_request(NULL, current_req); + +end: + ; +} + +static void +dns_search_cancel_test(void *arg) +{ + struct basic_test_data *data = arg; + struct event_base *base = data->base; + struct evdns_base *dns = NULL; + struct evdns_server_port *port = NULL; + ev_uint16_t portnum = 0; + struct generic_dns_callback_result r1; + char buf[64]; + + port = regress_get_dnsserver(base, &portnum, NULL, + search_cancel_server_cb, NULL); + tt_assert(port); + evutil_snprintf(buf, sizeof(buf), "127.0.0.1:%d", (int)portnum); + + dns = evdns_base_new(base, 0); + tt_assert(!evdns_base_nameserver_ip_add(dns, buf)); + + evdns_base_search_add(dns, "a.example.com"); + evdns_base_search_add(dns, "b.example.com"); + evdns_base_search_add(dns, "c.example.com"); + evdns_base_search_add(dns, "d.example.com"); + + exit_base = base; + request_count = 3; + n_replies_left = 1; + + current_req = evdns_base_resolve_ipv4(dns, "host", 0, + generic_dns_callback, &r1); + event_base_dispatch(base); + + tt_int_op(r1.result, ==, DNS_ERR_CANCEL); + +end: + if (port) + evdns_close_server_port(port); + if (dns) + evdns_base_free(dns, 0); +} + +static void +fail_server_cb(struct evdns_server_request *req, void *data) +{ + const char *question; + int *count = data; + struct in_addr in; + + /* Drop the first N requests that we get. */ + if (*count > 0) { + --*count; + tt_want(! evdns_server_request_drop(req)); + return; + } + + if (req->nquestions != 1) + TT_DIE(("Only handling one question at a time; got %d", + req->nquestions)); + + question = req->questions[0]->name; + + if (!evutil_ascii_strcasecmp(question, "google.com")) { + /* Detect a probe, and get out of the loop. */ + event_base_loopexit(exit_base, NULL); + } + + tt_assert(evutil_inet_pton(AF_INET, "16.32.64.128", &in)); + evdns_server_request_add_a_reply(req, question, 1, &in.s_addr, + 100); + tt_assert(! evdns_server_request_respond(req, 0)) + return; +end: + tt_want(! evdns_server_request_drop(req)); +} + +static void +dns_retry_test_impl(void *arg, int flags) +{ + struct basic_test_data *data = arg; + struct event_base *base = data->base; + struct evdns_server_port *port = NULL; + struct evdns_base *dns = NULL; + int drop_count = 2; + ev_uint16_t portnum = 0; + char buf[64]; + + struct generic_dns_callback_result r1; + + port = regress_get_dnsserver(base, &portnum, NULL, + fail_server_cb, &drop_count); + tt_assert(port); + evutil_snprintf(buf, sizeof(buf), "127.0.0.1:%d", (int)portnum); + + dns = evdns_base_new(base, flags); + tt_assert(!evdns_base_nameserver_ip_add(dns, buf)); + tt_assert(! evdns_base_set_option(dns, "timeout", "0.2")); + tt_assert(! evdns_base_set_option(dns, "max-timeouts:", "10")); + tt_assert(! evdns_base_set_option(dns, "initial-probe-timeout", "0.1")); + + evdns_base_resolve_ipv4(dns, "host.example.com", 0, + generic_dns_callback, &r1); + + n_replies_left = 1; + exit_base = base; + + event_base_dispatch(base); + + tt_int_op(drop_count, ==, 0); + + tt_int_op(r1.type, ==, DNS_IPv4_A); + tt_int_op(r1.count, ==, 1); + tt_int_op(((ev_uint32_t*)r1.addrs)[0], ==, htonl(0x10204080)); + + /* Now try again, but this time have the server get treated as + * failed, so we can send it a test probe. */ + drop_count = 4; + tt_assert(! evdns_base_set_option(dns, "max-timeouts:", "2")); + tt_assert(! evdns_base_set_option(dns, "attempts:", "3")); + memset(&r1, 0, sizeof(r1)); + + evdns_base_resolve_ipv4(dns, "host.example.com", 0, + generic_dns_callback, &r1); + + n_replies_left = 2; + + /* This will run until it answers the "google.com" probe request. */ + event_base_dispatch(base); + + /* We'll treat the server as failed here. */ + tt_int_op(r1.result, ==, DNS_ERR_TIMEOUT); + + /* It should work this time. */ + tt_int_op(drop_count, ==, 0); + evdns_base_resolve_ipv4(dns, "host.example.com", 0, + generic_dns_callback, &r1); + + event_base_dispatch(base); + tt_int_op(r1.result, ==, DNS_ERR_NONE); + tt_int_op(r1.type, ==, DNS_IPv4_A); + tt_int_op(r1.count, ==, 1); + tt_int_op(((ev_uint32_t*)r1.addrs)[0], ==, htonl(0x10204080)); + +end: + if (dns) + evdns_base_free(dns, 0); + if (port) + evdns_close_server_port(port); +} +static void +dns_retry_test(void *arg) +{ + dns_retry_test_impl(arg, 0); +} +static void +dns_retry_disable_when_inactive_test(void *arg) +{ + dns_retry_test_impl(arg, EVDNS_BASE_DISABLE_WHEN_INACTIVE); +} + +static struct regress_dns_server_table internal_error_table[] = { + /* Error 4 (NOTIMPL) makes us reissue the request to another server + if we can. + + XXXX we should reissue under a much wider set of circumstances! + */ + { "foof.example.com", "err", "4", 0, 0 }, + { NULL, NULL, NULL, 0, 0 } +}; + +static struct regress_dns_server_table reissue_table[] = { + { "foof.example.com", "A", "240.15.240.15", 0, 0 }, + { NULL, NULL, NULL, 0, 0 } +}; + +static void +dns_reissue_test_impl(void *arg, int flags) +{ + struct basic_test_data *data = arg; + struct event_base *base = data->base; + struct evdns_server_port *port1 = NULL, *port2 = NULL; + struct evdns_base *dns = NULL; + struct generic_dns_callback_result r1; + ev_uint16_t portnum1 = 0, portnum2=0; + char buf1[64], buf2[64]; + + port1 = regress_get_dnsserver(base, &portnum1, NULL, + regress_dns_server_cb, internal_error_table); + tt_assert(port1); + port2 = regress_get_dnsserver(base, &portnum2, NULL, + regress_dns_server_cb, reissue_table); + tt_assert(port2); + evutil_snprintf(buf1, sizeof(buf1), "127.0.0.1:%d", (int)portnum1); + evutil_snprintf(buf2, sizeof(buf2), "127.0.0.1:%d", (int)portnum2); + + dns = evdns_base_new(base, flags); + tt_assert(!evdns_base_nameserver_ip_add(dns, buf1)); + tt_assert(! evdns_base_set_option(dns, "timeout:", "0.3")); + tt_assert(! evdns_base_set_option(dns, "max-timeouts:", "2")); + tt_assert(! evdns_base_set_option(dns, "attempts:", "5")); + + memset(&r1, 0, sizeof(r1)); + evdns_base_resolve_ipv4(dns, "foof.example.com", 0, + generic_dns_callback, &r1); + + /* Add this after, so that we are sure to get a reissue. */ + tt_assert(!evdns_base_nameserver_ip_add(dns, buf2)); + + n_replies_left = 1; + exit_base = base; + + event_base_dispatch(base); + tt_int_op(r1.result, ==, DNS_ERR_NONE); + tt_int_op(r1.type, ==, DNS_IPv4_A); + tt_int_op(r1.count, ==, 1); + tt_int_op(((ev_uint32_t*)r1.addrs)[0], ==, htonl(0xf00ff00f)); + + /* Make sure we dropped at least once. */ + tt_int_op(internal_error_table[0].seen, >, 0); + +end: + if (dns) + evdns_base_free(dns, 0); + if (port1) + evdns_close_server_port(port1); + if (port2) + evdns_close_server_port(port2); +} +static void +dns_reissue_test(void *arg) +{ + dns_reissue_test_impl(arg, 0); +} +static void +dns_reissue_disable_when_inactive_test(void *arg) +{ + dns_reissue_test_impl(arg, EVDNS_BASE_DISABLE_WHEN_INACTIVE); +} + +#if 0 +static void +dumb_bytes_fn(char *p, size_t n) +{ + unsigned i; + /* This gets us 6 bits of entropy per transaction ID, which means we + * will have probably have collisions and need to pick again. */ + for (i=0;ibase; + struct evdns_base *dns = NULL; + struct evdns_server_port *dns_port = NULL; + ev_uint16_t portnum = 0; + char buf[64]; + int disable_when_inactive = flags & EVDNS_BASE_DISABLE_WHEN_INACTIVE; + + struct generic_dns_callback_result r[20]; + int i; + + dns_port = regress_get_dnsserver(base, &portnum, NULL, + regress_dns_server_cb, reissue_table); + tt_assert(dns_port); + if (disable_when_inactive) { + exit_port = dns_port; + } + + evutil_snprintf(buf, sizeof(buf), "127.0.0.1:%d", (int)portnum); + + dns = evdns_base_new(base, flags); + tt_assert(!evdns_base_nameserver_ip_add(dns, buf)); + tt_assert(! evdns_base_set_option(dns, "max-inflight:", "3")); + tt_assert(! evdns_base_set_option(dns, "randomize-case:", "0")); + + for (i=0;i<20;++i) + evdns_base_resolve_ipv4(dns, "foof.example.com", 0, generic_dns_callback, &r[i]); + + n_replies_left = 20; + exit_base = base; + + event_base_dispatch(base); + + for (i=0;i<20;++i) { + tt_int_op(r[i].type, ==, DNS_IPv4_A); + tt_int_op(r[i].count, ==, 1); + tt_int_op(((ev_uint32_t*)r[i].addrs)[0], ==, htonl(0xf00ff00f)); + } + +end: + if (dns) + evdns_base_free(dns, 0); + if (exit_port) { + evdns_close_server_port(exit_port); + exit_port = NULL; + } else if (! disable_when_inactive) { + evdns_close_server_port(dns_port); + } +} + +static void +dns_inflight_test(void *arg) +{ + dns_inflight_test_impl(arg, 0); +} + +static void +dns_disable_when_inactive_test(void *arg) +{ + dns_inflight_test_impl(arg, EVDNS_BASE_DISABLE_WHEN_INACTIVE); +} + +static void +dns_disable_when_inactive_no_ns_test(void *arg) +{ + struct basic_test_data *data = arg; + struct event_base *base = data->base, *inactive_base; + struct evdns_base *dns = NULL; + ev_uint16_t portnum = 0; + char buf[64]; + struct generic_dns_callback_result r; + + inactive_base = event_base_new(); + tt_assert(inactive_base); + + /** Create dns server with inactive base, to avoid replying to clients */ + tt_assert(regress_dnsserver(inactive_base, &portnum, search_table)); + evutil_snprintf(buf, sizeof(buf), "127.0.0.1:%d", (int)portnum); + + dns = evdns_base_new(base, EVDNS_BASE_DISABLE_WHEN_INACTIVE); + tt_assert(!evdns_base_nameserver_ip_add(dns, buf)); + tt_assert(! evdns_base_set_option(dns, "timeout:", "0.1")); + + evdns_base_resolve_ipv4(dns, "foof.example.com", 0, generic_dns_callback, &r); + n_replies_left = 1; + exit_base = base; + + event_base_dispatch(base); + + tt_int_op(n_replies_left, ==, 0); + + tt_int_op(r.result, ==, DNS_ERR_TIMEOUT); + tt_int_op(r.count, ==, 0); + tt_ptr_op(r.addrs, ==, NULL); + +end: + if (dns) + evdns_base_free(dns, 0); + regress_clean_dnsserver(); + if (inactive_base) + event_base_free(inactive_base); +} + +static void +dns_initialize_nameservers_test(void *arg) +{ + struct basic_test_data *data = arg; + struct event_base *base = data->base; + struct evdns_base *dns = NULL; + + dns = evdns_base_new(base, 0); + tt_assert(dns); + tt_int_op(evdns_base_get_nameserver_addr(dns, 0, NULL, 0), ==, -1); + evdns_base_free(dns, 0); + + dns = evdns_base_new(base, EVDNS_BASE_INITIALIZE_NAMESERVERS); + tt_assert(dns); + tt_int_op(evdns_base_get_nameserver_addr(dns, 0, NULL, 0), ==, sizeof(struct sockaddr)); + +end: + if (dns) + evdns_base_free(dns, 0); +} +#ifndef _WIN32 +#define RESOLV_FILE "empty-resolv.conf" +static void +dns_nameservers_no_default_test(void *arg) +{ + struct basic_test_data *data = arg; + struct event_base *base = data->base; + struct evdns_base *dns = NULL; + int ok = access(RESOLV_FILE, R_OK); + + tt_assert(ok); + + dns = evdns_base_new(base, 0); + tt_assert(dns); + tt_int_op(evdns_base_get_nameserver_addr(dns, 0, NULL, 0), ==, -1); + + /* We cannot test + * EVDNS_BASE_INITIALIZE_NAMESERVERS|EVDNS_BASE_NAMESERVERS_NO_DEFAULT + * because we cannot mock "/etc/resolv.conf" (yet). */ + + evdns_base_resolv_conf_parse(dns, + DNS_OPTIONS_ALL|DNS_OPTION_NAMESERVERS_NO_DEFAULT, RESOLV_FILE); + tt_int_op(evdns_base_get_nameserver_addr(dns, 0, NULL, 0), ==, -1); + + evdns_base_resolv_conf_parse(dns, DNS_OPTIONS_ALL, RESOLV_FILE); + tt_int_op(evdns_base_get_nameserver_addr(dns, 0, NULL, 0), ==, sizeof(struct sockaddr)); + +end: + if (dns) + evdns_base_free(dns, 0); +} +#endif + +/* === Test for bufferevent_socket_connect_hostname */ + +static int total_connected_or_failed = 0; +static int total_n_accepted = 0; +static struct event_base *be_connect_hostname_base = NULL; + +/* Implements a DNS server for the connect_hostname test and the + * getaddrinfo_async test */ +static void +be_getaddrinfo_server_cb(struct evdns_server_request *req, void *data) +{ + int i; + int *n_got_p=data; + int added_any=0; + ++*n_got_p; + + for (i = 0; i < req->nquestions; ++i) { + const int qtype = req->questions[i]->type; + const int qclass = req->questions[i]->dns_question_class; + const char *qname = req->questions[i]->name; + struct in_addr ans; + struct in6_addr ans6; + memset(&ans6, 0, sizeof(ans6)); + + TT_BLATHER(("Got question about %s, type=%d", qname, qtype)); + + if (qtype == EVDNS_TYPE_A && + qclass == EVDNS_CLASS_INET && + !evutil_ascii_strcasecmp(qname, "nobodaddy.example.com")) { + ans.s_addr = htonl(0x7f000001); + evdns_server_request_add_a_reply(req, qname, + 1, &ans.s_addr, 2000); + added_any = 1; + } else if (!evutil_ascii_strcasecmp(qname, + "nosuchplace.example.com")) { + /* ok, just say notfound. */ + } else if (!evutil_ascii_strcasecmp(qname, + "both.example.com")) { + if (qtype == EVDNS_TYPE_A) { + ans.s_addr = htonl(0x50502020); + evdns_server_request_add_a_reply(req, qname, + 1, &ans.s_addr, 2000); + added_any = 1; + } else if (qtype == EVDNS_TYPE_AAAA) { + ans6.s6_addr[0] = 0x80; + ans6.s6_addr[1] = 0xff; + ans6.s6_addr[14] = 0xbb; + ans6.s6_addr[15] = 0xbb; + evdns_server_request_add_aaaa_reply(req, qname, + 1, &ans6.s6_addr, 2000); + added_any = 1; + } + evdns_server_request_add_cname_reply(req, qname, + "both-canonical.example.com", 1000); + } else if (!evutil_ascii_strcasecmp(qname, + "v4only.example.com") || + !evutil_ascii_strcasecmp(qname, "v4assert.example.com")) { + if (qtype == EVDNS_TYPE_A) { + ans.s_addr = htonl(0x12345678); + evdns_server_request_add_a_reply(req, qname, + 1, &ans.s_addr, 2000); + added_any = 1; + } else if (!evutil_ascii_strcasecmp(qname, + "v4assert.example.com")) { + TT_FAIL(("Got an AAAA request for v4assert")); + } + } else if (!evutil_ascii_strcasecmp(qname, + "v6only.example.com") || + !evutil_ascii_strcasecmp(qname, "v6assert.example.com")) { + if (qtype == EVDNS_TYPE_AAAA) { + ans6.s6_addr[0] = 0x0b; + ans6.s6_addr[1] = 0x0b; + ans6.s6_addr[14] = 0xf0; + ans6.s6_addr[15] = 0x0d; + evdns_server_request_add_aaaa_reply(req, qname, + 1, &ans6.s6_addr, 2000); + added_any = 1; + } else if (!evutil_ascii_strcasecmp(qname, + "v6assert.example.com")) { + TT_FAIL(("Got a A request for v6assert")); + } + } else if (!evutil_ascii_strcasecmp(qname, + "v6timeout.example.com")) { + if (qtype == EVDNS_TYPE_A) { + ans.s_addr = htonl(0xabcdef01); + evdns_server_request_add_a_reply(req, qname, + 1, &ans.s_addr, 2000); + added_any = 1; + } else if (qtype == EVDNS_TYPE_AAAA) { + /* Let the v6 request time out.*/ + evdns_server_request_drop(req); + return; + } + } else if (!evutil_ascii_strcasecmp(qname, + "v4timeout.example.com")) { + if (qtype == EVDNS_TYPE_AAAA) { + ans6.s6_addr[0] = 0x0a; + ans6.s6_addr[1] = 0x0a; + ans6.s6_addr[14] = 0xff; + ans6.s6_addr[15] = 0x01; + evdns_server_request_add_aaaa_reply(req, qname, + 1, &ans6.s6_addr, 2000); + added_any = 1; + } else if (qtype == EVDNS_TYPE_A) { + /* Let the v4 request time out.*/ + evdns_server_request_drop(req); + return; + } + } else if (!evutil_ascii_strcasecmp(qname, + "v6timeout-nonexist.example.com")) { + if (qtype == EVDNS_TYPE_A) { + /* Fall through, give an nexist. */ + } else if (qtype == EVDNS_TYPE_AAAA) { + /* Let the v6 request time out.*/ + evdns_server_request_drop(req); + return; + } + } else if (!evutil_ascii_strcasecmp(qname, + "all-timeout.example.com")) { + /* drop all requests */ + evdns_server_request_drop(req); + return; + } else { + TT_GRIPE(("Got weird request for %s",qname)); + } + } + if (added_any) { + TT_BLATHER(("answering")); + evdns_server_request_respond(req, 0); + } else { + TT_BLATHER(("saying nexist.")); + evdns_server_request_respond(req, 3); + } +} + +/* Implements a listener for connect_hostname test. */ +static void +nil_accept_cb(struct evconnlistener *l, evutil_socket_t fd, struct sockaddr *s, + int socklen, void *arg) +{ + int *p = arg; + (*p)++; + ++total_n_accepted; + /* don't do anything with the socket; let it close when we exit() */ + if (total_n_accepted >= 3 && total_connected_or_failed >= 5) + event_base_loopexit(be_connect_hostname_base, + NULL); +} + +struct be_conn_hostname_result { + int dnserr; + int what; +}; + +/* Bufferevent event callback for the connect_hostname test: remembers what + * event we got. */ +static void +be_connect_hostname_event_cb(struct bufferevent *bev, short what, void *ctx) +{ + struct be_conn_hostname_result *got = ctx; + + if (got->what) { + TT_FAIL(("Two events on one bufferevent. %d,%d", + got->what, (int)what)); + } + + TT_BLATHER(("Got a bufferevent event %d", what)); + got->what = what; + + if ((what & BEV_EVENT_CONNECTED) || (what & BEV_EVENT_ERROR)) { + int expected = 3; + int r = bufferevent_socket_get_dns_error(bev); + + if (r) { + got->dnserr = r; + TT_BLATHER(("DNS error %d: %s", r, + evutil_gai_strerror(r))); + } + ++total_connected_or_failed; + TT_BLATHER(("Got %d connections or errors.", total_connected_or_failed)); + + /** emfile test */ + if (errno == EMFILE) { + expected = 0; + } + + if (total_n_accepted >= expected && total_connected_or_failed >= 5) + event_base_loopexit(be_connect_hostname_base, + NULL); + } +} + +static void +test_bufferevent_connect_hostname(void *arg) +{ + struct basic_test_data *data = arg; + struct evconnlistener *listener = NULL; + struct bufferevent *be[5]; + struct be_conn_hostname_result be_outcome[ARRAY_SIZE(be)]; + int expect_err; + struct evdns_base *dns=NULL; + struct evdns_server_port *port=NULL; + struct sockaddr_in sin; + int listener_port=-1; + ev_uint16_t dns_port=0; + int n_accept=0, n_dns=0; + char buf[128]; + int emfile = data->setup_data && !strcmp(data->setup_data, "emfile"); + unsigned i; + int ret; + + be_connect_hostname_base = data->base; + + /* Bind an address and figure out what port it's on. */ + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(0x7f000001); /* 127.0.0.1 */ + sin.sin_port = 0; + listener = evconnlistener_new_bind(data->base, nil_accept_cb, + &n_accept, + LEV_OPT_REUSEABLE|LEV_OPT_CLOSE_ON_EXEC, + -1, (struct sockaddr *)&sin, sizeof(sin)); + tt_assert(listener); + listener_port = regress_get_socket_port( + evconnlistener_get_fd(listener)); + + port = regress_get_dnsserver(data->base, &dns_port, NULL, + be_getaddrinfo_server_cb, &n_dns); + tt_assert(port); + tt_int_op(dns_port, >=, 0); + + /* Start an evdns_base that uses the server as its resolver. */ + dns = evdns_base_new(data->base, 0); + evutil_snprintf(buf, sizeof(buf), "127.0.0.1:%d", (int)dns_port); + evdns_base_nameserver_ip_add(dns, buf); + +#ifdef EVENT__HAVE_SETRLIMIT + if (emfile) { + int fd = socket(AF_INET, SOCK_STREAM, 0); + struct rlimit file = { fd, fd }; + + tt_int_op(fd, >=, 0); + tt_assert(!close(fd)); + + tt_assert(!setrlimit(RLIMIT_NOFILE, &file)); + } +#endif + + /* Now, finally, at long last, launch the bufferevents. One should do + * a failing lookup IP, one should do a successful lookup by IP, + * and one should do a successful lookup by hostname. */ + for (i = 0; i < ARRAY_SIZE(be); ++i) { + memset(&be_outcome[i], 0, sizeof(be_outcome[i])); + be[i] = bufferevent_socket_new(data->base, -1, BEV_OPT_CLOSE_ON_FREE); + bufferevent_setcb(be[i], NULL, NULL, be_connect_hostname_event_cb, + &be_outcome[i]); + } + + /* Use the blocking resolver. This one will fail if your resolver + * can't resolve localhost to 127.0.0.1 */ + tt_assert(!bufferevent_socket_connect_hostname(be[3], NULL, AF_INET, + "localhost", listener_port)); + /* Use the blocking resolver with a nonexistent hostname. */ + tt_assert(!bufferevent_socket_connect_hostname(be[4], NULL, AF_INET, + "nonesuch.nowhere.example.com", 80)); + { + /* The blocking resolver will use the system nameserver, which + * might tell us anything. (Yes, some twits even pretend that + * example.com is real.) Let's see what answer to expect. */ + struct evutil_addrinfo hints, *ai = NULL; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + expect_err = evutil_getaddrinfo( + "nonesuch.nowhere.example.com", "80", &hints, &ai); + } + /* Launch an async resolve that will fail. */ + tt_assert(!bufferevent_socket_connect_hostname(be[0], dns, AF_INET, + "nosuchplace.example.com", listener_port)); + /* Connect to the IP without resolving. */ + tt_assert(!bufferevent_socket_connect_hostname(be[1], dns, AF_INET, + "127.0.0.1", listener_port)); + /* Launch an async resolve that will succeed. */ + tt_assert(!bufferevent_socket_connect_hostname(be[2], dns, AF_INET, + "nobodaddy.example.com", listener_port)); + + ret = event_base_dispatch(data->base); +#ifdef __sun__ + if (emfile && !strcmp(event_base_get_method(data->base), "devpoll")) { + tt_int_op(ret, ==, -1); + /** DP_POLL failed */ + tt_skip(); + } else +#endif + { + tt_int_op(ret, ==, 0); + } + + tt_int_op(be_outcome[0].what, ==, BEV_EVENT_ERROR); + tt_int_op(be_outcome[0].dnserr, ==, EVUTIL_EAI_NONAME); + tt_int_op(be_outcome[1].what, ==, !emfile ? BEV_EVENT_CONNECTED : BEV_EVENT_ERROR); + tt_int_op(be_outcome[1].dnserr, ==, 0); + tt_int_op(be_outcome[2].what, ==, !emfile ? BEV_EVENT_CONNECTED : BEV_EVENT_ERROR); + tt_int_op(be_outcome[2].dnserr, ==, 0); + tt_int_op(be_outcome[3].what, ==, !emfile ? BEV_EVENT_CONNECTED : BEV_EVENT_ERROR); + if (!emfile) { + tt_int_op(be_outcome[3].dnserr, ==, 0); + } else { + tt_int_op(be_outcome[3].dnserr, !=, 0); + } + if (expect_err) { + tt_int_op(be_outcome[4].what, ==, BEV_EVENT_ERROR); + tt_int_op(be_outcome[4].dnserr, ==, expect_err); + } + + if (emfile) { + tt_int_op(n_accept, ==, 0); + } else { + tt_int_op(n_accept, ==, 3); + } + tt_int_op(n_dns, ==, 2); + +end: + if (listener) + evconnlistener_free(listener); + if (port) + evdns_close_server_port(port); + if (dns) + evdns_base_free(dns, 0); + for (i = 0; i < ARRAY_SIZE(be); ++i) { + if (be[i]) + bufferevent_free(be[i]); + } +} + + +struct gai_outcome { + int err; + struct evutil_addrinfo *ai; +}; + +static int n_gai_results_pending = 0; +static struct event_base *exit_base_on_no_pending_results = NULL; + +static void +gai_cb(int err, struct evutil_addrinfo *res, void *ptr) +{ + struct gai_outcome *go = ptr; + go->err = err; + go->ai = res; + if (--n_gai_results_pending <= 0 && exit_base_on_no_pending_results) + event_base_loopexit(exit_base_on_no_pending_results, NULL); + if (n_gai_results_pending < 900) + TT_BLATHER(("Got an answer; expecting %d more.", + n_gai_results_pending)); +} + +static void +cancel_gai_cb(evutil_socket_t fd, short what, void *ptr) +{ + struct evdns_getaddrinfo_request *r = ptr; + evdns_getaddrinfo_cancel(r); +} + +static void +test_getaddrinfo_async(void *arg) +{ + struct basic_test_data *data = arg; + struct evutil_addrinfo hints, *a; + struct gai_outcome local_outcome; + struct gai_outcome a_out[12]; + unsigned i; + struct evdns_getaddrinfo_request *r; + char buf[128]; + struct evdns_server_port *port = NULL; + ev_uint16_t dns_port = 0; + int n_dns_questions = 0; + struct evdns_base *dns_base; + + memset(a_out, 0, sizeof(a_out)); + memset(&local_outcome, 0, sizeof(local_outcome)); + + dns_base = evdns_base_new(data->base, 0); + tt_assert(dns_base); + + /* for localhost */ + evdns_base_load_hosts(dns_base, NULL); + + tt_assert(! evdns_base_set_option(dns_base, "timeout", "0.3")); + tt_assert(! evdns_base_set_option(dns_base, "getaddrinfo-allow-skew", "0.2")); + + n_gai_results_pending = 10000; /* don't think about exiting yet. */ + + /* 1. Try some cases that will never hit the asynchronous resolver. */ + /* 1a. Simple case with a symbolic service name */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + memset(&local_outcome, 0, sizeof(local_outcome)); + r = evdns_getaddrinfo(dns_base, "1.2.3.4", "http", + &hints, gai_cb, &local_outcome); + tt_assert(! r); + if (!local_outcome.err) { + tt_ptr_op(local_outcome.ai,!=,NULL); + test_ai_eq(local_outcome.ai, "1.2.3.4:80", SOCK_STREAM, IPPROTO_TCP); + evutil_freeaddrinfo(local_outcome.ai); + local_outcome.ai = NULL; + } else { + TT_BLATHER(("Apparently we have no getservbyname.")); + } + + /* 1b. EVUTIL_AI_NUMERICHOST is set */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_flags = EVUTIL_AI_NUMERICHOST; + memset(&local_outcome, 0, sizeof(local_outcome)); + r = evdns_getaddrinfo(dns_base, "www.google.com", "80", + &hints, gai_cb, &local_outcome); + tt_ptr_op(r,==,NULL); + tt_int_op(local_outcome.err,==,EVUTIL_EAI_NONAME); + tt_ptr_op(local_outcome.ai,==,NULL); + + /* 1c. We give a numeric address (ipv6) */ + memset(&hints, 0, sizeof(hints)); + memset(&local_outcome, 0, sizeof(local_outcome)); + hints.ai_family = PF_UNSPEC; + hints.ai_protocol = IPPROTO_TCP; + r = evdns_getaddrinfo(dns_base, "f::f", "8008", + &hints, gai_cb, &local_outcome); + tt_assert(!r); + tt_int_op(local_outcome.err,==,0); + tt_assert(local_outcome.ai); + tt_ptr_op(local_outcome.ai->ai_next,==,NULL); + test_ai_eq(local_outcome.ai, "[f::f]:8008", SOCK_STREAM, IPPROTO_TCP); + evutil_freeaddrinfo(local_outcome.ai); + local_outcome.ai = NULL; + + /* 1d. We give a numeric address (ipv4) */ + memset(&hints, 0, sizeof(hints)); + memset(&local_outcome, 0, sizeof(local_outcome)); + hints.ai_family = PF_UNSPEC; + r = evdns_getaddrinfo(dns_base, "5.6.7.8", NULL, + &hints, gai_cb, &local_outcome); + tt_assert(!r); + tt_int_op(local_outcome.err,==,0); + tt_assert(local_outcome.ai); + a = ai_find_by_protocol(local_outcome.ai, IPPROTO_TCP); + tt_assert(a); + test_ai_eq(a, "5.6.7.8", SOCK_STREAM, IPPROTO_TCP); + a = ai_find_by_protocol(local_outcome.ai, IPPROTO_UDP); + tt_assert(a); + test_ai_eq(a, "5.6.7.8", SOCK_DGRAM, IPPROTO_UDP); + evutil_freeaddrinfo(local_outcome.ai); + local_outcome.ai = NULL; + + /* 1e. nodename is NULL (bind) */ + memset(&hints, 0, sizeof(hints)); + memset(&local_outcome, 0, sizeof(local_outcome)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = EVUTIL_AI_PASSIVE; + r = evdns_getaddrinfo(dns_base, NULL, "9090", + &hints, gai_cb, &local_outcome); + tt_assert(!r); + tt_int_op(local_outcome.err,==,0); + tt_assert(local_outcome.ai); + /* we should get a v4 address of 0.0.0.0... */ + a = ai_find_by_family(local_outcome.ai, PF_INET); + tt_assert(a); + test_ai_eq(a, "0.0.0.0:9090", SOCK_DGRAM, IPPROTO_UDP); + /* ... and a v6 address of ::0 */ + a = ai_find_by_family(local_outcome.ai, PF_INET6); + tt_assert(a); + test_ai_eq(a, "[::]:9090", SOCK_DGRAM, IPPROTO_UDP); + evutil_freeaddrinfo(local_outcome.ai); + local_outcome.ai = NULL; + + /* 1f. nodename is NULL (connect) */ + memset(&hints, 0, sizeof(hints)); + memset(&local_outcome, 0, sizeof(local_outcome)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + r = evdns_getaddrinfo(dns_base, NULL, "2", + &hints, gai_cb, &local_outcome); + tt_assert(!r); + tt_int_op(local_outcome.err,==,0); + tt_assert(local_outcome.ai); + /* we should get a v4 address of 127.0.0.1 .... */ + a = ai_find_by_family(local_outcome.ai, PF_INET); + tt_assert(a); + test_ai_eq(a, "127.0.0.1:2", SOCK_STREAM, IPPROTO_TCP); + /* ... and a v6 address of ::1 */ + a = ai_find_by_family(local_outcome.ai, PF_INET6); + tt_assert(a); + test_ai_eq(a, "[::1]:2", SOCK_STREAM, IPPROTO_TCP); + evutil_freeaddrinfo(local_outcome.ai); + local_outcome.ai = NULL; + + /* 1g. We find localhost immediately. (pf_unspec) */ + memset(&hints, 0, sizeof(hints)); + memset(&local_outcome, 0, sizeof(local_outcome)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + r = evdns_getaddrinfo(dns_base, "LOCALHOST", "80", + &hints, gai_cb, &local_outcome); + tt_assert(!r); + tt_int_op(local_outcome.err,==,0); + tt_assert(local_outcome.ai); + /* we should get a v4 address of 127.0.0.1 .... */ + a = ai_find_by_family(local_outcome.ai, PF_INET); + tt_assert(a); + test_ai_eq(a, "127.0.0.1:80", SOCK_STREAM, IPPROTO_TCP); + /* ... and a v6 address of ::1 */ + a = ai_find_by_family(local_outcome.ai, PF_INET6); + tt_assert(a); + test_ai_eq(a, "[::1]:80", SOCK_STREAM, IPPROTO_TCP); + evutil_freeaddrinfo(local_outcome.ai); + local_outcome.ai = NULL; + + /* 1g. We find localhost immediately. (pf_inet6) */ + memset(&hints, 0, sizeof(hints)); + memset(&local_outcome, 0, sizeof(local_outcome)); + hints.ai_family = PF_INET6; + hints.ai_socktype = SOCK_STREAM; + r = evdns_getaddrinfo(dns_base, "LOCALHOST", "9999", + &hints, gai_cb, &local_outcome); + tt_assert(! r); + tt_int_op(local_outcome.err,==,0); + tt_assert(local_outcome.ai); + a = local_outcome.ai; + test_ai_eq(a, "[::1]:9999", SOCK_STREAM, IPPROTO_TCP); + tt_ptr_op(a->ai_next, ==, NULL); + evutil_freeaddrinfo(local_outcome.ai); + local_outcome.ai = NULL; + + /* 2. Okay, now we can actually test the asynchronous resolver. */ + /* Start a dummy local dns server... */ + port = regress_get_dnsserver(data->base, &dns_port, NULL, + be_getaddrinfo_server_cb, &n_dns_questions); + tt_assert(port); + tt_int_op(dns_port, >=, 0); + /* ... and tell the evdns_base about it. */ + evutil_snprintf(buf, sizeof(buf), "127.0.0.1:%d", dns_port); + evdns_base_nameserver_ip_add(dns_base, buf); + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = EVUTIL_AI_CANONNAME; + /* 0: Request for both.example.com should return both addresses. */ + r = evdns_getaddrinfo(dns_base, "both.example.com", "8000", + &hints, gai_cb, &a_out[0]); + tt_assert(r); + + /* 1: Request for v4only.example.com should return one address. */ + r = evdns_getaddrinfo(dns_base, "v4only.example.com", "8001", + &hints, gai_cb, &a_out[1]); + tt_assert(r); + + /* 2: Request for v6only.example.com should return one address. */ + hints.ai_flags = 0; + r = evdns_getaddrinfo(dns_base, "v6only.example.com", "8002", + &hints, gai_cb, &a_out[2]); + tt_assert(r); + + /* 3: PF_INET request for v4assert.example.com should not generate a + * v6 request. The server will fail the test if it does. */ + hints.ai_family = PF_INET; + r = evdns_getaddrinfo(dns_base, "v4assert.example.com", "8003", + &hints, gai_cb, &a_out[3]); + tt_assert(r); + + /* 4: PF_INET6 request for v6assert.example.com should not generate a + * v4 request. The server will fail the test if it does. */ + hints.ai_family = PF_INET6; + r = evdns_getaddrinfo(dns_base, "v6assert.example.com", "8004", + &hints, gai_cb, &a_out[4]); + tt_assert(r); + + /* 5: PF_INET request for nosuchplace.example.com should give NEXIST. */ + hints.ai_family = PF_INET; + r = evdns_getaddrinfo(dns_base, "nosuchplace.example.com", "8005", + &hints, gai_cb, &a_out[5]); + tt_assert(r); + + /* 6: PF_UNSPEC request for nosuchplace.example.com should give NEXIST. + */ + hints.ai_family = PF_UNSPEC; + r = evdns_getaddrinfo(dns_base, "nosuchplace.example.com", "8006", + &hints, gai_cb, &a_out[6]); + tt_assert(r); + + /* 7: PF_UNSPEC request for v6timeout.example.com should give an ipv4 + * address only. */ + hints.ai_family = PF_UNSPEC; + r = evdns_getaddrinfo(dns_base, "v6timeout.example.com", "8007", + &hints, gai_cb, &a_out[7]); + tt_assert(r); + + /* 8: PF_UNSPEC request for v6timeout-nonexist.example.com should give + * a NEXIST */ + hints.ai_family = PF_UNSPEC; + r = evdns_getaddrinfo(dns_base, "v6timeout-nonexist.example.com", + "8008", &hints, gai_cb, &a_out[8]); + tt_assert(r); + + /* 9: AI_ADDRCONFIG should at least not crash. Can't test it more + * without knowing what kind of internet we have. */ + hints.ai_flags |= EVUTIL_AI_ADDRCONFIG; + r = evdns_getaddrinfo(dns_base, "both.example.com", + "8009", &hints, gai_cb, &a_out[9]); + tt_assert(r); + + /* 10: PF_UNSPEC for v4timeout.example.com should give an ipv6 address + * only. */ + hints.ai_family = PF_UNSPEC; + hints.ai_flags = 0; + r = evdns_getaddrinfo(dns_base, "v4timeout.example.com", "8010", + &hints, gai_cb, &a_out[10]); + tt_assert(r); + + /* 11: timeout.example.com: cancel it after 100 msec. */ + r = evdns_getaddrinfo(dns_base, "all-timeout.example.com", "8011", + &hints, gai_cb, &a_out[11]); + tt_assert(r); + { + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 100*1000; /* 100 msec */ + event_base_once(data->base, -1, EV_TIMEOUT, cancel_gai_cb, + r, &tv); + } + + /* XXXXX There are more tests we could do, including: + + - A test to elicit NODATA. + + */ + + n_gai_results_pending = 12; + exit_base_on_no_pending_results = data->base; + + event_base_dispatch(data->base); + + /* 0: both.example.com */ + tt_int_op(a_out[0].err, ==, 0); + tt_assert(a_out[0].ai); + tt_assert(a_out[0].ai->ai_next); + tt_assert(!a_out[0].ai->ai_next->ai_next); + a = ai_find_by_family(a_out[0].ai, PF_INET); + tt_assert(a); + test_ai_eq(a, "80.80.32.32:8000", SOCK_STREAM, IPPROTO_TCP); + a = ai_find_by_family(a_out[0].ai, PF_INET6); + tt_assert(a); + test_ai_eq(a, "[80ff::bbbb]:8000", SOCK_STREAM, IPPROTO_TCP); + tt_assert(a_out[0].ai->ai_canonname); + tt_str_op(a_out[0].ai->ai_canonname, ==, "both-canonical.example.com"); + + /* 1: v4only.example.com */ + tt_int_op(a_out[1].err, ==, 0); + tt_assert(a_out[1].ai); + tt_assert(! a_out[1].ai->ai_next); + test_ai_eq(a_out[1].ai, "18.52.86.120:8001", SOCK_STREAM, IPPROTO_TCP); + tt_assert(a_out[1].ai->ai_canonname == NULL); + + + /* 2: v6only.example.com */ + tt_int_op(a_out[2].err, ==, 0); + tt_assert(a_out[2].ai); + tt_assert(! a_out[2].ai->ai_next); + test_ai_eq(a_out[2].ai, "[b0b::f00d]:8002", SOCK_STREAM, IPPROTO_TCP); + + /* 3: v4assert.example.com */ + tt_int_op(a_out[3].err, ==, 0); + tt_assert(a_out[3].ai); + tt_assert(! a_out[3].ai->ai_next); + test_ai_eq(a_out[3].ai, "18.52.86.120:8003", SOCK_STREAM, IPPROTO_TCP); + + /* 4: v6assert.example.com */ + tt_int_op(a_out[4].err, ==, 0); + tt_assert(a_out[4].ai); + tt_assert(! a_out[4].ai->ai_next); + test_ai_eq(a_out[4].ai, "[b0b::f00d]:8004", SOCK_STREAM, IPPROTO_TCP); + + /* 5: nosuchplace.example.com (inet) */ + tt_int_op(a_out[5].err, ==, EVUTIL_EAI_NONAME); + tt_assert(! a_out[5].ai); + + /* 6: nosuchplace.example.com (unspec) */ + tt_int_op(a_out[6].err, ==, EVUTIL_EAI_NONAME); + tt_assert(! a_out[6].ai); + + /* 7: v6timeout.example.com */ + tt_int_op(a_out[7].err, ==, 0); + tt_assert(a_out[7].ai); + tt_assert(! a_out[7].ai->ai_next); + test_ai_eq(a_out[7].ai, "171.205.239.1:8007", SOCK_STREAM, IPPROTO_TCP); + + /* 8: v6timeout-nonexist.example.com */ + tt_int_op(a_out[8].err, ==, EVUTIL_EAI_NONAME); + tt_assert(! a_out[8].ai); + + /* 9: both (ADDRCONFIG) */ + tt_int_op(a_out[9].err, ==, 0); + tt_assert(a_out[9].ai); + a = ai_find_by_family(a_out[9].ai, PF_INET); + if (a) + test_ai_eq(a, "80.80.32.32:8009", SOCK_STREAM, IPPROTO_TCP); + else + tt_assert(ai_find_by_family(a_out[9].ai, PF_INET6)); + a = ai_find_by_family(a_out[9].ai, PF_INET6); + if (a) + test_ai_eq(a, "[80ff::bbbb]:8009", SOCK_STREAM, IPPROTO_TCP); + else + tt_assert(ai_find_by_family(a_out[9].ai, PF_INET)); + + /* 10: v4timeout.example.com */ + tt_int_op(a_out[10].err, ==, 0); + tt_assert(a_out[10].ai); + tt_assert(! a_out[10].ai->ai_next); + test_ai_eq(a_out[10].ai, "[a0a::ff01]:8010", SOCK_STREAM, IPPROTO_TCP); + + /* 11: cancelled request. */ + tt_int_op(a_out[11].err, ==, EVUTIL_EAI_CANCEL); + tt_assert(a_out[11].ai == NULL); + +end: + if (local_outcome.ai) + evutil_freeaddrinfo(local_outcome.ai); + for (i = 0; i < ARRAY_SIZE(a_out); ++i) { + if (a_out[i].ai) + evutil_freeaddrinfo(a_out[i].ai); + } + if (port) + evdns_close_server_port(port); + if (dns_base) + evdns_base_free(dns_base, 0); +} + +struct gaic_request_status { + int magic; + struct event_base *base; + struct evdns_base *dns_base; + struct evdns_getaddrinfo_request *request; + struct event cancel_event; + int canceled; +}; + +#define GAIC_MAGIC 0x1234abcd + +static int gaic_pending = 0; +static int gaic_freed = 0; + +static void +gaic_cancel_request_cb(evutil_socket_t fd, short what, void *arg) +{ + struct gaic_request_status *status = arg; + + tt_assert(status->magic == GAIC_MAGIC); + status->canceled = 1; + evdns_getaddrinfo_cancel(status->request); + return; +end: + event_base_loopexit(status->base, NULL); +} + +static void +gaic_server_cb(struct evdns_server_request *req, void *arg) +{ + ev_uint32_t answer = 0x7f000001; + tt_assert(req->nquestions); + evdns_server_request_add_a_reply(req, req->questions[0]->name, 1, + &answer, 100); + evdns_server_request_respond(req, 0); + return; +end: + evdns_server_request_respond(req, DNS_ERR_REFUSED); +} + + +static void +gaic_getaddrinfo_cb(int result, struct evutil_addrinfo *res, void *arg) +{ + struct gaic_request_status *status = arg; + struct event_base *base = status->base; + tt_assert(status->magic == GAIC_MAGIC); + + if (result == EVUTIL_EAI_CANCEL) { + tt_assert(status->canceled); + } + event_del(&status->cancel_event); + + memset(status, 0xf0, sizeof(*status)); + free(status); + +end: + if (res) + { + TT_BLATHER(("evutil_freeaddrinfo(%p)", res)); + evutil_freeaddrinfo(res); + ++gaic_freed; + } + if (--gaic_pending <= 0) + event_base_loopexit(base, NULL); +} + +static void +gaic_launch(struct event_base *base, struct evdns_base *dns_base) +{ + struct gaic_request_status *status = calloc(1,sizeof(*status)); + struct timeval tv = { 0, 10000 }; + status->magic = GAIC_MAGIC; + status->base = base; + status->dns_base = dns_base; + event_assign(&status->cancel_event, base, -1, 0, gaic_cancel_request_cb, + status); + status->request = evdns_getaddrinfo(dns_base, + "foobar.bazquux.example.com", "80", NULL, gaic_getaddrinfo_cb, + status); + event_add(&status->cancel_event, &tv); + ++gaic_pending; +} + +#ifdef EVENT_SET_MEM_FUNCTIONS_IMPLEMENTED +/* FIXME: We should move this to regress_main.c if anything else needs it.*/ + +/* Trivial replacements for malloc/free/realloc to check for memory leaks. + * Not threadsafe. */ +static int allocated_chunks = 0; + +static void * +cnt_malloc(size_t sz) +{ + allocated_chunks += 1; + return malloc(sz); +} + +static void * +cnt_realloc(void *old, size_t sz) +{ + if (!old) + allocated_chunks += 1; + if (!sz) + allocated_chunks -= 1; + return realloc(old, sz); +} + +static void +cnt_free(void *ptr) +{ + allocated_chunks -= 1; + free(ptr); +} + +struct testleak_env_t { + struct event_base *base; + struct evdns_base *dns_base; + struct evdns_request *req; + struct generic_dns_callback_result r; +}; + +static void * +testleak_setup(const struct testcase_t *testcase) +{ + struct testleak_env_t *env; + + allocated_chunks = 0; + + /* Reset allocation counter, to start allocations from the very beginning. + * (this will avoid false-positive negative numbers for allocated_chunks) + */ + libevent_global_shutdown(); + + event_set_mem_functions(cnt_malloc, cnt_realloc, cnt_free); + + event_enable_debug_mode(); + + /* not mm_calloc: we don't want to mess with the count. */ + env = calloc(1, sizeof(struct testleak_env_t)); + env->base = event_base_new(); + env->dns_base = evdns_base_new(env->base, 0); + env->req = evdns_base_resolve_ipv4( + env->dns_base, "example.com", DNS_QUERY_NO_SEARCH, + generic_dns_callback, &env->r); + return env; +} + +static int +testleak_cleanup(const struct testcase_t *testcase, void *env_) +{ + int ok = 0; + struct testleak_env_t *env = env_; + tt_assert(env); +#ifdef EVENT__DISABLE_DEBUG_MODE + tt_int_op(allocated_chunks, ==, 0); +#else + libevent_global_shutdown(); + tt_int_op(allocated_chunks, ==, 0); +#endif + ok = 1; +end: + if (env) { + if (env->dns_base) + evdns_base_free(env->dns_base, 0); + if (env->base) + event_base_free(env->base); + free(env); + } + return ok; +} + +static struct testcase_setup_t testleak_funcs = { + testleak_setup, testleak_cleanup +}; + +static void +test_dbg_leak_cancel(void *env_) +{ + /* cancel, loop, free/dns, free/base */ + struct testleak_env_t *env = env_; + int send_err_shutdown = 1; + evdns_cancel_request(env->dns_base, env->req); + env->req = 0; + + /* `req` is freed in callback, that's why one loop is required. */ + event_base_loop(env->base, EVLOOP_NONBLOCK); + + /* send_err_shutdown means nothing as soon as our request is + * already canceled */ + evdns_base_free(env->dns_base, send_err_shutdown); + env->dns_base = 0; + event_base_free(env->base); + env->base = 0; +} + +static void +dbg_leak_resume(void *env_, int cancel, int send_err_shutdown) +{ + /* cancel, loop, free/dns, free/base */ + struct testleak_env_t *env = env_; + if (cancel) { + evdns_cancel_request(env->dns_base, env->req); + tt_assert(!evdns_base_resume(env->dns_base)); + } else { + /* TODO: No nameservers, request can't be processed, must be errored */ + tt_assert(!evdns_base_resume(env->dns_base)); + } + + event_base_loop(env->base, EVLOOP_NONBLOCK); + /** + * Because we don't cancel request, and want our callback to recieve + * DNS_ERR_SHUTDOWN, we use deferred callback, and there was: + * - one extra malloc(), + * @see reply_schedule_callback() + * - and one missing free + * @see request_finished() (req->handle->pending_cb = 1) + * than we don't need to count in testleak_cleanup(), but we can clean them + * if we will run loop once again, but *after* evdns base freed. + */ + evdns_base_free(env->dns_base, send_err_shutdown); + env->dns_base = 0; + event_base_loop(env->base, EVLOOP_NONBLOCK); + +end: + event_base_free(env->base); + env->base = 0; +} + +#define IMPL_DBG_LEAK_RESUME(name, cancel, send_err_shutdown) \ + static void \ + test_dbg_leak_##name##_(void *env_) \ + { \ + dbg_leak_resume(env_, cancel, send_err_shutdown); \ + } +IMPL_DBG_LEAK_RESUME(resume, 0, 0) +IMPL_DBG_LEAK_RESUME(cancel_and_resume, 1, 0) +IMPL_DBG_LEAK_RESUME(resume_send_err, 0, 1) +IMPL_DBG_LEAK_RESUME(cancel_and_resume_send_err, 1, 1) + +static void +test_dbg_leak_shutdown(void *env_) +{ + /* free/dns, loop, free/base */ + struct testleak_env_t *env = env_; + int send_err_shutdown = 1; + + /* `req` is freed both with `send_err_shutdown` and without it, + * the only difference is `evdns_callback` call */ + env->req = 0; + + evdns_base_free(env->dns_base, send_err_shutdown); + env->dns_base = 0; + + /* `req` is freed in callback, that's why one loop is required */ + event_base_loop(env->base, EVLOOP_NONBLOCK); + event_base_free(env->base); + env->base = 0; +} +#endif + +static void +test_getaddrinfo_async_cancel_stress(void *ptr) +{ + struct event_base *base; + struct evdns_base *dns_base = NULL; + struct evdns_server_port *server = NULL; + evutil_socket_t fd = -1; + struct sockaddr_in sin; + struct sockaddr_storage ss; + ev_socklen_t slen; + unsigned i; + + base = event_base_new(); + dns_base = evdns_base_new(base, 0); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = 0; + sin.sin_addr.s_addr = htonl(0x7f000001); + if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + tt_abort_perror("socket"); + } + evutil_make_socket_nonblocking(fd); + if (bind(fd, (struct sockaddr*)&sin, sizeof(sin))<0) { + tt_abort_perror("bind"); + } + server = evdns_add_server_port_with_base(base, fd, 0, gaic_server_cb, + base); + + memset(&ss, 0, sizeof(ss)); + slen = sizeof(ss); + if (getsockname(fd, (struct sockaddr*)&ss, &slen)<0) { + tt_abort_perror("getsockname"); + } + evdns_base_nameserver_sockaddr_add(dns_base, + (struct sockaddr*)&ss, slen, 0); + + for (i = 0; i < 1000; ++i) { + gaic_launch(base, dns_base); + } + + event_base_dispatch(base); + + // at least some was canceled via external event + tt_int_op(gaic_freed, !=, 1000); + +end: + if (dns_base) + evdns_base_free(dns_base, 1); + if (server) + evdns_close_server_port(server); + if (base) + event_base_free(base); + if (fd >= 0) + evutil_closesocket(fd); +} + +static void +dns_client_fail_requests_test(void *arg) +{ + struct basic_test_data *data = arg; + struct event_base *base = data->base; + int limit_inflight = data->setup_data && !strcmp(data->setup_data, "limit-inflight"); + struct evdns_base *dns = NULL; + struct evdns_server_port *dns_port = NULL; + ev_uint16_t portnum = 0; + char buf[64]; + + struct generic_dns_callback_result r[20]; + unsigned i; + + dns_port = regress_get_dnsserver(base, &portnum, NULL, + regress_dns_server_cb, reissue_table); + tt_assert(dns_port); + + evutil_snprintf(buf, sizeof(buf), "127.0.0.1:%d", (int)portnum); + + dns = evdns_base_new(base, EVDNS_BASE_DISABLE_WHEN_INACTIVE); + tt_assert(!evdns_base_nameserver_ip_add(dns, buf)); + + if (limit_inflight) + tt_assert(!evdns_base_set_option(dns, "max-inflight:", "11")); + + for (i = 0; i < 20; ++i) + evdns_base_resolve_ipv4(dns, "foof.example.com", 0, generic_dns_callback, &r[i]); + + n_replies_left = 20; + exit_base = base; + + evdns_base_free(dns, 1 /** fail requests */); + /** run defered callbacks, to trigger UAF */ + event_base_dispatch(base); + + tt_int_op(n_replies_left, ==, 0); + for (i = 0; i < 20; ++i) + tt_int_op(r[i].result, ==, DNS_ERR_SHUTDOWN); + +end: + evdns_close_server_port(dns_port); +} + +static void +getaddrinfo_cb(int err, struct evutil_addrinfo *res, void *ptr) +{ + generic_dns_callback(err, 0, 0, 0, NULL, ptr); +} +static void +dns_client_fail_requests_getaddrinfo_test(void *arg) +{ + struct basic_test_data *data = arg; + struct event_base *base = data->base; + struct evdns_base *dns = NULL; + struct evdns_server_port *dns_port = NULL; + ev_uint16_t portnum = 0; + char buf[64]; + + struct generic_dns_callback_result r[20]; + int i; + + dns_port = regress_get_dnsserver(base, &portnum, NULL, + regress_dns_server_cb, reissue_table); + tt_assert(dns_port); + + evutil_snprintf(buf, sizeof(buf), "127.0.0.1:%d", (int)portnum); + + dns = evdns_base_new(base, EVDNS_BASE_DISABLE_WHEN_INACTIVE); + tt_assert(!evdns_base_nameserver_ip_add(dns, buf)); + + for (i = 0; i < 20; ++i) + tt_assert(evdns_getaddrinfo(dns, "foof.example.com", "80", NULL, getaddrinfo_cb, &r[i])); + + n_replies_left = 20; + exit_base = base; + + evdns_base_free(dns, 1 /** fail requests */); + /** run defered callbacks, to trigger UAF */ + event_base_dispatch(base); + + tt_int_op(n_replies_left, ==, 0); + for (i = 0; i < 20; ++i) + tt_int_op(r[i].result, ==, EVUTIL_EAI_FAIL); + +end: + evdns_close_server_port(dns_port); +} + +#ifdef EVTHREAD_USE_PTHREADS_IMPLEMENTED +struct race_param +{ + void *lock; + void *reqs_cmpl_cond; + int bw_threads; + void *bw_threads_exited_cond; + volatile int stopping; + void *base; + void *dns; + + int locked; +}; +static void * +race_base_run(void *arg) +{ + struct race_param *rp = (struct race_param *)arg; + event_base_loop(rp->base, EVLOOP_NO_EXIT_ON_EMPTY); + THREAD_RETURN(); +} +static void * +race_busywait_run(void *arg) +{ + struct race_param *rp = (struct race_param *)arg; + struct sockaddr_storage ss; + while (!rp->stopping) + evdns_base_get_nameserver_addr(rp->dns, 0, (struct sockaddr *)&ss, sizeof(ss)); + EVLOCK_LOCK(rp->lock, 0); + if (--rp->bw_threads == 0) + EVTHREAD_COND_SIGNAL(rp->bw_threads_exited_cond); + EVLOCK_UNLOCK(rp->lock, 0); + THREAD_RETURN(); +} +static void +race_gai_cb(int result, struct evutil_addrinfo *res, void *arg) +{ + struct race_param *rp = arg; + (void)result; + (void)res; + + --n_replies_left; + if (n_replies_left == 0) { + EVLOCK_LOCK(rp->lock, 0); + EVTHREAD_COND_SIGNAL(rp->reqs_cmpl_cond); + EVLOCK_UNLOCK(rp->lock, 0); + } +} +static void +getaddrinfo_race_gotresolve_test(void *arg) +{ + struct race_param rp; + struct evdns_server_port *dns_port = NULL; + ev_uint16_t portnum = 0; + char buf[64]; + int i; + + // Some stress is needed to yield inside getaddrinfo between resolve_ipv4 and resolve_ipv6 + int n_reqs = 16384; +#ifdef _SC_NPROCESSORS_ONLN + int n_threads = sysconf(_SC_NPROCESSORS_ONLN) + 1; +#else + int n_threads = 17; +#endif + THREAD_T thread[n_threads]; + struct timeval tv; + + (void)arg; + + evthread_use_pthreads(); + + rp.base = event_base_new(); + tt_assert(rp.base); + if (evthread_make_base_notifiable(rp.base) < 0) + tt_abort_msg("Couldn't make base notifiable!"); + + dns_port = regress_get_dnsserver(rp.base, &portnum, NULL, + regress_dns_server_cb, reissue_table); + tt_assert(dns_port); + + evutil_snprintf(buf, sizeof(buf), "127.0.0.1:%d", (int)portnum); + + rp.dns = evdns_base_new(rp.base, 0); + tt_assert(!evdns_base_nameserver_ip_add(rp.dns, buf)); + + n_replies_left = n_reqs; + + EVTHREAD_ALLOC_LOCK(rp.lock, 0); + EVTHREAD_ALLOC_COND(rp.reqs_cmpl_cond); + EVTHREAD_ALLOC_COND(rp.bw_threads_exited_cond); + tt_assert(rp.lock); + tt_assert(rp.reqs_cmpl_cond); + tt_assert(rp.bw_threads_exited_cond); + rp.bw_threads = 0; + rp.stopping = 0; + + // Run resolver thread + THREAD_START(thread[0], race_base_run, &rp); + // Run busy-wait threads used to force yield this thread + for (i = 1; i < n_threads; i++) { + rp.bw_threads++; + THREAD_START(thread[i], race_busywait_run, &rp); + } + + EVLOCK_LOCK(rp.lock, 0); + rp.locked = 1; + + for (i = 0; i < n_reqs; ++i) { + tt_assert(evdns_getaddrinfo(rp.dns, "foof.example.com", "80", NULL, race_gai_cb, &rp)); + // This magic along with busy-wait threads make this thread yield frequently + if (i % 100 == 0) { + tv.tv_sec = 0; + tv.tv_usec = 10000; + evutil_usleep_(&tv); + } + } + + exit_base = rp.base; + + // Wait for some time + tv.tv_sec = 5; + tv.tv_usec = 0; + EVTHREAD_COND_WAIT_TIMED(rp.reqs_cmpl_cond, rp.lock, &tv); + + // Stop busy-wait threads + tv.tv_sec = 1; + tv.tv_usec = 0; + rp.stopping = 1; + tt_assert(EVTHREAD_COND_WAIT_TIMED(rp.bw_threads_exited_cond, rp.lock, &tv) == 0); + + EVLOCK_UNLOCK(rp.lock, 0); + rp.locked = 0; + + evdns_base_free(rp.dns, 1 /** fail requests */); + + tt_int_op(n_replies_left, ==, 0); + +end: + if (rp.locked) + EVLOCK_UNLOCK(rp.lock, 0); + EVTHREAD_FREE_LOCK(rp.lock, 0); + EVTHREAD_FREE_COND(rp.reqs_cmpl_cond); + EVTHREAD_FREE_COND(rp.bw_threads_exited_cond); + evdns_close_server_port(dns_port); + event_base_loopbreak(rp.base); + event_base_free(rp.base); +} +#endif + +static void +test_set_so_rcvbuf_so_sndbuf(void *arg) +{ + struct basic_test_data *data = arg; + struct evdns_base *dns_base; + + dns_base = evdns_base_new(data->base, 0); + tt_assert(dns_base); + + tt_assert(!evdns_base_set_option(dns_base, "so-rcvbuf", "10240")); + tt_assert(!evdns_base_set_option(dns_base, "so-sndbuf", "10240")); + + /* actually check SO_RCVBUF/SO_SNDBUF not fails */ + tt_assert(!evdns_base_nameserver_ip_add(dns_base, "127.0.0.1")); + +end: + if (dns_base) + evdns_base_free(dns_base, 0); +} + +static void +test_set_option(void *arg) +{ +#define SUCCESS 0 +#define FAIL -1 + struct basic_test_data *data = arg; + struct evdns_base *dns_base; + size_t i; + /* Option names are allowed to have ':' at the end. + * So all test option names come in pairs. + */ + const char *int_options[] = { + "ndots", "ndots:", + "max-timeouts", "max-timeouts:", + "max-inflight", "max-inflight:", + "attempts", "attempts:", + "randomize-case", "randomize-case:", + "so-rcvbuf", "so-rcvbuf:", + "so-sndbuf", "so-sndbuf:", + }; + const char *timeval_options[] = { + "timeout", "timeout:", + "getaddrinfo-allow-skew", "getaddrinfo-allow-skew:", + "initial-probe-timeout", "initial-probe-timeout:", + }; + const char *addr_port_options[] = { + "bind-to", "bind-to:", + }; + + dns_base = evdns_base_new(data->base, 0); + tt_assert(dns_base); + + for (i = 0; i < ARRAY_SIZE(int_options); ++i) { + tt_assert(SUCCESS == evdns_base_set_option(dns_base, int_options[i], "0")); + tt_assert(SUCCESS == evdns_base_set_option(dns_base, int_options[i], "1")); + tt_assert(SUCCESS == evdns_base_set_option(dns_base, int_options[i], "10000")); + tt_assert(FAIL == evdns_base_set_option(dns_base, int_options[i], "foo")); + tt_assert(FAIL == evdns_base_set_option(dns_base, int_options[i], "3.14")); + } + + for (i = 0; i < ARRAY_SIZE(timeval_options); ++i) { + tt_assert(SUCCESS == evdns_base_set_option(dns_base, timeval_options[i], "1")); + tt_assert(SUCCESS == evdns_base_set_option(dns_base, timeval_options[i], "0.001")); + tt_assert(SUCCESS == evdns_base_set_option(dns_base, timeval_options[i], "3.14")); + tt_assert(SUCCESS == evdns_base_set_option(dns_base, timeval_options[i], "10000")); + tt_assert(FAIL == evdns_base_set_option(dns_base, timeval_options[i], "0")); + tt_assert(FAIL == evdns_base_set_option(dns_base, timeval_options[i], "foo")); + } + + for (i = 0; i < ARRAY_SIZE(addr_port_options); ++i) { + tt_assert(SUCCESS == evdns_base_set_option(dns_base, addr_port_options[i], "8.8.8.8:80")); + tt_assert(SUCCESS == evdns_base_set_option(dns_base, addr_port_options[i], "1.2.3.4")); + tt_assert(SUCCESS == evdns_base_set_option(dns_base, addr_port_options[i], "::1:82")); + tt_assert(SUCCESS == evdns_base_set_option(dns_base, addr_port_options[i], "3::4")); + tt_assert(FAIL == evdns_base_set_option(dns_base, addr_port_options[i], "3.14")); + tt_assert(FAIL == evdns_base_set_option(dns_base, addr_port_options[i], "foo")); + } + +#undef SUCCESS +#undef FAIL +end: + if (dns_base) + evdns_base_free(dns_base, 0); +} + +#define DNS_LEGACY(name, flags) \ + { #name, run_legacy_test_fn, flags|TT_LEGACY, &legacy_setup, \ + dns_##name } + +struct testcase_t dns_testcases[] = { + DNS_LEGACY(server, TT_FORK|TT_NEED_BASE), + DNS_LEGACY(gethostbyname, TT_FORK|TT_NEED_BASE|TT_NEED_DNS|TT_OFF_BY_DEFAULT), + DNS_LEGACY(gethostbyname6, TT_FORK|TT_NEED_BASE|TT_NEED_DNS|TT_OFF_BY_DEFAULT), + DNS_LEGACY(gethostbyaddr, TT_FORK|TT_NEED_BASE|TT_NEED_DNS|TT_OFF_BY_DEFAULT), + { "resolve_reverse", dns_resolve_reverse, TT_FORK|TT_OFF_BY_DEFAULT, NULL, NULL }, + { "search_empty", dns_search_empty_test, TT_FORK|TT_NEED_BASE, &basic_setup, NULL }, + { "search", dns_search_test, TT_FORK|TT_NEED_BASE, &basic_setup, NULL }, + { "search_lower", dns_search_lower_test, TT_FORK|TT_NEED_BASE, &basic_setup, NULL }, + { "search_cancel", dns_search_cancel_test, + TT_FORK|TT_NEED_BASE, &basic_setup, NULL }, + { "retry", dns_retry_test, TT_FORK|TT_NEED_BASE|TT_NO_LOGS, &basic_setup, NULL }, + { "retry_disable_when_inactive", dns_retry_disable_when_inactive_test, + TT_FORK|TT_NEED_BASE|TT_NO_LOGS, &basic_setup, NULL }, + { "reissue", dns_reissue_test, TT_FORK|TT_NEED_BASE|TT_NO_LOGS, &basic_setup, NULL }, + { "reissue_disable_when_inactive", dns_reissue_disable_when_inactive_test, + TT_FORK|TT_NEED_BASE|TT_NO_LOGS, &basic_setup, NULL }, + { "inflight", dns_inflight_test, TT_FORK|TT_NEED_BASE, &basic_setup, NULL }, + { "bufferevent_connect_hostname", test_bufferevent_connect_hostname, + TT_FORK|TT_NEED_BASE, &basic_setup, NULL }, +#ifdef EVENT__HAVE_SETRLIMIT + { "bufferevent_connect_hostname_emfile", test_bufferevent_connect_hostname, + TT_FORK|TT_NEED_BASE, &basic_setup, (char*)"emfile" }, +#endif + { "disable_when_inactive", dns_disable_when_inactive_test, + TT_FORK|TT_NEED_BASE, &basic_setup, NULL }, + { "disable_when_inactive_no_ns", dns_disable_when_inactive_no_ns_test, + TT_FORK|TT_NEED_BASE|TT_NO_LOGS, &basic_setup, NULL }, + + { "initialize_nameservers", dns_initialize_nameservers_test, + TT_FORK|TT_NEED_BASE, &basic_setup, NULL }, +#ifndef _WIN32 + { "nameservers_no_default", dns_nameservers_no_default_test, + TT_FORK|TT_NEED_BASE, &basic_setup, NULL }, +#endif + + { "getaddrinfo_async", test_getaddrinfo_async, + TT_FORK|TT_NEED_BASE, &basic_setup, (char*)"" }, + { "getaddrinfo_cancel_stress", test_getaddrinfo_async_cancel_stress, + TT_FORK, NULL, NULL }, + +#ifdef EVENT_SET_MEM_FUNCTIONS_IMPLEMENTED + { "leak_shutdown", test_dbg_leak_shutdown, TT_FORK, &testleak_funcs, NULL }, + { "leak_cancel", test_dbg_leak_cancel, TT_FORK, &testleak_funcs, NULL }, + + { "leak_resume", test_dbg_leak_resume_, TT_FORK, &testleak_funcs, NULL }, + { "leak_cancel_and_resume", test_dbg_leak_cancel_and_resume_, + TT_FORK, &testleak_funcs, NULL }, + { "leak_resume_send_err", test_dbg_leak_resume_send_err_, + TT_FORK, &testleak_funcs, NULL }, + { "leak_cancel_and_resume_send_err", test_dbg_leak_cancel_and_resume_send_err_, + TT_FORK, &testleak_funcs, NULL }, +#endif + + { "client_fail_requests", dns_client_fail_requests_test, + TT_FORK|TT_NEED_BASE|TT_NO_LOGS, &basic_setup, NULL }, + { "client_fail_waiting_requests", dns_client_fail_requests_test, + TT_FORK|TT_NEED_BASE|TT_NO_LOGS, &basic_setup, (char*)"limit-inflight" }, + { "client_fail_requests_getaddrinfo", + dns_client_fail_requests_getaddrinfo_test, + TT_FORK|TT_NEED_BASE|TT_NO_LOGS, &basic_setup, NULL }, +#ifdef EVTHREAD_USE_PTHREADS_IMPLEMENTED + { "getaddrinfo_race_gotresolve", + getaddrinfo_race_gotresolve_test, + TT_FORK|TT_OFF_BY_DEFAULT, NULL, NULL }, +#endif + + { "set_SO_RCVBUF_SO_SNDBUF", test_set_so_rcvbuf_so_sndbuf, + TT_FORK|TT_NEED_BASE, &basic_setup, NULL }, + { "set_options", test_set_option, + TT_FORK|TT_NEED_BASE, &basic_setup, NULL }, + + END_OF_TESTCASES +}; + diff --git a/ipc/chromium/src/third_party/libevent/test/regress_et.c b/ipc/chromium/src/third_party/libevent/test/regress_et.c new file mode 100644 index 0000000000..1b1f819eda --- /dev/null +++ b/ipc/chromium/src/third_party/libevent/test/regress_et.c @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2009-2012 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "../util-internal.h" +#include "event2/event-config.h" + +#ifdef _WIN32 +#include +#endif +#include +#include +#ifdef EVENT__HAVE_SYS_SOCKET_H +#include +#endif +#include +#include +#include +#include +#ifndef _WIN32 +#include +#include +#endif +#include + +#include "event2/event.h" +#include "event2/util.h" + +#include "regress.h" + +static int was_et = 0; + +static int base_supports_et(struct event_base *base) +{ + return + (!strcmp(event_base_get_method(base), "epoll") || + !strcmp(event_base_get_method(base), "epoll (with changelist)") || + !strcmp(event_base_get_method(base), "kqueue")); +} + +static void +read_cb(evutil_socket_t fd, short event, void *arg) +{ + char buf; + int len; + + len = recv(fd, &buf, sizeof(buf), 0); + + called++; + if (event & EV_ET) + was_et = 1; + + if (!len) + event_del(arg); +} + +static void +test_edgetriggered(void *data_) +{ + struct basic_test_data *data = data_; + struct event_base *base = data->base; + evutil_socket_t *pair = data->pair; + struct event *ev = NULL; + const char *test = "test string"; + int supports_et; + + /* On Linux 3.2.1 (at least, as patched by Fedora and tested by Nick), + * doing a "recv" on an AF_UNIX socket resets the readability of the + * socket, even though there is no state change, so we don't actually + * get edge-triggered behavior. Yuck! Linux 3.1.9 didn't have this + * problem. + */ + + called = was_et = 0; + + tt_int_op(send(pair[0], test, (int)strlen(test)+1, 0), >, 0); + tt_int_op(shutdown(pair[0], EVUTIL_SHUT_WR), ==, 0); + + supports_et = base_supports_et(base); + TT_BLATHER(("Checking for edge-triggered events with %s, which should %s" + "support edge-triggering", event_base_get_method(base), + supports_et?"":"not ")); + + /* Initialize one event */ + ev = event_new(base, pair[1], EV_READ|EV_ET|EV_PERSIST, read_cb, &ev); + tt_assert(ev != NULL); + tt_int_op(event_add(ev, NULL), ==, 0); + + /* We're going to call the dispatch function twice. The first invocation + * will read a single byte from pair[1] in either case. If we're edge + * triggered, we'll only see the event once (since we only see transitions + * from no data to data), so the second invocation of event_base_loop will + * do nothing. If we're level triggered, the second invocation of + * event_base_loop will also activate the event (because there's still + * data to read). */ + tt_int_op(event_base_loop(base,EVLOOP_NONBLOCK|EVLOOP_ONCE), ==, 0); + tt_int_op(event_base_loop(base,EVLOOP_NONBLOCK|EVLOOP_ONCE), ==, 0); + + if (supports_et) { + tt_int_op(called, ==, 1); + tt_assert(was_et); + } else { + tt_int_op(called, ==, 2); + tt_assert(!was_et); + } + +end: + if (ev) { + event_del(ev); + event_free(ev); + } +} + +static void +test_edgetriggered_mix_error(void *data_) +{ + struct basic_test_data *data = data_; + struct event_base *base = NULL; + struct event *ev_et=NULL, *ev_lt=NULL; + +#ifdef EVENT__DISABLE_DEBUG_MODE + if (1) + tt_skip(); +#endif + + if (!libevent_tests_running_in_debug_mode) + event_enable_debug_mode(); + + base = event_base_new(); + + /* try mixing edge-triggered and level-triggered to make sure it fails*/ + ev_et = event_new(base, data->pair[0], EV_READ|EV_ET, read_cb, ev_et); + tt_assert(ev_et); + ev_lt = event_new(base, data->pair[0], EV_READ, read_cb, ev_lt); + tt_assert(ev_lt); + + /* Add edge-triggered, then level-triggered. Get an error. */ + tt_int_op(0, ==, event_add(ev_et, NULL)); + tt_int_op(-1, ==, event_add(ev_lt, NULL)); + tt_int_op(EV_READ, ==, event_pending(ev_et, EV_READ, NULL)); + tt_int_op(0, ==, event_pending(ev_lt, EV_READ, NULL)); + + tt_int_op(0, ==, event_del(ev_et)); + /* Add level-triggered, then edge-triggered. Get an error. */ + tt_int_op(0, ==, event_add(ev_lt, NULL)); + tt_int_op(-1, ==, event_add(ev_et, NULL)); + tt_int_op(EV_READ, ==, event_pending(ev_lt, EV_READ, NULL)); + tt_int_op(0, ==, event_pending(ev_et, EV_READ, NULL)); + +end: + if (ev_et) + event_free(ev_et); + if (ev_lt) + event_free(ev_lt); + if (base) + event_base_free(base); +} + +static int read_notification_count; +static int last_read_notification_was_et; +static void +read_notification_cb(evutil_socket_t fd, short event, void *arg) +{ + read_notification_count++; + last_read_notification_was_et = (event & EV_ET); +} + +static int write_notification_count; +static int last_write_notification_was_et; +static void +write_notification_cb(evutil_socket_t fd, short event, void *arg) +{ + write_notification_count++; + last_write_notification_was_et = (event & EV_ET); +} + +/* After two or more events have been registered for the same + * file descriptor using EV_ET, if one of the events is + * deleted, then the epoll_ctl() call issued by libevent drops + * the EPOLLET flag resulting in level triggered + * notifications. + */ +static void +test_edge_triggered_multiple_events(void *data_) +{ + struct basic_test_data *data = data_; + struct event *read_ev = NULL; + struct event *write_ev = NULL; + const char c = 'A'; + struct event_base *base = data->base; + evutil_socket_t *pair = data->pair; + + if (!base_supports_et(base)) { + tt_skip(); + return; + } + + read_notification_count = 0; + last_read_notification_was_et = 0; + write_notification_count = 0; + last_write_notification_was_et = 0; + + /* Make pair[1] readable */ + tt_int_op(send(pair[0], &c, 1, 0), >, 0); + + read_ev = event_new(base, pair[1], EV_READ|EV_ET|EV_PERSIST, + read_notification_cb, NULL); + write_ev = event_new(base, pair[1], EV_WRITE|EV_ET|EV_PERSIST, + write_notification_cb, NULL); + + event_add(read_ev, NULL); + event_add(write_ev, NULL); + event_base_loop(base, EVLOOP_NONBLOCK|EVLOOP_ONCE); + event_base_loop(base, EVLOOP_NONBLOCK|EVLOOP_ONCE); + + tt_assert(last_read_notification_was_et); + tt_int_op(read_notification_count, ==, 1); + tt_assert(last_write_notification_was_et); + tt_int_op(write_notification_count, ==, 1); + + event_del(read_ev); + + /* trigger acitivity second time for the backend that can have multiple + * events for one fd (like kqueue) */ + close(pair[0]); + pair[0] = -1; + + /* Verify that we are still edge-triggered for write notifications */ + event_base_loop(base, EVLOOP_NONBLOCK|EVLOOP_ONCE); + event_base_loop(base, EVLOOP_NONBLOCK|EVLOOP_ONCE); + tt_assert(last_write_notification_was_et); + tt_int_op(write_notification_count, ==, 2); + +end: + if (read_ev) + event_free(read_ev); + if (write_ev) + event_free(write_ev); +} + +struct testcase_t edgetriggered_testcases[] = { + { "et", test_edgetriggered, + TT_FORK|TT_NEED_BASE|TT_NEED_SOCKETPAIR, &basic_setup, NULL }, + { "et_mix_error", test_edgetriggered_mix_error, + TT_FORK|TT_NEED_SOCKETPAIR|TT_NO_LOGS, &basic_setup, NULL }, + { "et_multiple_events", test_edge_triggered_multiple_events, + TT_FORK|TT_NEED_BASE|TT_NEED_SOCKETPAIR, &basic_setup, NULL }, + END_OF_TESTCASES +}; diff --git a/ipc/chromium/src/third_party/libevent/test/regress_finalize.c b/ipc/chromium/src/third_party/libevent/test/regress_finalize.c new file mode 100644 index 0000000000..9e57188121 --- /dev/null +++ b/ipc/chromium/src/third_party/libevent/test/regress_finalize.c @@ -0,0 +1,395 @@ +/* + * Copyright (c) 2013 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "event2/event-config.h" +#include "evconfig-private.h" +#include "tinytest.h" +#include "tinytest_macros.h" +#include + +#include "event2/event.h" +#include "event2/util.h" +#include "event-internal.h" +#include "defer-internal.h" + +#include "regress.h" +#include "regress_thread.h" + +static void +timer_callback(evutil_socket_t fd, short what, void *arg) +{ + int *int_arg = arg; + *int_arg += 1; + (void)fd; + (void)what; +} +static void +simple_callback(struct event_callback *evcb, void *arg) +{ + int *int_arg = arg; + *int_arg += 1; + (void)evcb; +} +static void +event_finalize_callback_1(struct event *ev, void *arg) +{ + int *int_arg = arg; + *int_arg += 100; + (void)ev; +} +static void +callback_finalize_callback_1(struct event_callback *evcb, void *arg) +{ + int *int_arg = arg; + *int_arg += 100; + (void)evcb; +} + + +static void +test_fin_cb_invoked(void *arg) +{ + struct basic_test_data *data = arg; + struct event_base *base = data->base; + + struct event *ev; + struct event ev2; + struct event_callback evcb; + int cb_called = 0; + int ev_called = 0; + + const struct timeval ten_sec = {10,0}; + + event_deferred_cb_init_(&evcb, 0, simple_callback, &cb_called); + ev = evtimer_new(base, timer_callback, &ev_called); + /* Just finalize them; don't bother adding. */ + event_free_finalize(0, ev, event_finalize_callback_1); + event_callback_finalize_(base, 0, &evcb, callback_finalize_callback_1); + + event_base_dispatch(base); + + tt_int_op(cb_called, ==, 100); + tt_int_op(ev_called, ==, 100); + + ev_called = cb_called = 0; + event_base_assert_ok_(base); + + /* Now try it when they're active. (actually, don't finalize: make + * sure activation can happen! */ + ev = evtimer_new(base, timer_callback, &ev_called); + event_deferred_cb_init_(&evcb, 0, simple_callback, &cb_called); + + event_active(ev, EV_TIMEOUT, 1); + event_callback_activate_(base, &evcb); + + event_base_dispatch(base); + tt_int_op(cb_called, ==, 1); + tt_int_op(ev_called, ==, 1); + + ev_called = cb_called = 0; + event_base_assert_ok_(base); + + /* Great, it worked. Now activate and finalize and make sure only + * finalizing happens. */ + event_active(ev, EV_TIMEOUT, 1); + event_callback_activate_(base, &evcb); + event_free_finalize(0, ev, event_finalize_callback_1); + event_callback_finalize_(base, 0, &evcb, callback_finalize_callback_1); + + event_base_dispatch(base); + tt_int_op(cb_called, ==, 100); + tt_int_op(ev_called, ==, 100); + + ev_called = 0; + + event_base_assert_ok_(base); + + /* Okay, now add but don't have it become active, and make sure *that* + * works. */ + ev = evtimer_new(base, timer_callback, &ev_called); + event_add(ev, &ten_sec); + event_free_finalize(0, ev, event_finalize_callback_1); + + event_base_dispatch(base); + tt_int_op(ev_called, ==, 100); + + ev_called = 0; + event_base_assert_ok_(base); + + /* Now try adding and deleting after finalizing. */ + ev = evtimer_new(base, timer_callback, &ev_called); + evtimer_assign(&ev2, base, timer_callback, &ev_called); + event_add(ev, &ten_sec); + event_free_finalize(0, ev, event_finalize_callback_1); + event_finalize(0, &ev2, event_finalize_callback_1); + + event_add(&ev2, &ten_sec); + event_del(ev); + event_active(&ev2, EV_TIMEOUT, 1); + + event_base_dispatch(base); + tt_int_op(ev_called, ==, 200); + + event_base_assert_ok_(base); + +end: + ; +} + +#ifndef EVENT__DISABLE_MM_REPLACEMENT +static void * +tfff_malloc(size_t n) +{ + return malloc(n); +} +static void *tfff_p1=NULL, *tfff_p2=NULL; +static int tfff_p1_freed=0, tfff_p2_freed=0; +static void +tfff_free(void *p) +{ + if (! p) + return; + if (p == tfff_p1) + ++tfff_p1_freed; + if (p == tfff_p2) + ++tfff_p2_freed; + free(p); +} +static void * +tfff_realloc(void *p, size_t sz) +{ + return realloc(p,sz); +} +#endif + +static void +test_fin_free_finalize(void *arg) +{ +#ifdef EVENT__DISABLE_MM_REPLACEMENT + tinytest_set_test_skipped_(); +#else + struct event_base *base = NULL; + struct event *ev, *ev2; + int ev_called = 0; + int ev2_called = 0; + + (void)arg; + + event_set_mem_functions(tfff_malloc, tfff_realloc, tfff_free); + + base = event_base_new(); + tt_assert(base); + + ev = evtimer_new(base, timer_callback, &ev_called); + ev2 = evtimer_new(base, timer_callback, &ev2_called); + tfff_p1 = ev; + tfff_p2 = ev2; + event_free_finalize(0, ev, event_finalize_callback_1); + event_finalize(0, ev2, event_finalize_callback_1); + + event_base_dispatch(base); + + tt_int_op(ev_called, ==, 100); + tt_int_op(ev2_called, ==, 100); + + event_base_assert_ok_(base); + tt_int_op(tfff_p1_freed, ==, 1); + tt_int_op(tfff_p2_freed, ==, 0); + + event_free(ev2); + +end: + if (base) + event_base_free(base); +#endif +} + +/* For test_fin_within_cb */ +struct event_and_count { + struct event *ev; + struct event *ev2; + int count; +}; +static void +event_finalize_callback_2(struct event *ev, void *arg) +{ + struct event_and_count *evc = arg; + evc->count += 100; + event_free(ev); +} +static void +timer_callback_2(evutil_socket_t fd, short what, void *arg) +{ + struct event_and_count *evc = arg; + event_finalize(0, evc->ev, event_finalize_callback_2); + event_finalize(0, evc->ev2, event_finalize_callback_2); + ++ evc->count; + (void)fd; + (void)what; +} + +static void +test_fin_within_cb(void *arg) +{ + struct basic_test_data *data = arg; + struct event_base *base = data->base; + + struct event_and_count evc1, evc2; + evc1.count = evc2.count = 0; + evc2.ev2 = evc1.ev = evtimer_new(base, timer_callback_2, &evc1); + evc1.ev2 = evc2.ev = evtimer_new(base, timer_callback_2, &evc2); + + /* Activate both. The first one will have its callback run, which + * will finalize both of them, preventing the second one's callback + * from running. */ + event_active(evc1.ev, EV_TIMEOUT, 1); + event_active(evc2.ev, EV_TIMEOUT, 1); + + event_base_dispatch(base); + tt_int_op(evc1.count, ==, 101); + tt_int_op(evc2.count, ==, 100); + + event_base_assert_ok_(base); + /* Now try with EV_PERSIST events. */ + evc1.count = evc2.count = 0; + evc2.ev2 = evc1.ev = event_new(base, -1, EV_PERSIST, timer_callback_2, &evc1); + evc1.ev2 = evc2.ev = event_new(base, -1, EV_PERSIST, timer_callback_2, &evc2); + + event_active(evc1.ev, EV_TIMEOUT, 1); + event_active(evc2.ev, EV_TIMEOUT, 1); + + event_base_dispatch(base); + tt_int_op(evc1.count, ==, 101); + tt_int_op(evc2.count, ==, 100); + + event_base_assert_ok_(base); +end: + ; +} + +static void +event_finalize_callback_free(struct event *ev, void *arg) +{ + struct event_base *base = arg; + int err; + if (base) { + err = event_assign(ev, base, -1, EV_TIMEOUT, NULL, NULL); + tt_int_op(err, ==, 0); + test_ok += 1; + } else { + free(ev); + test_ok += 1; + } + +end: + ; +} +static void +test_fin_debug_use_after_free(void *arg) +{ + struct basic_test_data *data = arg; + struct event_base *base = data->base; + struct event *ev; + + tt_ptr_op(ev = event_new(base, -1, EV_TIMEOUT, NULL, base), !=, NULL); + tt_int_op(event_add(ev, NULL), ==, 0); + tt_int_op(event_finalize(0, ev, event_finalize_callback_free), ==, 0); + + // Dispatch base to trigger callbacks + event_base_dispatch(base); + event_base_assert_ok_(base); + tt_int_op(test_ok, ==, 1); + + // Now add again, since we did event_assign in event_finalize_callback_free + // This used to fail in event_debug_assert_is_setup_ + tt_int_op(event_add(ev, NULL), ==, 0); + + // Finalize and dispatch again + tt_int_op(event_finalize(0, ev, event_finalize_callback_free), ==, 0); + event_base_dispatch(base); + event_base_assert_ok_(base); + tt_int_op(test_ok, ==, 2); + +end: + ; +} + +#if 0 +static void +timer_callback_3(evutil_socket_t *fd, short what, void *arg) +{ + (void)fd; + (void)what; + +} +static void +test_fin_many(void *arg) +{ + struct basic_test_data *data = arg; + struct event_base *base = data->base; + + struct event *ev1, *ev2; + struct event_callback evcb1, evcb2; + int ev1_count = 0, ev2_count = 0; + int evcb1_count = 0, evcb2_count = 0; + struct event_callback *array[4]; + + int n; + + /* First attempt: call finalize_many with no events running */ + ev1 = evtimer_new(base, timer_callback, &ev1_count); + ev1 = evtimer_new(base, timer_callback, &ev2_count); + event_deferred_cb_init_(&evcb1, 0, simple_callback, &evcb1_called); + event_deferred_cb_init_(&evcb2, 0, simple_callback, &evcb2_called); + array[0] = &ev1->ev_evcallback; + array[1] = &ev2->ev_evcallback; + array[2] = &evcb1; + array[3] = &evcb2; + + + + n = event_callback_finalize_many(base, 4, array, + callback_finalize_callback_1); + +} +#endif + + +#define TEST(name, flags) \ + { #name, test_fin_##name, (flags), &basic_setup, NULL } + +struct testcase_t finalize_testcases[] = { + + TEST(cb_invoked, TT_FORK|TT_NEED_BASE), + TEST(free_finalize, TT_FORK), + TEST(within_cb, TT_FORK|TT_NEED_BASE), + TEST(debug_use_after_free, TT_FORK|TT_NEED_BASE|TT_ENABLE_DEBUG_MODE), +// TEST(many, TT_FORK|TT_NEED_BASE), + + + END_OF_TESTCASES +}; + diff --git a/ipc/chromium/src/third_party/libevent/test/regress_http.c b/ipc/chromium/src/third_party/libevent/test/regress_http.c new file mode 100644 index 0000000000..4493907163 --- /dev/null +++ b/ipc/chromium/src/third_party/libevent/test/regress_http.c @@ -0,0 +1,4807 @@ +/* + * Copyright (c) 2003-2007 Niels Provos + * Copyright (c) 2007-2012 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "util-internal.h" + +#ifdef _WIN32 +#include +#include +#include +#endif + +#include "event2/event-config.h" + +#include +#include +#ifdef EVENT__HAVE_SYS_TIME_H +#include +#endif +#include +#ifndef _WIN32 +#include +#include +#include +#include +#endif +#include +#include +#include +#include +#include + +#include "event2/dns.h" + +#include "event2/event.h" +#include "event2/http.h" +#include "event2/buffer.h" +#include "event2/bufferevent.h" +#include "event2/bufferevent_ssl.h" +#include "event2/util.h" +#include "event2/listener.h" +#include "log-internal.h" +#include "http-internal.h" +#include "regress.h" +#include "regress_testutils.h" + +#define ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0])) + +/* set if a test needs to call loopexit on a base */ +static struct event_base *exit_base; + +static char const BASIC_REQUEST_BODY[] = "This is funny"; + +static void http_basic_cb(struct evhttp_request *req, void *arg); +static void http_timeout_cb(struct evhttp_request *req, void *arg); +static void http_large_cb(struct evhttp_request *req, void *arg); +static void http_chunked_cb(struct evhttp_request *req, void *arg); +static void http_post_cb(struct evhttp_request *req, void *arg); +static void http_put_cb(struct evhttp_request *req, void *arg); +static void http_delete_cb(struct evhttp_request *req, void *arg); +static void http_delay_cb(struct evhttp_request *req, void *arg); +static void http_large_delay_cb(struct evhttp_request *req, void *arg); +static void http_badreq_cb(struct evhttp_request *req, void *arg); +static void http_dispatcher_cb(struct evhttp_request *req, void *arg); +static void http_on_complete_cb(struct evhttp_request *req, void *arg); + +#define HTTP_BIND_IPV6 1 +#define HTTP_BIND_SSL 2 +#define HTTP_SSL_FILTER 4 +static int +http_bind(struct evhttp *myhttp, ev_uint16_t *pport, int mask) +{ + int port; + struct evhttp_bound_socket *sock; + int ipv6 = mask & HTTP_BIND_IPV6; + + if (ipv6) + sock = evhttp_bind_socket_with_handle(myhttp, "::1", *pport); + else + sock = evhttp_bind_socket_with_handle(myhttp, "127.0.0.1", *pport); + + if (sock == NULL) { + if (ipv6) + return -1; + else + event_errx(1, "Could not start web server"); + } + + port = regress_get_socket_port(evhttp_bound_socket_get_fd(sock)); + if (port < 0) + return -1; + *pport = (ev_uint16_t) port; + + return 0; +} + +#ifdef EVENT__HAVE_OPENSSL +static struct bufferevent * +https_bev(struct event_base *base, void *arg) +{ + SSL *ssl = SSL_new(get_ssl_ctx()); + + SSL_use_certificate(ssl, ssl_getcert(ssl_getkey())); + SSL_use_PrivateKey(ssl, ssl_getkey()); + + return bufferevent_openssl_socket_new( + base, -1, ssl, BUFFEREVENT_SSL_ACCEPTING, + BEV_OPT_CLOSE_ON_FREE); +} +#endif +static struct evhttp * +http_setup_gencb(ev_uint16_t *pport, struct event_base *base, int mask, + void (*cb)(struct evhttp_request *, void *), void *cbarg) +{ + struct evhttp *myhttp; + + /* Try a few different ports */ + myhttp = evhttp_new(base); + + if (http_bind(myhttp, pport, mask) < 0) + return NULL; +#ifdef EVENT__HAVE_OPENSSL + if (mask & HTTP_BIND_SSL) { + init_ssl(); + evhttp_set_bevcb(myhttp, https_bev, NULL); + } +#endif + + evhttp_set_gencb(myhttp, cb, cbarg); + + /* Register a callback for certain types of requests */ + evhttp_set_cb(myhttp, "/test", http_basic_cb, myhttp); + evhttp_set_cb(myhttp, "/test nonconformant", http_basic_cb, myhttp); + evhttp_set_cb(myhttp, "/timeout", http_timeout_cb, myhttp); + evhttp_set_cb(myhttp, "/large", http_large_cb, base); + evhttp_set_cb(myhttp, "/chunked", http_chunked_cb, base); + evhttp_set_cb(myhttp, "/streamed", http_chunked_cb, base); + evhttp_set_cb(myhttp, "/postit", http_post_cb, base); + evhttp_set_cb(myhttp, "/putit", http_put_cb, base); + evhttp_set_cb(myhttp, "/deleteit", http_delete_cb, base); + evhttp_set_cb(myhttp, "/delay", http_delay_cb, base); + evhttp_set_cb(myhttp, "/largedelay", http_large_delay_cb, base); + evhttp_set_cb(myhttp, "/badrequest", http_badreq_cb, base); + evhttp_set_cb(myhttp, "/oncomplete", http_on_complete_cb, base); + evhttp_set_cb(myhttp, "/", http_dispatcher_cb, base); + return (myhttp); +} +static struct evhttp * +http_setup(ev_uint16_t *pport, struct event_base *base, int mask) +{ return http_setup_gencb(pport, base, mask, NULL, NULL); } + +#ifndef NI_MAXSERV +#define NI_MAXSERV 1024 +#endif + +static evutil_socket_t +http_connect(const char *address, ev_uint16_t port) +{ + /* Stupid code for connecting */ + struct evutil_addrinfo ai, *aitop; + char strport[NI_MAXSERV]; + + struct sockaddr *sa; + size_t slen; + evutil_socket_t fd; + + memset(&ai, 0, sizeof(ai)); + ai.ai_family = AF_INET; + ai.ai_socktype = SOCK_STREAM; + evutil_snprintf(strport, sizeof(strport), "%d", port); + if (evutil_getaddrinfo(address, strport, &ai, &aitop) != 0) { + event_warn("getaddrinfo"); + return (-1); + } + sa = aitop->ai_addr; + slen = aitop->ai_addrlen; + + fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd == -1) + event_err(1, "socket failed"); + + evutil_make_socket_nonblocking(fd); + if (connect(fd, sa, slen) == -1) { +#ifdef _WIN32 + int tmp_err = WSAGetLastError(); + if (tmp_err != WSAEINPROGRESS && tmp_err != WSAEINVAL && + tmp_err != WSAEWOULDBLOCK) + event_err(1, "connect failed"); +#else + if (errno != EINPROGRESS) + event_err(1, "connect failed"); +#endif + } + + evutil_freeaddrinfo(aitop); + + return (fd); +} + +/* Helper: do a strcmp on the contents of buf and the string s. */ +static int +evbuffer_datacmp(struct evbuffer *buf, const char *s) +{ + size_t b_sz = evbuffer_get_length(buf); + size_t s_sz = strlen(s); + unsigned char *d; + int r; + + if (b_sz < s_sz) + return -1; + + d = evbuffer_pullup(buf, s_sz); + if (!d) + d = (unsigned char *)""; + if ((r = memcmp(d, s, s_sz))) + return r; + + if (b_sz > s_sz) + return 1; + else + return 0; +} + +/* Helper: Return true iff buf contains s */ +static int +evbuffer_contains(struct evbuffer *buf, const char *s) +{ + struct evbuffer_ptr ptr; + ptr = evbuffer_search(buf, s, strlen(s), NULL); + return ptr.pos != -1; +} + +static void +http_readcb(struct bufferevent *bev, void *arg) +{ + const char *what = BASIC_REQUEST_BODY; + struct event_base *my_base = arg; + + if (evbuffer_contains(bufferevent_get_input(bev), what)) { + struct evhttp_request *req = evhttp_request_new(NULL, NULL); + enum message_read_status done; + + /* req->kind = EVHTTP_RESPONSE; */ + done = evhttp_parse_firstline_(req, bufferevent_get_input(bev)); + if (done != ALL_DATA_READ) + goto out; + + done = evhttp_parse_headers_(req, bufferevent_get_input(bev)); + if (done != ALL_DATA_READ) + goto out; + + if (done == 1 && + evhttp_find_header(evhttp_request_get_input_headers(req), + "Content-Type") != NULL) + test_ok++; + + out: + evhttp_request_free(req); + bufferevent_disable(bev, EV_READ); + if (exit_base) + event_base_loopexit(exit_base, NULL); + else if (my_base) + event_base_loopexit(my_base, NULL); + else { + fprintf(stderr, "No way to exit loop!\n"); + exit(1); + } + } +} + +static void +http_writecb(struct bufferevent *bev, void *arg) +{ + if (evbuffer_get_length(bufferevent_get_output(bev)) == 0) { + /* enable reading of the reply */ + bufferevent_enable(bev, EV_READ); + test_ok++; + } +} + +static void +http_errorcb(struct bufferevent *bev, short what, void *arg) +{ + /** For ssl */ + if (what & BEV_EVENT_CONNECTED) + return; + test_ok = -2; + event_base_loopexit(arg, NULL); +} + +static int found_multi = 0; +static int found_multi2 = 0; + +static void +http_basic_cb(struct evhttp_request *req, void *arg) +{ + struct evbuffer *evb = evbuffer_new(); + struct evhttp_connection *evcon; + int empty = evhttp_find_header(evhttp_request_get_input_headers(req), "Empty") != NULL; + + TT_BLATHER(("%s: called\n", __func__)); + evbuffer_add_printf(evb, BASIC_REQUEST_BODY); + + evcon = evhttp_request_get_connection(req); + tt_assert(evhttp_connection_get_server(evcon) == arg); + + { + const struct sockaddr *sa; + char addrbuf[128]; + + sa = evhttp_connection_get_addr(evcon); + tt_assert(sa); + + if (sa->sa_family == AF_INET) { + evutil_format_sockaddr_port_((struct sockaddr *)sa, addrbuf, sizeof(addrbuf)); + tt_assert(!strncmp(addrbuf, "127.0.0.1:", strlen("127.0.0.1:"))); + } else if (sa->sa_family == AF_INET6) { + evutil_format_sockaddr_port_((struct sockaddr *)sa, addrbuf, sizeof(addrbuf)); + tt_assert(!strncmp(addrbuf, "[::1]:", strlen("[::1]:"))); + } else { + tt_fail_msg("Unsupported family"); + } + } + + /* For multi-line headers test */ + { + const char *multi = + evhttp_find_header(evhttp_request_get_input_headers(req),"X-Multi"); + if (multi) { + found_multi = !strcmp(multi,"aaaaaaaa a END"); + if (strcmp("END", multi + strlen(multi) - 3) == 0) + test_ok++; + if (evhttp_find_header(evhttp_request_get_input_headers(req), "X-Last")) + test_ok++; + } + } + { + const char *multi2 = + evhttp_find_header(evhttp_request_get_input_headers(req),"X-Multi-Extra-WS"); + if (multi2) { + found_multi2 = !strcmp(multi2,"libevent 2.1"); + } + } + + + /* injecting a bad content-length */ + if (evhttp_find_header(evhttp_request_get_input_headers(req), "X-Negative")) + evhttp_add_header(evhttp_request_get_output_headers(req), + "Content-Length", "-100"); + + /* allow sending of an empty reply */ + evhttp_send_reply(req, HTTP_OK, "Everything is fine", + !empty ? evb : NULL); + +end: + evbuffer_free(evb); +} + +static void http_timeout_reply_cb(evutil_socket_t fd, short events, void *arg) +{ + struct evhttp_request *req = arg; + evhttp_send_reply(req, HTTP_OK, "Everything is fine", NULL); + test_ok++; +} +static void +http_timeout_cb(struct evhttp_request *req, void *arg) +{ + struct timeval when = { 0, 100 }; + event_base_once(exit_base, -1, EV_TIMEOUT, + http_timeout_reply_cb, req, &when); +} + +static void +http_large_cb(struct evhttp_request *req, void *arg) +{ + struct evbuffer *evb = evbuffer_new(); + int i; + + for (i = 0; i < 1<<20; ++i) { + evbuffer_add_printf(evb, BASIC_REQUEST_BODY); + } + evhttp_send_reply(req, HTTP_OK, "Everything is fine", evb); + evbuffer_free(evb); +} + +static char const* const CHUNKS[] = { + "This is funny", + "but not hilarious.", + "bwv 1052" +}; + +struct chunk_req_state { + struct event_base *base; + struct evhttp_request *req; + int i; +}; + +static void +http_chunked_trickle_cb(evutil_socket_t fd, short events, void *arg) +{ + struct evbuffer *evb = evbuffer_new(); + struct chunk_req_state *state = arg; + struct timeval when = { 0, 0 }; + + evbuffer_add_printf(evb, "%s", CHUNKS[state->i]); + evhttp_send_reply_chunk(state->req, evb); + evbuffer_free(evb); + + if (++state->i < (int) (sizeof(CHUNKS)/sizeof(CHUNKS[0]))) { + event_base_once(state->base, -1, EV_TIMEOUT, + http_chunked_trickle_cb, state, &when); + } else { + evhttp_send_reply_end(state->req); + free(state); + } +} + +static void +http_chunked_cb(struct evhttp_request *req, void *arg) +{ + struct timeval when = { 0, 0 }; + struct chunk_req_state *state = malloc(sizeof(struct chunk_req_state)); + TT_BLATHER(("%s: called\n", __func__)); + + memset(state, 0, sizeof(struct chunk_req_state)); + state->req = req; + state->base = arg; + + if (strcmp(evhttp_request_get_uri(req), "/streamed") == 0) { + evhttp_add_header(evhttp_request_get_output_headers(req), "Content-Length", "39"); + } + + /* generate a chunked/streamed reply */ + evhttp_send_reply_start(req, HTTP_OK, "Everything is fine"); + + /* but trickle it across several iterations to ensure we're not + * assuming it comes all at once */ + event_base_once(arg, -1, EV_TIMEOUT, http_chunked_trickle_cb, state, &when); +} + +static struct bufferevent * +create_bev(struct event_base *base, evutil_socket_t fd, int ssl_mask, int flags_) +{ + int flags = BEV_OPT_DEFER_CALLBACKS | flags_; + struct bufferevent *bev = NULL; + + if (!ssl_mask) { + bev = bufferevent_socket_new(base, fd, flags); + } else { +#ifdef EVENT__HAVE_OPENSSL + SSL *ssl = SSL_new(get_ssl_ctx()); + if (ssl_mask & HTTP_SSL_FILTER) { + struct bufferevent *underlying = + bufferevent_socket_new(base, fd, flags); + bev = bufferevent_openssl_filter_new( + base, underlying, ssl, BUFFEREVENT_SSL_CONNECTING, flags); + } else { + bev = bufferevent_openssl_socket_new( + base, fd, ssl, BUFFEREVENT_SSL_CONNECTING, flags); + } + bufferevent_openssl_set_allow_dirty_shutdown(bev, 1); +#endif + } + + return bev; +} + +static void +http_half_writecb(struct bufferevent *bev, void *arg) +{ + if (evbuffer_get_length(bufferevent_get_output(bev)) == 0) { + if (!test_ok) { + const char http_request[] = "host\r\n" + "Connection: close\r\n" + "\r\n"; + bufferevent_write(bev, http_request, strlen(http_request)); + } + /* enable reading of the reply */ + bufferevent_enable(bev, EV_READ); + test_ok++; + } +} + +static void +http_basic_test_impl(void *arg, int ssl, const char *request_line) +{ + struct basic_test_data *data = arg; + struct bufferevent *bev = NULL; + evutil_socket_t fd; + const char *http_request; + ev_uint16_t port = 0, port2 = 0; + int server_flags = ssl ? HTTP_BIND_SSL : 0; + struct evhttp *http = http_setup(&port, data->base, server_flags); + struct evbuffer *out; + + exit_base = data->base; + + /* bind to a second socket */ + if (http_bind(http, &port2, server_flags) == -1) { + fprintf(stdout, "FAILED (bind)\n"); + exit(1); + } + + fd = http_connect("127.0.0.1", port); + + /* Stupid thing to send a request */ + bev = create_bev(data->base, fd, ssl, BEV_OPT_CLOSE_ON_FREE); + bufferevent_setcb(bev, http_readcb, http_half_writecb, + http_errorcb, data->base); + out = bufferevent_get_output(bev); + + /* first half of the http request */ + evbuffer_add_printf(out, + "%s\r\n" + "Host: some", request_line); + + test_ok = 0; + event_base_dispatch(data->base); + tt_int_op(test_ok, ==, 3); + + /* connect to the second port */ + bufferevent_free(bev); + + fd = http_connect("127.0.0.1", port2); + + /* Stupid thing to send a request */ + bev = create_bev(data->base, fd, ssl, BEV_OPT_CLOSE_ON_FREE); + bufferevent_setcb(bev, http_readcb, http_writecb, + http_errorcb, data->base); + out = bufferevent_get_output(bev); + + evbuffer_add_printf(out, + "%s\r\n" + "Host: somehost\r\n" + "Connection: close\r\n" + "\r\n", request_line); + + test_ok = 0; + event_base_dispatch(data->base); + tt_int_op(test_ok, ==, 2); + + /* Connect to the second port again. This time, send an absolute uri. */ + bufferevent_free(bev); + + fd = http_connect("127.0.0.1", port2); + + /* Stupid thing to send a request */ + bev = create_bev(data->base, fd, ssl, BEV_OPT_CLOSE_ON_FREE); + bufferevent_setcb(bev, http_readcb, http_writecb, + http_errorcb, data->base); + + http_request = + "GET http://somehost.net/test HTTP/1.1\r\n" + "Host: somehost\r\n" + "Connection: close\r\n" + "\r\n"; + + bufferevent_write(bev, http_request, strlen(http_request)); + + test_ok = 0; + event_base_dispatch(data->base); + tt_int_op(test_ok, ==, 2); + + evhttp_free(http); +end: + if (bev) + bufferevent_free(bev); +} +static void http_basic_test(void *arg)\ +{ http_basic_test_impl(arg, 0, "GET /test HTTP/1.1"); } +static void http_basic_trailing_space_test(void *arg) +{ http_basic_test_impl(arg, 0, "GET /test HTTP/1.1 "); } + + +static void +http_delay_reply(evutil_socket_t fd, short what, void *arg) +{ + struct evhttp_request *req = arg; + + evhttp_send_reply(req, HTTP_OK, "Everything is fine", NULL); + + ++test_ok; +} + +static void +http_delay_cb(struct evhttp_request *req, void *arg) +{ + struct timeval tv; + evutil_timerclear(&tv); + tv.tv_sec = 0; + tv.tv_usec = 200 * 1000; + + event_base_once(arg, -1, EV_TIMEOUT, http_delay_reply, req, &tv); +} + +static void +http_badreq_cb(struct evhttp_request *req, void *arg) +{ + struct evbuffer *buf = evbuffer_new(); + + evhttp_add_header(evhttp_request_get_output_headers(req), "Content-Type", "text/xml; charset=UTF-8"); + evbuffer_add_printf(buf, "Hello, %s!", "127.0.0.1"); + + evhttp_send_reply(req, HTTP_OK, "OK", buf); + evbuffer_free(buf); +} + +static void +http_badreq_errorcb(struct bufferevent *bev, short what, void *arg) +{ + TT_BLATHER(("%s: called (what=%04x, arg=%p)", __func__, what, arg)); + /* ignore */ +} + +static void +http_badreq_readcb(struct bufferevent *bev, void *arg) +{ + const char *what = "Hello, 127.0.0.1"; + const char *bad_request = "400 Bad Request"; + + if (evbuffer_contains(bufferevent_get_input(bev), bad_request)) { + TT_FAIL(("%s:bad request detected", __func__)); + bufferevent_disable(bev, EV_READ); + event_base_loopexit(arg, NULL); + return; + } + + if (evbuffer_contains(bufferevent_get_input(bev), what)) { + struct evhttp_request *req = evhttp_request_new(NULL, NULL); + enum message_read_status done; + + /* req->kind = EVHTTP_RESPONSE; */ + done = evhttp_parse_firstline_(req, bufferevent_get_input(bev)); + if (done != ALL_DATA_READ) + goto out; + + done = evhttp_parse_headers_(req, bufferevent_get_input(bev)); + if (done != ALL_DATA_READ) + goto out; + + if (done == 1 && + evhttp_find_header(evhttp_request_get_input_headers(req), + "Content-Type") != NULL) + test_ok++; + + out: + evhttp_request_free(req); + evbuffer_drain(bufferevent_get_input(bev), evbuffer_get_length(bufferevent_get_input(bev))); + } + + shutdown(bufferevent_getfd(bev), EVUTIL_SHUT_WR); +} + +static void +http_badreq_successcb(evutil_socket_t fd, short what, void *arg) +{ + TT_BLATHER(("%s: called (what=%04x, arg=%p)", __func__, what, arg)); + event_base_loopexit(exit_base, NULL); +} + +static void +http_bad_request_test(void *arg) +{ + struct basic_test_data *data = arg; + struct timeval tv; + struct bufferevent *bev = NULL; + evutil_socket_t fd = EVUTIL_INVALID_SOCKET; + const char *http_request; + ev_uint16_t port=0, port2=0; + struct evhttp *http = http_setup(&port, data->base, 0); + + test_ok = 0; + exit_base = data->base; + + /* bind to a second socket */ + if (http_bind(http, &port2, 0) == -1) + TT_DIE(("Bind socket failed")); + + /* NULL request test */ + fd = http_connect("127.0.0.1", port); + tt_assert(fd != EVUTIL_INVALID_SOCKET); + + /* Stupid thing to send a request */ + bev = bufferevent_socket_new(data->base, fd, 0); + bufferevent_setcb(bev, http_badreq_readcb, http_writecb, + http_badreq_errorcb, data->base); + bufferevent_enable(bev, EV_READ); + + /* real NULL request */ + http_request = ""; + + bufferevent_write(bev, http_request, strlen(http_request)); + + shutdown(fd, EVUTIL_SHUT_WR); + timerclear(&tv); + tv.tv_usec = 10000; + event_base_once(data->base, -1, EV_TIMEOUT, http_badreq_successcb, bev, &tv); + + event_base_dispatch(data->base); + + bufferevent_free(bev); + evutil_closesocket(fd); + + if (test_ok != 0) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + /* Second answer (BAD REQUEST) on connection close */ + + /* connect to the second port */ + fd = http_connect("127.0.0.1", port2); + tt_assert(fd != EVUTIL_INVALID_SOCKET); + + /* Stupid thing to send a request */ + bev = bufferevent_socket_new(data->base, fd, 0); + bufferevent_setcb(bev, http_badreq_readcb, http_writecb, + http_badreq_errorcb, data->base); + bufferevent_enable(bev, EV_READ); + + /* first half of the http request */ + http_request = + "GET /badrequest HTTP/1.0\r\n" \ + "Connection: Keep-Alive\r\n" \ + "\r\n"; + + bufferevent_write(bev, http_request, strlen(http_request)); + + timerclear(&tv); + tv.tv_usec = 10000; + event_base_once(data->base, -1, EV_TIMEOUT, http_badreq_successcb, bev, &tv); + + event_base_dispatch(data->base); + + tt_int_op(test_ok, ==, 2); + +end: + evhttp_free(http); + if (bev) + bufferevent_free(bev); + if (fd >= 0) + evutil_closesocket(fd); +} + +static struct evhttp_connection *delayed_client; + +static void +http_large_delay_cb(struct evhttp_request *req, void *arg) +{ + struct timeval tv; + evutil_timerclear(&tv); + tv.tv_usec = 500000; + + event_base_once(arg, -1, EV_TIMEOUT, http_delay_reply, req, &tv); + evhttp_connection_fail_(delayed_client, EVREQ_HTTP_EOF); +} + +/* + * HTTP DELETE test, just piggyback on the basic test + */ + +static void +http_delete_cb(struct evhttp_request *req, void *arg) +{ + struct evbuffer *evb = evbuffer_new(); + int empty = evhttp_find_header(evhttp_request_get_input_headers(req), "Empty") != NULL; + + /* Expecting a DELETE request */ + if (evhttp_request_get_command(req) != EVHTTP_REQ_DELETE) { + fprintf(stdout, "FAILED (delete type)\n"); + exit(1); + } + + TT_BLATHER(("%s: called\n", __func__)); + evbuffer_add_printf(evb, BASIC_REQUEST_BODY); + + /* allow sending of an empty reply */ + evhttp_send_reply(req, HTTP_OK, "Everything is fine", + !empty ? evb : NULL); + + evbuffer_free(evb); +} + +static void +http_delete_test(void *arg) +{ + struct basic_test_data *data = arg; + struct bufferevent *bev; + evutil_socket_t fd = EVUTIL_INVALID_SOCKET; + const char *http_request; + ev_uint16_t port = 0; + struct evhttp *http = http_setup(&port, data->base, 0); + + exit_base = data->base; + test_ok = 0; + + tt_assert(http); + fd = http_connect("127.0.0.1", port); + tt_assert(fd != EVUTIL_INVALID_SOCKET); + + /* Stupid thing to send a request */ + bev = bufferevent_socket_new(data->base, fd, 0); + bufferevent_setcb(bev, http_readcb, http_writecb, + http_errorcb, data->base); + + http_request = + "DELETE /deleteit HTTP/1.1\r\n" + "Host: somehost\r\n" + "Connection: close\r\n" + "\r\n"; + + bufferevent_write(bev, http_request, strlen(http_request)); + + event_base_dispatch(data->base); + + bufferevent_free(bev); + evutil_closesocket(fd); + fd = EVUTIL_INVALID_SOCKET; + + evhttp_free(http); + + tt_int_op(test_ok, ==, 2); + end: + if (fd >= 0) + evutil_closesocket(fd); +} + +static void +http_sent_cb(struct evhttp_request *req, void *arg) +{ + ev_uintptr_t val = (ev_uintptr_t)arg; + struct evbuffer *b; + + if (val != 0xDEADBEEF) { + fprintf(stdout, "FAILED on_complete_cb argument\n"); + exit(1); + } + + b = evhttp_request_get_output_buffer(req); + if (evbuffer_get_length(b) != 0) { + fprintf(stdout, "FAILED on_complete_cb output buffer not written\n"); + exit(1); + } + + TT_BLATHER(("%s: called\n", __func__)); + + ++test_ok; +} + +static void +http_on_complete_cb(struct evhttp_request *req, void *arg) +{ + struct evbuffer *evb = evbuffer_new(); + + evhttp_request_set_on_complete_cb(req, http_sent_cb, (void *)0xDEADBEEF); + + TT_BLATHER(("%s: called\n", __func__)); + evbuffer_add_printf(evb, BASIC_REQUEST_BODY); + + /* allow sending of an empty reply */ + evhttp_send_reply(req, HTTP_OK, "Everything is fine", evb); + + evbuffer_free(evb); + + ++test_ok; +} + +static void +http_on_complete_test(void *arg) +{ + struct basic_test_data *data = arg; + struct bufferevent *bev; + evutil_socket_t fd = EVUTIL_INVALID_SOCKET; + const char *http_request; + ev_uint16_t port = 0; + struct evhttp *http = http_setup(&port, data->base, 0); + + exit_base = data->base; + test_ok = 0; + + fd = http_connect("127.0.0.1", port); + tt_assert(fd != EVUTIL_INVALID_SOCKET); + + /* Stupid thing to send a request */ + bev = bufferevent_socket_new(data->base, fd, 0); + bufferevent_setcb(bev, http_readcb, http_writecb, + http_errorcb, data->base); + + http_request = + "GET /oncomplete HTTP/1.1\r\n" + "Host: somehost\r\n" + "Connection: close\r\n" + "\r\n"; + + bufferevent_write(bev, http_request, strlen(http_request)); + + event_base_dispatch(data->base); + + bufferevent_free(bev); + + evhttp_free(http); + + tt_int_op(test_ok, ==, 4); + end: + if (fd >= 0) + evutil_closesocket(fd); +} + +static void +http_allowed_methods_eventcb(struct bufferevent *bev, short what, void *arg) +{ + char **output = arg; + if ((what & (BEV_EVENT_ERROR|BEV_EVENT_EOF))) { + char buf[4096]; + int n; + n = evbuffer_remove(bufferevent_get_input(bev), buf, + sizeof(buf)-1); + if (n >= 0) { + buf[n]='\0'; + if (*output) + free(*output); + *output = strdup(buf); + } + event_base_loopexit(exit_base, NULL); + } +} + +static void +http_allowed_methods_test(void *arg) +{ + struct basic_test_data *data = arg; + struct bufferevent *bev1, *bev2, *bev3; + evutil_socket_t fd1=-1, fd2=-1, fd3=-1; + const char *http_request; + char *result1=NULL, *result2=NULL, *result3=NULL; + ev_uint16_t port = 0; + struct evhttp *http = http_setup(&port, data->base, 0); + + exit_base = data->base; + test_ok = 0; + + fd1 = http_connect("127.0.0.1", port); + tt_assert(fd1 != EVUTIL_INVALID_SOCKET); + + /* GET is out; PATCH is in. */ + evhttp_set_allowed_methods(http, EVHTTP_REQ_PATCH); + + /* Stupid thing to send a request */ + bev1 = bufferevent_socket_new(data->base, fd1, 0); + bufferevent_enable(bev1, EV_READ|EV_WRITE); + bufferevent_setcb(bev1, NULL, NULL, + http_allowed_methods_eventcb, &result1); + + http_request = + "GET /index.html HTTP/1.1\r\n" + "Host: somehost\r\n" + "Connection: close\r\n" + "\r\n"; + + bufferevent_write(bev1, http_request, strlen(http_request)); + + event_base_dispatch(data->base); + + fd2 = http_connect("127.0.0.1", port); + tt_assert(fd2 != EVUTIL_INVALID_SOCKET); + + bev2 = bufferevent_socket_new(data->base, fd2, 0); + bufferevent_enable(bev2, EV_READ|EV_WRITE); + bufferevent_setcb(bev2, NULL, NULL, + http_allowed_methods_eventcb, &result2); + + http_request = + "PATCH /test HTTP/1.1\r\n" + "Host: somehost\r\n" + "Connection: close\r\n" + "\r\n"; + + bufferevent_write(bev2, http_request, strlen(http_request)); + + event_base_dispatch(data->base); + + fd3 = http_connect("127.0.0.1", port); + tt_assert(fd3 != EVUTIL_INVALID_SOCKET); + + bev3 = bufferevent_socket_new(data->base, fd3, 0); + bufferevent_enable(bev3, EV_READ|EV_WRITE); + bufferevent_setcb(bev3, NULL, NULL, + http_allowed_methods_eventcb, &result3); + + http_request = + "FLOOP /test HTTP/1.1\r\n" + "Host: somehost\r\n" + "Connection: close\r\n" + "\r\n"; + + bufferevent_write(bev3, http_request, strlen(http_request)); + + event_base_dispatch(data->base); + + bufferevent_free(bev1); + bufferevent_free(bev2); + bufferevent_free(bev3); + + evhttp_free(http); + + /* Method known but disallowed */ + tt_assert(result1); + tt_assert(!strncmp(result1, "HTTP/1.1 501 ", strlen("HTTP/1.1 501 "))); + + /* Method known and allowed */ + tt_assert(result2); + tt_assert(!strncmp(result2, "HTTP/1.1 200 ", strlen("HTTP/1.1 200 "))); + + /* Method unknown */ + tt_assert(result3); + tt_assert(!strncmp(result3, "HTTP/1.1 501 ", strlen("HTTP/1.1 501 "))); + + end: + if (result1) + free(result1); + if (result2) + free(result2); + if (result3) + free(result3); + if (fd1 >= 0) + evutil_closesocket(fd1); + if (fd2 >= 0) + evutil_closesocket(fd2); + if (fd3 >= 0) + evutil_closesocket(fd3); +} + +static void http_request_no_action_done(struct evhttp_request *, void *); +static void http_request_done(struct evhttp_request *, void *); +static void http_request_empty_done(struct evhttp_request *, void *); + +static void +http_connection_test_(struct basic_test_data *data, int persistent, + const char *address, struct evdns_base *dnsbase, int ipv6, int family, + int ssl) +{ + ev_uint16_t port = 0; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req = NULL; + struct evhttp *http; + + int mask = 0; + if (ipv6) + mask |= HTTP_BIND_IPV6; + if (ssl) + mask |= HTTP_BIND_SSL; + + http = http_setup(&port, data->base, mask); + + test_ok = 0; + if (!http && ipv6) { + tt_skip(); + } + tt_assert(http); + + if (ssl) { +#ifdef EVENT__HAVE_OPENSSL + SSL *ssl = SSL_new(get_ssl_ctx()); + struct bufferevent *bev = bufferevent_openssl_socket_new( + data->base, -1, ssl, + BUFFEREVENT_SSL_CONNECTING, BEV_OPT_DEFER_CALLBACKS); + bufferevent_openssl_set_allow_dirty_shutdown(bev, 1); + + evcon = evhttp_connection_base_bufferevent_new(data->base, dnsbase, bev, address, port); +#else + tt_skip(); +#endif + } else { + evcon = evhttp_connection_base_new(data->base, dnsbase, address, port); + } + tt_assert(evcon); + evhttp_connection_set_family(evcon, family); + + tt_assert(evhttp_connection_get_base(evcon) == data->base); + + exit_base = data->base; + + tt_assert(evhttp_connection_get_server(evcon) == NULL); + + /* + * At this point, we want to schedule a request to the HTTP + * server using our make request method. + */ + req = evhttp_request_new(http_request_done, (void*) BASIC_REQUEST_BODY); + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost"); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/test") == -1) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + event_base_dispatch(data->base); + + tt_assert(test_ok); + + /* try to make another request over the same connection */ + test_ok = 0; + + req = evhttp_request_new(http_request_done, (void*) BASIC_REQUEST_BODY); + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost"); + + /* + * if our connections are not supposed to be persistent; request + * a close from the server. + */ + if (!persistent) + evhttp_add_header(evhttp_request_get_output_headers(req), "Connection", "close"); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/test") == -1) { + tt_abort_msg("couldn't make request"); + } + + event_base_dispatch(data->base); + + /* make another request: request empty reply */ + test_ok = 0; + + req = evhttp_request_new(http_request_empty_done, data->base); + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Empty", "itis"); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/test") == -1) { + tt_abort_msg("Couldn't make request"); + } + + event_base_dispatch(data->base); + + end: + if (evcon) + evhttp_connection_free(evcon); + if (http) + evhttp_free(http); +} + +static void +http_connection_test(void *arg) +{ + http_connection_test_(arg, 0, "127.0.0.1", NULL, 0, AF_UNSPEC, 0); +} +static void +http_persist_connection_test(void *arg) +{ + http_connection_test_(arg, 1, "127.0.0.1", NULL, 0, AF_UNSPEC, 0); +} + +static struct regress_dns_server_table search_table[] = { + { "localhost", "A", "127.0.0.1", 0, 0 }, + { NULL, NULL, NULL, 0, 0 } +}; + +static void +http_connection_async_test(void *arg) +{ + struct basic_test_data *data = arg; + ev_uint16_t port = 0; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req = NULL; + struct evdns_base *dns_base = NULL; + ev_uint16_t portnum = 0; + char address[64]; + struct evhttp *http = http_setup(&port, data->base, 0); + + exit_base = data->base; + tt_assert(regress_dnsserver(data->base, &portnum, search_table)); + + dns_base = evdns_base_new(data->base, 0/* init name servers */); + tt_assert(dns_base); + + /* Add ourself as the only nameserver, and make sure we really are + * the only nameserver. */ + evutil_snprintf(address, sizeof(address), "127.0.0.1:%d", portnum); + evdns_base_nameserver_ip_add(dns_base, address); + + test_ok = 0; + + evcon = evhttp_connection_base_new(data->base, dns_base, "127.0.0.1", port); + tt_assert(evcon); + + /* + * At this point, we want to schedule a request to the HTTP + * server using our make request method. + */ + + req = evhttp_request_new(http_request_done, (void*) BASIC_REQUEST_BODY); + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost"); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/test") == -1) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + event_base_dispatch(data->base); + + tt_assert(test_ok); + + /* try to make another request over the same connection */ + test_ok = 0; + + req = evhttp_request_new(http_request_done, (void*) BASIC_REQUEST_BODY); + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost"); + + /* + * if our connections are not supposed to be persistent; request + * a close from the server. + */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Connection", "close"); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/test") == -1) { + tt_abort_msg("couldn't make request"); + } + + event_base_dispatch(data->base); + + /* make another request: request empty reply */ + test_ok = 0; + + req = evhttp_request_new(http_request_empty_done, data->base); + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Empty", "itis"); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/test") == -1) { + tt_abort_msg("Couldn't make request"); + } + + event_base_dispatch(data->base); + + end: + if (evcon) + evhttp_connection_free(evcon); + if (http) + evhttp_free(http); + if (dns_base) + evdns_base_free(dns_base, 0); + regress_clean_dnsserver(); +} + +static void +http_autofree_connection_test(void *arg) +{ + struct basic_test_data *data = arg; + ev_uint16_t port = 0; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req[2] = { NULL }; + struct evhttp *http = http_setup(&port, data->base, 0); + size_t i; + + test_ok = 0; + + evcon = evhttp_connection_base_new(data->base, NULL, "127.0.0.1", port); + tt_assert(evcon); + + /* + * At this point, we want to schedule two request to the HTTP + * server using our make request method. + */ + req[0] = evhttp_request_new(http_request_empty_done, data->base); + req[1] = evhttp_request_new(http_request_empty_done, data->base); + + /* Add the information that we care about */ + for (i = 0; i < ARRAY_SIZE(req); ++i) { + evhttp_add_header(evhttp_request_get_output_headers(req[i]), "Host", "somehost"); + evhttp_add_header(evhttp_request_get_output_headers(req[i]), "Connection", "close"); + evhttp_add_header(evhttp_request_get_output_headers(req[i]), "Empty", "itis"); + + if (evhttp_make_request(evcon, req[i], EVHTTP_REQ_GET, "/test") == -1) { + tt_abort_msg("couldn't make request"); + } + } + + /* + * Tell libevent to free the connection when the request completes + * We then set the evcon pointer to NULL since we don't want to free it + * when this function ends. + */ + evhttp_connection_free_on_completion(evcon); + evcon = NULL; + + for (i = 0; i < ARRAY_SIZE(req); ++i) + event_base_dispatch(data->base); + + /* at this point, the http server should have no connection */ + tt_assert(TAILQ_FIRST(&http->connections) == NULL); + + end: + if (evcon) + evhttp_connection_free(evcon); + if (http) + evhttp_free(http); +} + +static void +http_request_never_call(struct evhttp_request *req, void *arg) +{ + fprintf(stdout, "FAILED\n"); + exit(1); +} +static void +http_failed_request_done(struct evhttp_request *req, void *arg) +{ + tt_assert(!req); +end: + event_base_loopexit(arg, NULL); +} +#ifndef _WIN32 +static void +http_timed_out_request_done(struct evhttp_request *req, void *arg) +{ + tt_assert(req); + tt_int_op(evhttp_request_get_response_code(req), !=, HTTP_OK); +end: + event_base_loopexit(arg, NULL); +} +#endif + +static void +http_request_error_cb_with_cancel(enum evhttp_request_error error, void *arg) +{ + if (error != EVREQ_HTTP_REQUEST_CANCEL) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + test_ok = 1; + + { + struct timeval tv; + evutil_timerclear(&tv); + tv.tv_sec = 0; + tv.tv_usec = 500 * 1000; + event_base_loopexit(exit_base, &tv); + } +} +static void +http_do_cancel(evutil_socket_t fd, short what, void *arg) +{ + struct evhttp_request *req = arg; + evhttp_cancel_request(req); + ++test_ok; +} +static void +http_no_write(struct evbuffer *buffer, const struct evbuffer_cb_info *info, void *arg) +{ + fprintf(stdout, "FAILED\n"); + exit(1); +} +static void +http_free_evcons(struct evhttp_connection **evcons) +{ + struct evhttp_connection *evcon, **orig = evcons; + + if (!evcons) + return; + + while ((evcon = *evcons++)) { + evhttp_connection_free(evcon); + } + free(orig); +} +/** fill the backlog to force server drop packages for timeouts */ +static struct evhttp_connection ** +http_fill_backlog(struct event_base *base, int port) +{ +#define BACKLOG_SIZE 256 + struct evhttp_connection **evcon = malloc(sizeof(*evcon) * (BACKLOG_SIZE + 1)); + int i; + + for (i = 0; i < BACKLOG_SIZE; ++i) { + struct evhttp_request *req; + + evcon[i] = evhttp_connection_base_new(base, NULL, "127.0.0.1", port); + tt_assert(evcon[i]); + evhttp_connection_set_timeout(evcon[i], 5); + + req = evhttp_request_new(http_request_never_call, NULL); + tt_assert(req); + tt_int_op(evhttp_make_request(evcon[i], req, EVHTTP_REQ_GET, "/delay"), !=, -1); + } + evcon[i] = NULL; + + return evcon; + end: + fprintf(stderr, "Couldn't fill the backlog"); + return NULL; +} + +enum http_cancel_test_type { + BASIC = 1, + BY_HOST = 2, + NO_NS = 4, + INACTIVE_SERVER = 8, + SERVER_TIMEOUT = 16, + NS_TIMEOUT = 32, +}; +static struct evhttp_request * +http_cancel_test_bad_request_new(enum http_cancel_test_type type, + struct event_base *base) +{ +#ifndef _WIN32 + if (!(type & NO_NS) && (type & SERVER_TIMEOUT)) + return evhttp_request_new(http_timed_out_request_done, base); + else +#endif + if ((type & INACTIVE_SERVER) || (type & NO_NS)) + return evhttp_request_new(http_failed_request_done, base); + else + return NULL; +} +static void +http_cancel_test(void *arg) +{ + struct basic_test_data *data = arg; + ev_uint16_t port = 0; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req = NULL; + struct bufferevent *bufev = NULL; + struct timeval tv; + struct evdns_base *dns_base = NULL; + ev_uint16_t portnum = 0; + char address[64]; + struct evhttp *inactive_http = NULL; + struct event_base *inactive_base = NULL; + struct evhttp_connection **evcons = NULL; + struct event_base *base_to_fill = data->base; + + enum http_cancel_test_type type = + (enum http_cancel_test_type)data->setup_data; + struct evhttp *http = http_setup(&port, data->base, 0); + + if (type & BY_HOST) { + const char *timeout = (type & NS_TIMEOUT) ? "6" : "3"; + + tt_assert(regress_dnsserver(data->base, &portnum, search_table)); + + dns_base = evdns_base_new(data->base, 0/* init name servers */); + tt_assert(dns_base); + + /** XXX: Hack the port to make timeout after resolving */ + if (type & NO_NS) + ++portnum; + + evutil_snprintf(address, sizeof(address), "127.0.0.1:%d", portnum); + evdns_base_nameserver_ip_add(dns_base, address); + + evdns_base_set_option(dns_base, "timeout:", timeout); + evdns_base_set_option(dns_base, "initial-probe-timeout:", timeout); + evdns_base_set_option(dns_base, "attempts:", "1"); + } + + exit_base = data->base; + + test_ok = 0; + + if (type & INACTIVE_SERVER) { + port = 0; + inactive_base = event_base_new(); + inactive_http = http_setup(&port, inactive_base, 0); + + base_to_fill = inactive_base; + } + + if (type & SERVER_TIMEOUT) + evcons = http_fill_backlog(base_to_fill, port); + + evcon = evhttp_connection_base_new( + data->base, dns_base, + type & BY_HOST ? "localhost" : "127.0.0.1", + port); + if (type & INACTIVE_SERVER) + evhttp_connection_set_timeout(evcon, 5); + tt_assert(evcon); + + bufev = evhttp_connection_get_bufferevent(evcon); + /* Guarantee that we stack in connect() not after waiting EV_READ after + * write() */ + if (type & SERVER_TIMEOUT) + evbuffer_add_cb(bufferevent_get_output(bufev), http_no_write, NULL); + + /* + * At this point, we want to schedule a request to the HTTP + * server using our make request method. + */ + + req = evhttp_request_new(http_request_never_call, NULL); + evhttp_request_set_error_cb(req, http_request_error_cb_with_cancel); + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost"); + + /* We give ownership of the request to the connection */ + tt_int_op(evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/delay"), + !=, -1); + + evutil_timerclear(&tv); + tv.tv_sec = 0; + tv.tv_usec = 100 * 1000; + + event_base_once(data->base, -1, EV_TIMEOUT, http_do_cancel, req, &tv); + + event_base_dispatch(data->base); + + if (type & NO_NS || type & INACTIVE_SERVER) + tt_int_op(test_ok, ==, 2); /** no servers responses */ + else + tt_int_op(test_ok, ==, 3); + + /* try to make another request over the same connection */ + test_ok = 0; + + http_free_evcons(evcons); + if (type & SERVER_TIMEOUT) + evcons = http_fill_backlog(base_to_fill, port); + + req = http_cancel_test_bad_request_new(type, data->base); + if (!req) + req = evhttp_request_new(http_request_done, (void*) BASIC_REQUEST_BODY); + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost"); + + /* We give ownership of the request to the connection */ + tt_int_op(evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/test"), + !=, -1); + + event_base_dispatch(data->base); + + /* make another request: request empty reply */ + test_ok = 0; + + http_free_evcons(evcons); + if (type & SERVER_TIMEOUT) + evcons = http_fill_backlog(base_to_fill, port); + + req = http_cancel_test_bad_request_new(type, data->base); + if (!req) + req = evhttp_request_new(http_request_empty_done, data->base); + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Empty", "itis"); + + /* We give ownership of the request to the connection */ + tt_int_op(evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/test"), + !=, -1); + + event_base_dispatch(data->base); + + end: + http_free_evcons(evcons); + if (bufev) + evbuffer_remove_cb(bufferevent_get_output(bufev), http_no_write, NULL); + if (evcon) + evhttp_connection_free(evcon); + if (http) + evhttp_free(http); + if (dns_base) + evdns_base_free(dns_base, 0); + regress_clean_dnsserver(); + if (inactive_http) + evhttp_free(inactive_http); + if (inactive_base) + event_base_free(inactive_base); +} + +static void +http_request_no_action_done(struct evhttp_request *req, void *arg) +{ + EVUTIL_ASSERT(exit_base); + event_base_loopexit(exit_base, NULL); +} + +static void +http_request_done(struct evhttp_request *req, void *arg) +{ + const char *what = arg; + + if (!req) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + if (evhttp_request_get_response_code(req) != HTTP_OK) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + if (evhttp_find_header(evhttp_request_get_input_headers(req), "Content-Type") == NULL) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + if (evbuffer_get_length(evhttp_request_get_input_buffer(req)) != strlen(what)) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + if (evbuffer_datacmp(evhttp_request_get_input_buffer(req), what) != 0) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + test_ok = 1; + EVUTIL_ASSERT(exit_base); + event_base_loopexit(exit_base, NULL); +} + +static void +http_request_expect_error(struct evhttp_request *req, void *arg) +{ + if (evhttp_request_get_response_code(req) == HTTP_OK) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + test_ok = 1; + EVUTIL_ASSERT(arg); + event_base_loopexit(arg, NULL); +} + +/* test virtual hosts */ +static void +http_virtual_host_test(void *arg) +{ + struct basic_test_data *data = arg; + ev_uint16_t port = 0; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req = NULL; + struct evhttp *second = NULL, *third = NULL; + evutil_socket_t fd; + struct bufferevent *bev; + const char *http_request; + struct evhttp *http = http_setup(&port, data->base, 0); + + exit_base = data->base; + + /* virtual host */ + second = evhttp_new(NULL); + evhttp_set_cb(second, "/funnybunny", http_basic_cb, http); + third = evhttp_new(NULL); + evhttp_set_cb(third, "/blackcoffee", http_basic_cb, http); + + if (evhttp_add_virtual_host(http, "foo.com", second) == -1) { + tt_abort_msg("Couldn't add vhost"); + } + + if (evhttp_add_virtual_host(http, "bar.*.foo.com", third) == -1) { + tt_abort_msg("Couldn't add wildcarded vhost"); + } + + /* add some aliases to the vhosts */ + tt_assert(evhttp_add_server_alias(second, "manolito.info") == 0); + tt_assert(evhttp_add_server_alias(third, "bonkers.org") == 0); + + evcon = evhttp_connection_base_new(data->base, NULL, "127.0.0.1", port); + tt_assert(evcon); + + /* make a request with a different host and expect an error */ + req = evhttp_request_new(http_request_expect_error, data->base); + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost"); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, + "/funnybunny") == -1) { + tt_abort_msg("Couldn't make request"); + } + + event_base_dispatch(data->base); + + tt_assert(test_ok == 1); + + test_ok = 0; + + /* make a request with the right host and expect a response */ + req = evhttp_request_new(http_request_done, (void*) BASIC_REQUEST_BODY); + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "foo.com"); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, + "/funnybunny") == -1) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + event_base_dispatch(data->base); + + tt_assert(test_ok == 1); + + test_ok = 0; + + /* make a request with the right host and expect a response */ + req = evhttp_request_new(http_request_done, (void*) BASIC_REQUEST_BODY); + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "bar.magic.foo.com"); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, + "/blackcoffee") == -1) { + tt_abort_msg("Couldn't make request"); + } + + event_base_dispatch(data->base); + + tt_assert(test_ok == 1) + + test_ok = 0; + + /* make a request with the right host and expect a response */ + req = evhttp_request_new(http_request_done, (void*) BASIC_REQUEST_BODY); + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "manolito.info"); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, + "/funnybunny") == -1) { + tt_abort_msg("Couldn't make request"); + } + + event_base_dispatch(data->base); + + tt_assert(test_ok == 1) + + test_ok = 0; + + /* make a request with the right host and expect a response */ + req = evhttp_request_new(http_request_done, (void*) BASIC_REQUEST_BODY); + + /* Add the Host header. This time with the optional port. */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "bonkers.org:8000"); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, + "/blackcoffee") == -1) { + tt_abort_msg("Couldn't make request"); + } + + event_base_dispatch(data->base); + + tt_assert(test_ok == 1) + + test_ok = 0; + + /* Now make a raw request with an absolute URI. */ + fd = http_connect("127.0.0.1", port); + tt_assert(fd != EVUTIL_INVALID_SOCKET); + + /* Stupid thing to send a request */ + bev = bufferevent_socket_new(data->base, fd, 0); + bufferevent_setcb(bev, http_readcb, http_writecb, + http_errorcb, NULL); + + /* The host in the URI should override the Host: header */ + http_request = + "GET http://manolito.info/funnybunny HTTP/1.1\r\n" + "Host: somehost\r\n" + "Connection: close\r\n" + "\r\n"; + + bufferevent_write(bev, http_request, strlen(http_request)); + + event_base_dispatch(data->base); + + tt_int_op(test_ok, ==, 2); + + bufferevent_free(bev); + evutil_closesocket(fd); + + end: + if (evcon) + evhttp_connection_free(evcon); + if (http) + evhttp_free(http); +} + + +/* test date header and content length */ + +static void +http_request_empty_done(struct evhttp_request *req, void *arg) +{ + if (!req) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + if (evhttp_request_get_response_code(req) != HTTP_OK) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + if (evhttp_find_header(evhttp_request_get_input_headers(req), "Date") == NULL) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + + if (evhttp_find_header(evhttp_request_get_input_headers(req), "Content-Length") == NULL) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + if (strcmp(evhttp_find_header(evhttp_request_get_input_headers(req), "Content-Length"), + "0")) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + if (evbuffer_get_length(evhttp_request_get_input_buffer(req)) != 0) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + test_ok = 1; + EVUTIL_ASSERT(arg); + event_base_loopexit(arg, NULL); +} + +/* + * HTTP DISPATCHER test + */ + +void +http_dispatcher_cb(struct evhttp_request *req, void *arg) +{ + + struct evbuffer *evb = evbuffer_new(); + TT_BLATHER(("%s: called\n", __func__)); + evbuffer_add_printf(evb, "DISPATCHER_TEST"); + + evhttp_send_reply(req, HTTP_OK, "Everything is fine", evb); + + evbuffer_free(evb); +} + +static void +http_dispatcher_test_done(struct evhttp_request *req, void *arg) +{ + struct event_base *base = arg; + const char *what = "DISPATCHER_TEST"; + + if (!req) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + if (evhttp_request_get_response_code(req) != HTTP_OK) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + if (evhttp_find_header(evhttp_request_get_input_headers(req), "Content-Type") == NULL) { + fprintf(stderr, "FAILED (content type)\n"); + exit(1); + } + + if (evbuffer_get_length(evhttp_request_get_input_buffer(req)) != strlen(what)) { + fprintf(stderr, "FAILED (length %lu vs %lu)\n", + (unsigned long)evbuffer_get_length(evhttp_request_get_input_buffer(req)), (unsigned long)strlen(what)); + exit(1); + } + + if (evbuffer_datacmp(evhttp_request_get_input_buffer(req), what) != 0) { + fprintf(stderr, "FAILED (data)\n"); + exit(1); + } + + test_ok = 1; + event_base_loopexit(base, NULL); +} + +static void +http_dispatcher_test(void *arg) +{ + struct basic_test_data *data = arg; + ev_uint16_t port = 0; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req = NULL; + struct evhttp *http = http_setup(&port, data->base, 0); + + test_ok = 0; + + evcon = evhttp_connection_base_new(data->base, NULL, "127.0.0.1", port); + tt_assert(evcon); + + /* also bind to local host */ + evhttp_connection_set_local_address(evcon, "127.0.0.1"); + + /* + * At this point, we want to schedule an HTTP GET request + * server using our make request method. + */ + + req = evhttp_request_new(http_dispatcher_test_done, data->base); + tt_assert(req); + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost"); + + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/?arg=val") == -1) { + tt_abort_msg("Couldn't make request"); + } + + event_base_dispatch(data->base); + + end: + if (evcon) + evhttp_connection_free(evcon); + if (http) + evhttp_free(http); +} + +/* + * HTTP POST test. + */ + +void http_postrequest_done(struct evhttp_request *, void *); + +#define POST_DATA "Okay. Not really printf" + +static void +http_post_test(void *arg) +{ + struct basic_test_data *data = arg; + ev_uint16_t port = 0; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req = NULL; + struct evhttp *http = http_setup(&port, data->base, 0); + + test_ok = 0; + + evcon = evhttp_connection_base_new(data->base, NULL, "127.0.0.1", port); + tt_assert(evcon); + + /* + * At this point, we want to schedule an HTTP POST request + * server using our make request method. + */ + + req = evhttp_request_new(http_postrequest_done, data->base); + tt_assert(req); + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost"); + evbuffer_add_printf(evhttp_request_get_output_buffer(req), POST_DATA); + + if (evhttp_make_request(evcon, req, EVHTTP_REQ_POST, "/postit") == -1) { + tt_abort_msg("Couldn't make request"); + } + + event_base_dispatch(data->base); + + tt_int_op(test_ok, ==, 1); + + test_ok = 0; + + req = evhttp_request_new(http_postrequest_done, data->base); + tt_assert(req); + + /* Now try with 100-continue. */ + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost"); + evhttp_add_header(evhttp_request_get_output_headers(req), "Expect", "100-continue"); + evbuffer_add_printf(evhttp_request_get_output_buffer(req), POST_DATA); + + if (evhttp_make_request(evcon, req, EVHTTP_REQ_POST, "/postit") == -1) { + tt_abort_msg("Couldn't make request"); + } + + event_base_dispatch(data->base); + + tt_int_op(test_ok, ==, 1); + + evhttp_connection_free(evcon); + evhttp_free(http); + + end: + ; +} + +void +http_post_cb(struct evhttp_request *req, void *arg) +{ + struct evbuffer *evb; + TT_BLATHER(("%s: called\n", __func__)); + + /* Yes, we are expecting a post request */ + if (evhttp_request_get_command(req) != EVHTTP_REQ_POST) { + fprintf(stdout, "FAILED (post type)\n"); + exit(1); + } + + if (evbuffer_get_length(evhttp_request_get_input_buffer(req)) != strlen(POST_DATA)) { + fprintf(stdout, "FAILED (length: %lu vs %lu)\n", + (unsigned long) evbuffer_get_length(evhttp_request_get_input_buffer(req)), (unsigned long) strlen(POST_DATA)); + exit(1); + } + + if (evbuffer_datacmp(evhttp_request_get_input_buffer(req), POST_DATA) != 0) { + fprintf(stdout, "FAILED (data)\n"); + fprintf(stdout, "Got :%s\n", evbuffer_pullup(evhttp_request_get_input_buffer(req),-1)); + fprintf(stdout, "Want:%s\n", POST_DATA); + exit(1); + } + + evb = evbuffer_new(); + evbuffer_add_printf(evb, BASIC_REQUEST_BODY); + + evhttp_send_reply(req, HTTP_OK, "Everything is fine", evb); + + evbuffer_free(evb); +} + +void +http_postrequest_done(struct evhttp_request *req, void *arg) +{ + const char *what = BASIC_REQUEST_BODY; + struct event_base *base = arg; + + if (req == NULL) { + fprintf(stderr, "FAILED (timeout)\n"); + exit(1); + } + + if (evhttp_request_get_response_code(req) != HTTP_OK) { + + fprintf(stderr, "FAILED (response code)\n"); + exit(1); + } + + if (evhttp_find_header(evhttp_request_get_input_headers(req), "Content-Type") == NULL) { + fprintf(stderr, "FAILED (content type)\n"); + exit(1); + } + + if (evbuffer_get_length(evhttp_request_get_input_buffer(req)) != strlen(what)) { + fprintf(stderr, "FAILED (length %lu vs %lu)\n", + (unsigned long)evbuffer_get_length(evhttp_request_get_input_buffer(req)), (unsigned long)strlen(what)); + exit(1); + } + + if (evbuffer_datacmp(evhttp_request_get_input_buffer(req), what) != 0) { + fprintf(stderr, "FAILED (data)\n"); + exit(1); + } + + test_ok = 1; + event_base_loopexit(base, NULL); +} + +/* + * HTTP PUT test, basically just like POST, but ... + */ + +void http_putrequest_done(struct evhttp_request *, void *); + +#define PUT_DATA "Hi, I'm some PUT data" + +static void +http_put_test(void *arg) +{ + struct basic_test_data *data = arg; + ev_uint16_t port = 0; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req = NULL; + struct evhttp *http = http_setup(&port, data->base, 0); + + test_ok = 0; + + evcon = evhttp_connection_base_new(data->base, NULL, "127.0.0.1", port); + tt_assert(evcon); + + /* + * Schedule the HTTP PUT request + */ + + req = evhttp_request_new(http_putrequest_done, data->base); + tt_assert(req); + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "someotherhost"); + evbuffer_add_printf(evhttp_request_get_output_buffer(req), PUT_DATA); + + if (evhttp_make_request(evcon, req, EVHTTP_REQ_PUT, "/putit") == -1) { + tt_abort_msg("Couldn't make request"); + } + + event_base_dispatch(data->base); + + evhttp_connection_free(evcon); + evhttp_free(http); + + tt_int_op(test_ok, ==, 1); + end: + ; +} + +void +http_put_cb(struct evhttp_request *req, void *arg) +{ + struct evbuffer *evb; + TT_BLATHER(("%s: called\n", __func__)); + + /* Expecting a PUT request */ + if (evhttp_request_get_command(req) != EVHTTP_REQ_PUT) { + fprintf(stdout, "FAILED (put type)\n"); + exit(1); + } + + if (evbuffer_get_length(evhttp_request_get_input_buffer(req)) != strlen(PUT_DATA)) { + fprintf(stdout, "FAILED (length: %lu vs %lu)\n", + (unsigned long)evbuffer_get_length(evhttp_request_get_input_buffer(req)), (unsigned long)strlen(PUT_DATA)); + exit(1); + } + + if (evbuffer_datacmp(evhttp_request_get_input_buffer(req), PUT_DATA) != 0) { + fprintf(stdout, "FAILED (data)\n"); + fprintf(stdout, "Got :%s\n", evbuffer_pullup(evhttp_request_get_input_buffer(req),-1)); + fprintf(stdout, "Want:%s\n", PUT_DATA); + exit(1); + } + + evb = evbuffer_new(); + evbuffer_add_printf(evb, "That ain't funny"); + + evhttp_send_reply(req, HTTP_OK, "Everything is great", evb); + + evbuffer_free(evb); +} + +void +http_putrequest_done(struct evhttp_request *req, void *arg) +{ + struct event_base *base = arg; + const char *what = "That ain't funny"; + + if (req == NULL) { + fprintf(stderr, "FAILED (timeout)\n"); + exit(1); + } + + if (evhttp_request_get_response_code(req) != HTTP_OK) { + + fprintf(stderr, "FAILED (response code)\n"); + exit(1); + } + + if (evhttp_find_header(evhttp_request_get_input_headers(req), "Content-Type") == NULL) { + fprintf(stderr, "FAILED (content type)\n"); + exit(1); + } + + if (evbuffer_get_length(evhttp_request_get_input_buffer(req)) != strlen(what)) { + fprintf(stderr, "FAILED (length %lu vs %lu)\n", + (unsigned long)evbuffer_get_length(evhttp_request_get_input_buffer(req)), (unsigned long)strlen(what)); + exit(1); + } + + + if (evbuffer_datacmp(evhttp_request_get_input_buffer(req), what) != 0) { + fprintf(stderr, "FAILED (data)\n"); + exit(1); + } + + test_ok = 1; + event_base_loopexit(base, NULL); +} + +static void +http_failure_readcb(struct bufferevent *bev, void *arg) +{ + const char *what = "400 Bad Request"; + if (evbuffer_contains(bufferevent_get_input(bev), what)) { + test_ok = 2; + bufferevent_disable(bev, EV_READ); + event_base_loopexit(arg, NULL); + } +} + +/* + * Testing that the HTTP server can deal with a malformed request. + */ +static void +http_failure_test(void *arg) +{ + struct basic_test_data *data = arg; + struct bufferevent *bev; + evutil_socket_t fd = EVUTIL_INVALID_SOCKET; + const char *http_request; + ev_uint16_t port = 0; + struct evhttp *http = http_setup(&port, data->base, 0); + + test_ok = 0; + + fd = http_connect("127.0.0.1", port); + tt_assert(fd != EVUTIL_INVALID_SOCKET); + + /* Stupid thing to send a request */ + bev = bufferevent_socket_new(data->base, fd, 0); + bufferevent_setcb(bev, http_failure_readcb, http_writecb, + http_errorcb, data->base); + + http_request = "illegal request\r\n"; + + bufferevent_write(bev, http_request, strlen(http_request)); + + event_base_dispatch(data->base); + + bufferevent_free(bev); + + evhttp_free(http); + + tt_int_op(test_ok, ==, 2); + end: + if (fd >= 0) + evutil_closesocket(fd); +} + +static void +close_detect_done(struct evhttp_request *req, void *arg) +{ + struct timeval tv; + tt_assert(req); + tt_assert(evhttp_request_get_response_code(req) == HTTP_OK); + + test_ok = 1; + + end: + evutil_timerclear(&tv); + tv.tv_usec = 150000; + event_base_loopexit(arg, &tv); +} + +static void +close_detect_launch(evutil_socket_t fd, short what, void *arg) +{ + struct evhttp_connection *evcon = arg; + struct event_base *base = evhttp_connection_get_base(evcon); + struct evhttp_request *req; + + req = evhttp_request_new(close_detect_done, base); + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost"); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/test") == -1) { + tt_fail_msg("Couldn't make request"); + } +} + +static void +close_detect_cb(struct evhttp_request *req, void *arg) +{ + struct evhttp_connection *evcon = arg; + struct event_base *base = evhttp_connection_get_base(evcon); + struct timeval tv; + + if (req != NULL && evhttp_request_get_response_code(req) != HTTP_OK) { + tt_abort_msg("Failed"); + } + + evutil_timerclear(&tv); + tv.tv_sec = 0; /* longer than the http time out */ + tv.tv_usec = 600000; /* longer than the http time out */ + + /* launch a new request on the persistent connection in .3 seconds */ + event_base_once(base, -1, EV_TIMEOUT, close_detect_launch, evcon, &tv); + end: + ; +} + + +static void +http_close_detection_(struct basic_test_data *data, int with_delay) +{ + ev_uint16_t port = 0; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req = NULL; + const struct timeval sec_tenth = { 0, 100000 }; + struct evhttp *http = http_setup(&port, data->base, 0); + + test_ok = 0; + + /* .1 second timeout */ + evhttp_set_timeout_tv(http, &sec_tenth); + + evcon = evhttp_connection_base_new(data->base, NULL, + "127.0.0.1", port); + tt_assert(evcon); + evhttp_connection_set_timeout_tv(evcon, &sec_tenth); + + + tt_assert(evcon); + delayed_client = evcon; + + /* + * At this point, we want to schedule a request to the HTTP + * server using our make request method. + */ + + req = evhttp_request_new(close_detect_cb, evcon); + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost"); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, + req, EVHTTP_REQ_GET, with_delay ? "/largedelay" : "/test") == -1) { + tt_abort_msg("couldn't make request"); + } + + event_base_dispatch(data->base); + + /* at this point, the http server should have no connection */ + tt_assert(TAILQ_FIRST(&http->connections) == NULL); + + end: + if (evcon) + evhttp_connection_free(evcon); + if (http) + evhttp_free(http); +} +static void +http_close_detection_test(void *arg) +{ + http_close_detection_(arg, 0); +} +static void +http_close_detection_delay_test(void *arg) +{ + http_close_detection_(arg, 1); +} + +static void +http_highport_test(void *arg) +{ + struct basic_test_data *data = arg; + int i = -1; + struct evhttp *myhttp = NULL; + + /* Try a few different ports */ + for (i = 0; i < 50; ++i) { + myhttp = evhttp_new(data->base); + if (evhttp_bind_socket(myhttp, "127.0.0.1", 65535 - i) == 0) { + test_ok = 1; + evhttp_free(myhttp); + return; + } + evhttp_free(myhttp); + } + + tt_fail_msg("Couldn't get a high port"); +} + +static void +http_bad_header_test(void *ptr) +{ + struct evkeyvalq headers; + + TAILQ_INIT(&headers); + + tt_want(evhttp_add_header(&headers, "One", "Two") == 0); + tt_want(evhttp_add_header(&headers, "One", "Two\r\n Three") == 0); + tt_want(evhttp_add_header(&headers, "One\r", "Two") == -1); + tt_want(evhttp_add_header(&headers, "One\n", "Two") == -1); + tt_want(evhttp_add_header(&headers, "One", "Two\r") == -1); + tt_want(evhttp_add_header(&headers, "One", "Two\n") == -1); + + evhttp_clear_headers(&headers); +} + +static int validate_header( + const struct evkeyvalq* headers, + const char *key, const char *value) +{ + const char *real_val = evhttp_find_header(headers, key); + tt_assert(real_val != NULL); + tt_want(strcmp(real_val, value) == 0); +end: + return (0); +} + +static void +http_parse_query_test(void *ptr) +{ + struct evkeyvalq headers; + int r; + + TAILQ_INIT(&headers); + + r = evhttp_parse_query("http://www.test.com/?q=test", &headers); + tt_want(validate_header(&headers, "q", "test") == 0); + tt_int_op(r, ==, 0); + evhttp_clear_headers(&headers); + + r = evhttp_parse_query("http://www.test.com/?q=test&foo=bar", &headers); + tt_want(validate_header(&headers, "q", "test") == 0); + tt_want(validate_header(&headers, "foo", "bar") == 0); + tt_int_op(r, ==, 0); + evhttp_clear_headers(&headers); + + r = evhttp_parse_query("http://www.test.com/?q=test+foo", &headers); + tt_want(validate_header(&headers, "q", "test foo") == 0); + tt_int_op(r, ==, 0); + evhttp_clear_headers(&headers); + + r = evhttp_parse_query("http://www.test.com/?q=test%0Afoo", &headers); + tt_want(validate_header(&headers, "q", "test\nfoo") == 0); + tt_int_op(r, ==, 0); + evhttp_clear_headers(&headers); + + r = evhttp_parse_query("http://www.test.com/?q=test%0Dfoo", &headers); + tt_want(validate_header(&headers, "q", "test\rfoo") == 0); + tt_int_op(r, ==, 0); + evhttp_clear_headers(&headers); + + r = evhttp_parse_query("http://www.test.com/?q=test&&q2", &headers); + tt_int_op(r, ==, -1); + evhttp_clear_headers(&headers); + + r = evhttp_parse_query("http://www.test.com/?q=test+this", &headers); + tt_want(validate_header(&headers, "q", "test this") == 0); + tt_int_op(r, ==, 0); + evhttp_clear_headers(&headers); + + r = evhttp_parse_query("http://www.test.com/?q=test&q2=foo", &headers); + tt_int_op(r, ==, 0); + tt_want(validate_header(&headers, "q", "test") == 0); + tt_want(validate_header(&headers, "q2", "foo") == 0); + evhttp_clear_headers(&headers); + + r = evhttp_parse_query("http://www.test.com/?q&q2=foo", &headers); + tt_int_op(r, ==, -1); + evhttp_clear_headers(&headers); + + r = evhttp_parse_query("http://www.test.com/?q=foo&q2", &headers); + tt_int_op(r, ==, -1); + evhttp_clear_headers(&headers); + + r = evhttp_parse_query("http://www.test.com/?q=foo&q2&q3=x", &headers); + tt_int_op(r, ==, -1); + evhttp_clear_headers(&headers); + + r = evhttp_parse_query("http://www.test.com/?q=&q2=&q3=", &headers); + tt_int_op(r, ==, 0); + tt_want(validate_header(&headers, "q", "") == 0); + tt_want(validate_header(&headers, "q2", "") == 0); + tt_want(validate_header(&headers, "q3", "") == 0); + evhttp_clear_headers(&headers); + +end: + evhttp_clear_headers(&headers); +} +static void +http_parse_query_str_test(void *ptr) +{ + struct evkeyvalq headers; + int r; + + TAILQ_INIT(&headers); + + r = evhttp_parse_query_str("http://www.test.com/?q=test", &headers); + tt_assert(evhttp_find_header(&headers, "q") == NULL); + tt_int_op(r, ==, 0); + evhttp_clear_headers(&headers); + + r = evhttp_parse_query_str("q=test", &headers); + tt_want(validate_header(&headers, "q", "test") == 0); + tt_int_op(r, ==, 0); + evhttp_clear_headers(&headers); + +end: + evhttp_clear_headers(&headers); +} + +static void +http_parse_uri_test(void *ptr) +{ + const int nonconform = (ptr != NULL); + const unsigned parse_flags = + nonconform ? EVHTTP_URI_NONCONFORMANT : 0; + struct evhttp_uri *uri = NULL; + char url_tmp[4096]; +#define URI_PARSE(uri) \ + evhttp_uri_parse_with_flags((uri), parse_flags) + +#define TT_URI(want) do { \ + char *ret = evhttp_uri_join(uri, url_tmp, sizeof(url_tmp)); \ + tt_want(ret != NULL); \ + tt_want(ret == url_tmp); \ + if (strcmp(ret,want) != 0) \ + TT_FAIL(("\"%s\" != \"%s\"",ret,want)); \ + } while(0) + + tt_want(evhttp_uri_join(NULL, 0, 0) == NULL); + tt_want(evhttp_uri_join(NULL, url_tmp, 0) == NULL); + tt_want(evhttp_uri_join(NULL, url_tmp, sizeof(url_tmp)) == NULL); + + /* bad URIs: parsing */ +#define BAD(s) do { \ + if (URI_PARSE(s) != NULL) \ + TT_FAIL(("Expected error parsing \"%s\"",s)); \ + } while(0) + /* Nonconformant URIs we can parse: parsing */ +#define NCF(s) do { \ + uri = URI_PARSE(s); \ + if (uri != NULL && !nonconform) { \ + TT_FAIL(("Expected error parsing \"%s\"",s)); \ + } else if (uri == NULL && nonconform) { \ + TT_FAIL(("Couldn't parse nonconformant URI \"%s\"", \ + s)); \ + } \ + if (uri) { \ + tt_want(evhttp_uri_join(uri, url_tmp, \ + sizeof(url_tmp))); \ + evhttp_uri_free(uri); \ + } \ + } while(0) + + NCF("http://www.test.com/ why hello"); + NCF("http://www.test.com/why-hello\x01"); + NCF("http://www.test.com/why-hello?\x01"); + NCF("http://www.test.com/why-hello#\x01"); + BAD("http://www.\x01.test.com/why-hello"); + BAD("http://www.%7test.com/why-hello"); + NCF("http://www.test.com/why-hell%7o"); + BAD("h%3ttp://www.test.com/why-hello"); + NCF("http://www.test.com/why-hello%7"); + NCF("http://www.test.com/why-hell%7o"); + NCF("http://www.test.com/foo?ba%r"); + NCF("http://www.test.com/foo#ba%r"); + BAD("99:99/foo"); + BAD("http://www.test.com:999x/"); + BAD("http://www.test.com:x/"); + BAD("http://[hello-there]/"); + BAD("http://[::1]]/"); + BAD("http://[::1/"); + BAD("http://[foob/"); + BAD("http://[/"); + BAD("http://[ffff:ffff:ffff:ffff:Ffff:ffff:ffff:" + "ffff:ffff:ffff:ffff:ffff:ffff:ffff]/"); + BAD("http://[vX.foo]/"); + BAD("http://[vX.foo]/"); + BAD("http://[v.foo]/"); + BAD("http://[v5.fo%o]/"); + BAD("http://[v5X]/"); + BAD("http://[v5]/"); + BAD("http://[]/"); + BAD("http://f\x01red@www.example.com/"); + BAD("http://f%0red@www.example.com/"); + BAD("http://www.example.com:9999999999999999999999999999999999999/"); + BAD("http://www.example.com:hihi/"); + BAD("://www.example.com/"); + + /* bad URIs: joining */ + uri = evhttp_uri_new(); + tt_want(0==evhttp_uri_set_host(uri, "www.example.com")); + tt_want(evhttp_uri_join(uri, url_tmp, sizeof(url_tmp)) != NULL); + /* not enough space: */ + tt_want(evhttp_uri_join(uri, url_tmp, 3) == NULL); + /* host is set, but path doesn't start with "/": */ + tt_want(0==evhttp_uri_set_path(uri, "hi_mom")); + tt_want(evhttp_uri_join(uri, url_tmp, sizeof(url_tmp)) == NULL); + tt_want(evhttp_uri_join(uri, NULL, sizeof(url_tmp))==NULL); + tt_want(evhttp_uri_join(uri, url_tmp, 0)==NULL); + evhttp_uri_free(uri); + uri = URI_PARSE("mailto:foo@bar"); + tt_want(uri != NULL); + tt_want(evhttp_uri_get_host(uri) == NULL); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(!strcmp(evhttp_uri_get_scheme(uri), "mailto")); + tt_want(!strcmp(evhttp_uri_get_path(uri), "foo@bar")); + tt_want(evhttp_uri_get_query(uri) == NULL); + tt_want(evhttp_uri_get_fragment(uri) == NULL); + TT_URI("mailto:foo@bar"); + evhttp_uri_free(uri); + + uri = evhttp_uri_new(); + /* Bad URI usage: setting invalid values */ + tt_want(-1 == evhttp_uri_set_scheme(uri,"")); + tt_want(-1 == evhttp_uri_set_scheme(uri,"33")); + tt_want(-1 == evhttp_uri_set_scheme(uri,"hi!")); + tt_want(-1 == evhttp_uri_set_userinfo(uri,"hello@")); + tt_want(-1 == evhttp_uri_set_host(uri,"[1.2.3.4]")); + tt_want(-1 == evhttp_uri_set_host(uri,"[")); + tt_want(-1 == evhttp_uri_set_host(uri,"www.[foo].com")); + tt_want(-1 == evhttp_uri_set_port(uri,-3)); + tt_want(-1 == evhttp_uri_set_path(uri,"hello?world")); + tt_want(-1 == evhttp_uri_set_query(uri,"hello#world")); + tt_want(-1 == evhttp_uri_set_fragment(uri,"hello#world")); + /* Valid URI usage: setting valid values */ + tt_want(0 == evhttp_uri_set_scheme(uri,"http")); + tt_want(0 == evhttp_uri_set_scheme(uri,NULL)); + tt_want(0 == evhttp_uri_set_userinfo(uri,"username:pass")); + tt_want(0 == evhttp_uri_set_userinfo(uri,NULL)); + tt_want(0 == evhttp_uri_set_host(uri,"www.example.com")); + tt_want(0 == evhttp_uri_set_host(uri,"1.2.3.4")); + tt_want(0 == evhttp_uri_set_host(uri,"[1:2:3:4::]")); + tt_want(0 == evhttp_uri_set_host(uri,"[v7.wobblewobble]")); + tt_want(0 == evhttp_uri_set_host(uri,NULL)); + tt_want(0 == evhttp_uri_set_host(uri,"")); + tt_want(0 == evhttp_uri_set_port(uri, -1)); + tt_want(0 == evhttp_uri_set_port(uri, 80)); + tt_want(0 == evhttp_uri_set_port(uri, 65535)); + tt_want(0 == evhttp_uri_set_path(uri, "")); + tt_want(0 == evhttp_uri_set_path(uri, "/documents/public/index.html")); + tt_want(0 == evhttp_uri_set_path(uri, NULL)); + tt_want(0 == evhttp_uri_set_query(uri, "key=val&key2=val2")); + tt_want(0 == evhttp_uri_set_query(uri, "keyvalblarg")); + tt_want(0 == evhttp_uri_set_query(uri, "")); + tt_want(0 == evhttp_uri_set_query(uri, NULL)); + tt_want(0 == evhttp_uri_set_fragment(uri, "")); + tt_want(0 == evhttp_uri_set_fragment(uri, "here?i?am")); + tt_want(0 == evhttp_uri_set_fragment(uri, NULL)); + evhttp_uri_free(uri); + + /* Valid parsing */ + uri = URI_PARSE("http://www.test.com/?q=t%33est"); + tt_want(strcmp(evhttp_uri_get_scheme(uri), "http") == 0); + tt_want(strcmp(evhttp_uri_get_host(uri), "www.test.com") == 0); + tt_want(strcmp(evhttp_uri_get_path(uri), "/") == 0); + tt_want(strcmp(evhttp_uri_get_query(uri), "q=t%33est") == 0); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(evhttp_uri_get_fragment(uri) == NULL); + TT_URI("http://www.test.com/?q=t%33est"); + evhttp_uri_free(uri); + + uri = URI_PARSE("http://%77ww.test.com"); + tt_want(strcmp(evhttp_uri_get_scheme(uri), "http") == 0); + tt_want(strcmp(evhttp_uri_get_host(uri), "%77ww.test.com") == 0); + tt_want(strcmp(evhttp_uri_get_path(uri), "") == 0); + tt_want(evhttp_uri_get_query(uri) == NULL); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(evhttp_uri_get_fragment(uri) == NULL); + TT_URI("http://%77ww.test.com"); + evhttp_uri_free(uri); + + uri = URI_PARSE("http://www.test.com?q=test"); + tt_want(strcmp(evhttp_uri_get_scheme(uri), "http") == 0); + tt_want(strcmp(evhttp_uri_get_host(uri), "www.test.com") == 0); + tt_want(strcmp(evhttp_uri_get_path(uri), "") == 0); + tt_want(strcmp(evhttp_uri_get_query(uri), "q=test") == 0); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(evhttp_uri_get_fragment(uri) == NULL); + TT_URI("http://www.test.com?q=test"); + evhttp_uri_free(uri); + + uri = URI_PARSE("http://www.test.com#fragment"); + tt_want(strcmp(evhttp_uri_get_scheme(uri), "http") == 0); + tt_want(strcmp(evhttp_uri_get_host(uri), "www.test.com") == 0); + tt_want(strcmp(evhttp_uri_get_path(uri), "") == 0); + tt_want(evhttp_uri_get_query(uri) == NULL); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want_str_op(evhttp_uri_get_fragment(uri), ==, "fragment"); + TT_URI("http://www.test.com#fragment"); + evhttp_uri_free(uri); + + uri = URI_PARSE("http://8000/"); + tt_want(strcmp(evhttp_uri_get_scheme(uri), "http") == 0); + tt_want(strcmp(evhttp_uri_get_host(uri), "8000") == 0); + tt_want(strcmp(evhttp_uri_get_path(uri), "/") == 0); + tt_want(evhttp_uri_get_query(uri) == NULL); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(evhttp_uri_get_fragment(uri) == NULL); + TT_URI("http://8000/"); + evhttp_uri_free(uri); + + uri = URI_PARSE("http://:8000/"); + tt_want(strcmp(evhttp_uri_get_scheme(uri), "http") == 0); + tt_want(strcmp(evhttp_uri_get_host(uri), "") == 0); + tt_want(strcmp(evhttp_uri_get_path(uri), "/") == 0); + tt_want(evhttp_uri_get_query(uri) == NULL); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == 8000); + tt_want(evhttp_uri_get_fragment(uri) == NULL); + TT_URI("http://:8000/"); + evhttp_uri_free(uri); + + uri = URI_PARSE("http://www.test.com:/"); /* empty port */ + tt_want(strcmp(evhttp_uri_get_scheme(uri), "http") == 0); + tt_want(strcmp(evhttp_uri_get_host(uri), "www.test.com") == 0); + tt_want_str_op(evhttp_uri_get_path(uri), ==, "/"); + tt_want(evhttp_uri_get_query(uri) == NULL); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(evhttp_uri_get_fragment(uri) == NULL); + TT_URI("http://www.test.com/"); + evhttp_uri_free(uri); + + uri = URI_PARSE("http://www.test.com:"); /* empty port 2 */ + tt_want(strcmp(evhttp_uri_get_scheme(uri), "http") == 0); + tt_want(strcmp(evhttp_uri_get_host(uri), "www.test.com") == 0); + tt_want(strcmp(evhttp_uri_get_path(uri), "") == 0); + tt_want(evhttp_uri_get_query(uri) == NULL); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(evhttp_uri_get_fragment(uri) == NULL); + TT_URI("http://www.test.com"); + evhttp_uri_free(uri); + + uri = URI_PARSE("ftp://www.test.com/?q=test"); + tt_want(strcmp(evhttp_uri_get_scheme(uri), "ftp") == 0); + tt_want(strcmp(evhttp_uri_get_host(uri), "www.test.com") == 0); + tt_want(strcmp(evhttp_uri_get_path(uri), "/") == 0); + tt_want(strcmp(evhttp_uri_get_query(uri), "q=test") == 0); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(evhttp_uri_get_fragment(uri) == NULL); + TT_URI("ftp://www.test.com/?q=test"); + evhttp_uri_free(uri); + + uri = URI_PARSE("ftp://[::1]:999/?q=test"); + tt_want(strcmp(evhttp_uri_get_scheme(uri), "ftp") == 0); + tt_want(strcmp(evhttp_uri_get_host(uri), "[::1]") == 0); + tt_want(strcmp(evhttp_uri_get_path(uri), "/") == 0); + tt_want(strcmp(evhttp_uri_get_query(uri), "q=test") == 0); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == 999); + tt_want(evhttp_uri_get_fragment(uri) == NULL); + TT_URI("ftp://[::1]:999/?q=test"); + evhttp_uri_free(uri); + + uri = URI_PARSE("ftp://[ff00::127.0.0.1]/?q=test"); + tt_want(strcmp(evhttp_uri_get_scheme(uri), "ftp") == 0); + tt_want(strcmp(evhttp_uri_get_host(uri), "[ff00::127.0.0.1]") == 0); + tt_want(strcmp(evhttp_uri_get_path(uri), "/") == 0); + tt_want(strcmp(evhttp_uri_get_query(uri), "q=test") == 0); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(evhttp_uri_get_fragment(uri) == NULL); + TT_URI("ftp://[ff00::127.0.0.1]/?q=test"); + evhttp_uri_free(uri); + + uri = URI_PARSE("ftp://[v99.not_(any:time)_soon]/?q=test"); + tt_want(strcmp(evhttp_uri_get_scheme(uri), "ftp") == 0); + tt_want(strcmp(evhttp_uri_get_host(uri), "[v99.not_(any:time)_soon]") == 0); + tt_want(strcmp(evhttp_uri_get_path(uri), "/") == 0); + tt_want(strcmp(evhttp_uri_get_query(uri), "q=test") == 0); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(evhttp_uri_get_fragment(uri) == NULL); + TT_URI("ftp://[v99.not_(any:time)_soon]/?q=test"); + evhttp_uri_free(uri); + + uri = URI_PARSE("scheme://user:pass@foo.com:42/?q=test&s=some+thing#fragment"); + tt_want(strcmp(evhttp_uri_get_scheme(uri), "scheme") == 0); + tt_want(strcmp(evhttp_uri_get_userinfo(uri), "user:pass") == 0); + tt_want(strcmp(evhttp_uri_get_host(uri), "foo.com") == 0); + tt_want(evhttp_uri_get_port(uri) == 42); + tt_want(strcmp(evhttp_uri_get_path(uri), "/") == 0); + tt_want(strcmp(evhttp_uri_get_query(uri), "q=test&s=some+thing") == 0); + tt_want(strcmp(evhttp_uri_get_fragment(uri), "fragment") == 0); + TT_URI("scheme://user:pass@foo.com:42/?q=test&s=some+thing#fragment"); + evhttp_uri_free(uri); + + uri = URI_PARSE("scheme://user@foo.com/#fragment"); + tt_want(strcmp(evhttp_uri_get_scheme(uri), "scheme") == 0); + tt_want(strcmp(evhttp_uri_get_userinfo(uri), "user") == 0); + tt_want(strcmp(evhttp_uri_get_host(uri), "foo.com") == 0); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(strcmp(evhttp_uri_get_path(uri), "/") == 0); + tt_want(evhttp_uri_get_query(uri) == NULL); + tt_want(strcmp(evhttp_uri_get_fragment(uri), "fragment") == 0); + TT_URI("scheme://user@foo.com/#fragment"); + evhttp_uri_free(uri); + + uri = URI_PARSE("scheme://%75ser@foo.com/#frag@ment"); + tt_want(strcmp(evhttp_uri_get_scheme(uri), "scheme") == 0); + tt_want(strcmp(evhttp_uri_get_userinfo(uri), "%75ser") == 0); + tt_want(strcmp(evhttp_uri_get_host(uri), "foo.com") == 0); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(strcmp(evhttp_uri_get_path(uri), "/") == 0); + tt_want(evhttp_uri_get_query(uri) == NULL); + tt_want(strcmp(evhttp_uri_get_fragment(uri), "frag@ment") == 0); + TT_URI("scheme://%75ser@foo.com/#frag@ment"); + evhttp_uri_free(uri); + + uri = URI_PARSE("file:///some/path/to/the/file"); + tt_want(strcmp(evhttp_uri_get_scheme(uri), "file") == 0); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(strcmp(evhttp_uri_get_host(uri), "") == 0); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(strcmp(evhttp_uri_get_path(uri), "/some/path/to/the/file") == 0); + tt_want(evhttp_uri_get_query(uri) == NULL); + tt_want(evhttp_uri_get_fragment(uri) == NULL); + TT_URI("file:///some/path/to/the/file"); + evhttp_uri_free(uri); + + uri = URI_PARSE("///some/path/to/the-file"); + tt_want(uri != NULL); + tt_want(evhttp_uri_get_scheme(uri) == NULL); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(strcmp(evhttp_uri_get_host(uri), "") == 0); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(strcmp(evhttp_uri_get_path(uri), "/some/path/to/the-file") == 0); + tt_want(evhttp_uri_get_query(uri) == NULL); + tt_want(evhttp_uri_get_fragment(uri) == NULL); + TT_URI("///some/path/to/the-file"); + evhttp_uri_free(uri); + + uri = URI_PARSE("/s:ome/path/to/the-file?q=99#fred"); + tt_want(uri != NULL); + tt_want(evhttp_uri_get_scheme(uri) == NULL); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_host(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(strcmp(evhttp_uri_get_path(uri), "/s:ome/path/to/the-file") == 0); + tt_want(strcmp(evhttp_uri_get_query(uri), "q=99") == 0); + tt_want(strcmp(evhttp_uri_get_fragment(uri), "fred") == 0); + TT_URI("/s:ome/path/to/the-file?q=99#fred"); + evhttp_uri_free(uri); + + uri = URI_PARSE("relative/path/with/co:lon"); + tt_want(uri != NULL); + tt_want(evhttp_uri_get_scheme(uri) == NULL); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_host(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(strcmp(evhttp_uri_get_path(uri), "relative/path/with/co:lon") == 0); + tt_want(evhttp_uri_get_query(uri) == NULL); + tt_want(evhttp_uri_get_fragment(uri) == NULL); + TT_URI("relative/path/with/co:lon"); + evhttp_uri_free(uri); + + uri = URI_PARSE("bob?q=99&q2=q?33#fr?ed"); + tt_want(uri != NULL); + tt_want(evhttp_uri_get_scheme(uri) == NULL); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_host(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(strcmp(evhttp_uri_get_path(uri), "bob") == 0); + tt_want(strcmp(evhttp_uri_get_query(uri), "q=99&q2=q?33") == 0); + tt_want(strcmp(evhttp_uri_get_fragment(uri), "fr?ed") == 0); + TT_URI("bob?q=99&q2=q?33#fr?ed"); + evhttp_uri_free(uri); + + uri = URI_PARSE("#fr?ed"); + tt_want(uri != NULL); + tt_want(evhttp_uri_get_scheme(uri) == NULL); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_host(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(strcmp(evhttp_uri_get_path(uri), "") == 0); + tt_want(evhttp_uri_get_query(uri) == NULL); + tt_want(strcmp(evhttp_uri_get_fragment(uri), "fr?ed") == 0); + TT_URI("#fr?ed"); + evhttp_uri_free(uri); +#undef URI_PARSE +#undef TT_URI +#undef BAD +} + +static void +http_uriencode_test(void *ptr) +{ + char *s=NULL, *s2=NULL; + size_t sz; + int bytes_decoded; + +#define ENC(from,want,plus) do { \ + s = evhttp_uriencode((from), -1, (plus)); \ + tt_assert(s); \ + tt_str_op(s,==,(want)); \ + sz = -1; \ + s2 = evhttp_uridecode((s), (plus), &sz); \ + tt_assert(s2); \ + tt_str_op(s2,==,(from)); \ + tt_int_op(sz,==,strlen(from)); \ + free(s); \ + free(s2); \ + s = s2 = NULL; \ + } while (0) + +#define DEC(from,want,dp) do { \ + s = evhttp_uridecode((from),(dp),&sz); \ + tt_assert(s); \ + tt_str_op(s,==,(want)); \ + tt_int_op(sz,==,strlen(want)); \ + free(s); \ + s = NULL; \ + } while (0) + +#define OLD_DEC(from,want) do { \ + s = evhttp_decode_uri((from)); \ + tt_assert(s); \ + tt_str_op(s,==,(want)); \ + free(s); \ + s = NULL; \ + } while (0) + + + ENC("Hello", "Hello",0); + ENC("99", "99",0); + ENC("", "",0); + ENC( + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789-.~_", + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789-.~_",0); + ENC(" ", "%20",0); + ENC(" ", "+",1); + ENC("\xff\xf0\xe0", "%FF%F0%E0",0); + ENC("\x01\x19", "%01%19",1); + ENC("http://www.ietf.org/rfc/rfc3986.txt", + "http%3A%2F%2Fwww.ietf.org%2Frfc%2Frfc3986.txt",1); + + ENC("1+2=3", "1%2B2%3D3",1); + ENC("1+2=3", "1%2B2%3D3",0); + + /* Now try encoding with internal NULs. */ + s = evhttp_uriencode("hello\0world", 11, 0); + tt_assert(s); + tt_str_op(s,==,"hello%00world"); + free(s); + s = NULL; + + /* Now try decoding just part of string. */ + s = malloc(6 + 1 /* NUL byte */); + bytes_decoded = evhttp_decode_uri_internal("hello%20%20", 6, s, 0); + tt_assert(s); + tt_int_op(bytes_decoded,==,6); + tt_str_op(s,==,"hello%"); + free(s); + s = NULL; + + /* Now try out some decoding cases that we don't generate with + * encode_uri: Make sure that malformed stuff doesn't crash... */ + DEC("%%xhello th+ere \xff", + "%%xhello th+ere \xff", 0); + /* Make sure plus decoding works */ + DEC("plus+should%20work+", "plus should work ",1); + /* Try some lowercase hex */ + DEC("%f0%a0%b0", "\xf0\xa0\xb0",1); + + /* Try an internal NUL. */ + sz = 0; + s = evhttp_uridecode("%00%00x%00%00", 1, &sz); + tt_int_op(sz,==,5); + tt_assert(!memcmp(s, "\0\0x\0\0", 5)); + free(s); + s = NULL; + + /* Try with size == NULL */ + sz = 0; + s = evhttp_uridecode("%00%00x%00%00", 1, NULL); + tt_assert(!memcmp(s, "\0\0x\0\0", 5)); + free(s); + s = NULL; + + /* Test out the crazy old behavior of the deprecated + * evhttp_decode_uri */ + OLD_DEC("http://example.com/normal+path/?key=val+with+spaces", + "http://example.com/normal+path/?key=val with spaces"); + +end: + if (s) + free(s); + if (s2) + free(s2); +#undef ENC +#undef DEC +#undef OLD_DEC +} + +static void +http_base_test(void *ptr) +{ + struct event_base *base = NULL; + struct bufferevent *bev; + evutil_socket_t fd; + const char *http_request; + ev_uint16_t port = 0; + struct evhttp *http; + + test_ok = 0; + base = event_base_new(); + tt_assert(base); + http = http_setup(&port, base, 0); + + fd = http_connect("127.0.0.1", port); + tt_assert(fd != EVUTIL_INVALID_SOCKET); + + /* Stupid thing to send a request */ + bev = bufferevent_socket_new(base, fd, 0); + bufferevent_setcb(bev, http_readcb, http_writecb, + http_errorcb, base); + bufferevent_base_set(base, bev); + + http_request = + "GET /test HTTP/1.1\r\n" + "Host: somehost\r\n" + "Connection: close\r\n" + "\r\n"; + + bufferevent_write(bev, http_request, strlen(http_request)); + + event_base_dispatch(base); + + bufferevent_free(bev); + evutil_closesocket(fd); + + evhttp_free(http); + + tt_int_op(test_ok, ==, 2); + +end: + if (base) + event_base_free(base); +} + +/* + * the server is just going to close the connection if it times out during + * reading the headers. + */ + +static void +http_incomplete_readcb(struct bufferevent *bev, void *arg) +{ + test_ok = -1; + event_base_loopexit(exit_base,NULL); +} + +static void +http_incomplete_errorcb(struct bufferevent *bev, short what, void *arg) +{ + /** For ssl */ + if (what & BEV_EVENT_CONNECTED) + return; + + if (what == (BEV_EVENT_READING|BEV_EVENT_EOF)) + test_ok++; + else + test_ok = -2; + event_base_loopexit(exit_base,NULL); +} + +static void +http_incomplete_writecb(struct bufferevent *bev, void *arg) +{ + if (arg != NULL) { + evutil_socket_t fd = *(evutil_socket_t *)arg; + /* terminate the write side to simulate EOF */ + shutdown(fd, EVUTIL_SHUT_WR); + } + if (evbuffer_get_length(bufferevent_get_output(bev)) == 0) { + /* enable reading of the reply */ + bufferevent_enable(bev, EV_READ); + test_ok++; + } +} + +static void +http_incomplete_test_(struct basic_test_data *data, int use_timeout, int ssl) +{ + struct bufferevent *bev; + evutil_socket_t fd; + const char *http_request; + ev_uint16_t port = 0; + struct timeval tv_start, tv_end; + struct evhttp *http = http_setup(&port, data->base, ssl ? HTTP_BIND_SSL : 0); + + exit_base = data->base; + test_ok = 0; + + evhttp_set_timeout(http, 1); + + fd = http_connect("127.0.0.1", port); + tt_assert(fd != EVUTIL_INVALID_SOCKET); + + /* Stupid thing to send a request */ + bev = create_bev(data->base, fd, ssl, 0); + bufferevent_setcb(bev, + http_incomplete_readcb, http_incomplete_writecb, + http_incomplete_errorcb, use_timeout ? NULL : &fd); + + http_request = + "GET /test HTTP/1.1\r\n" + "Host: somehost\r\n"; + + bufferevent_write(bev, http_request, strlen(http_request)); + + evutil_gettimeofday(&tv_start, NULL); + + event_base_dispatch(data->base); + + evutil_gettimeofday(&tv_end, NULL); + evutil_timersub(&tv_end, &tv_start, &tv_end); + + bufferevent_free(bev); + if (use_timeout) { + evutil_closesocket(fd); + fd = EVUTIL_INVALID_SOCKET; + } + + evhttp_free(http); + + if (use_timeout && tv_end.tv_sec >= 3) { + tt_abort_msg("time"); + } else if (!use_timeout && tv_end.tv_sec >= 1) { + /* we should be done immediately */ + tt_abort_msg("time"); + } + + tt_int_op(test_ok, ==, 2); + end: + if (fd >= 0) + evutil_closesocket(fd); +} +static void http_incomplete_test(void *arg) +{ http_incomplete_test_(arg, 0, 0); } +static void http_incomplete_timeout_test(void *arg) +{ http_incomplete_test_(arg, 1, 0); } + + +/* + * the server is going to reply with chunked data. + */ + +static void +http_chunked_readcb(struct bufferevent *bev, void *arg) +{ + /* nothing here */ +} + +static void +http_chunked_errorcb(struct bufferevent *bev, short what, void *arg) +{ + struct evhttp_request *req = NULL; + + /** SSL */ + if (what & BEV_EVENT_CONNECTED) + return; + + if (!test_ok) + goto out; + + test_ok = -1; + + if ((what & BEV_EVENT_EOF) != 0) { + const char *header; + enum message_read_status done; + req = evhttp_request_new(NULL, NULL); + + /* req->kind = EVHTTP_RESPONSE; */ + done = evhttp_parse_firstline_(req, bufferevent_get_input(bev)); + if (done != ALL_DATA_READ) + goto out; + + done = evhttp_parse_headers_(req, bufferevent_get_input(bev)); + if (done != ALL_DATA_READ) + goto out; + + header = evhttp_find_header(evhttp_request_get_input_headers(req), "Transfer-Encoding"); + if (header == NULL || strcmp(header, "chunked")) + goto out; + + header = evhttp_find_header(evhttp_request_get_input_headers(req), "Connection"); + if (header == NULL || strcmp(header, "close")) + goto out; + + header = evbuffer_readln(bufferevent_get_input(bev), NULL, EVBUFFER_EOL_CRLF); + if (header == NULL) + goto out; + /* 13 chars */ + if (strcmp(header, "d")) { + free((void*)header); + goto out; + } + free((void*)header); + + if (strncmp((char *)evbuffer_pullup(bufferevent_get_input(bev), 13), + "This is funny", 13)) + goto out; + + evbuffer_drain(bufferevent_get_input(bev), 13 + 2); + + header = evbuffer_readln(bufferevent_get_input(bev), NULL, EVBUFFER_EOL_CRLF); + if (header == NULL) + goto out; + /* 18 chars */ + if (strcmp(header, "12")) + goto out; + free((char *)header); + + if (strncmp((char *)evbuffer_pullup(bufferevent_get_input(bev), 18), + "but not hilarious.", 18)) + goto out; + + evbuffer_drain(bufferevent_get_input(bev), 18 + 2); + + header = evbuffer_readln(bufferevent_get_input(bev), NULL, EVBUFFER_EOL_CRLF); + if (header == NULL) + goto out; + /* 8 chars */ + if (strcmp(header, "8")) { + free((void*)header); + goto out; + } + free((char *)header); + + if (strncmp((char *)evbuffer_pullup(bufferevent_get_input(bev), 8), + "bwv 1052.", 8)) + goto out; + + evbuffer_drain(bufferevent_get_input(bev), 8 + 2); + + header = evbuffer_readln(bufferevent_get_input(bev), NULL, EVBUFFER_EOL_CRLF); + if (header == NULL) + goto out; + /* 0 chars */ + if (strcmp(header, "0")) { + free((void*)header); + goto out; + } + free((void *)header); + + test_ok = 2; + } + +out: + if (req) + evhttp_request_free(req); + + event_base_loopexit(arg, NULL); +} + +static void +http_chunked_writecb(struct bufferevent *bev, void *arg) +{ + if (evbuffer_get_length(bufferevent_get_output(bev)) == 0) { + /* enable reading of the reply */ + bufferevent_enable(bev, EV_READ); + test_ok++; + } +} + +static void +http_chunked_request_done(struct evhttp_request *req, void *arg) +{ + if (evhttp_request_get_response_code(req) != HTTP_OK) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + if (evhttp_find_header(evhttp_request_get_input_headers(req), + "Transfer-Encoding") == NULL) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + if (evbuffer_get_length(evhttp_request_get_input_buffer(req)) != 13 + 18 + 8) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + if (strncmp((char *)evbuffer_pullup(evhttp_request_get_input_buffer(req), 13 + 18 + 8), + "This is funnybut not hilarious.bwv 1052", + 13 + 18 + 8)) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + test_ok = 1; + event_base_loopexit(arg, NULL); +} + +static void +http_chunk_out_test_impl(void *arg, int ssl) +{ + struct basic_test_data *data = arg; + struct bufferevent *bev = NULL; + evutil_socket_t fd; + const char *http_request; + ev_uint16_t port = 0; + struct timeval tv_start, tv_end; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req = NULL; + int i; + struct evhttp *http = http_setup(&port, data->base, ssl ? HTTP_BIND_SSL : 0); + + exit_base = data->base; + test_ok = 0; + + fd = http_connect("127.0.0.1", port); + tt_assert(fd != EVUTIL_INVALID_SOCKET); + + /* Stupid thing to send a request */ + bev = create_bev(data->base, fd, ssl, BEV_OPT_CLOSE_ON_FREE); + bufferevent_setcb(bev, + http_chunked_readcb, http_chunked_writecb, + http_chunked_errorcb, data->base); + + http_request = + "GET /chunked HTTP/1.1\r\n" + "Host: somehost\r\n" + "Connection: close\r\n" + "\r\n"; + + bufferevent_write(bev, http_request, strlen(http_request)); + + evutil_gettimeofday(&tv_start, NULL); + + event_base_dispatch(data->base); + + bufferevent_free(bev); + bev = NULL; + + evutil_gettimeofday(&tv_end, NULL); + evutil_timersub(&tv_end, &tv_start, &tv_end); + + tt_int_op(tv_end.tv_sec, <, 1); + + tt_int_op(test_ok, ==, 2); + + /* now try again with the regular connection object */ + bev = create_bev(data->base, -1, ssl, BEV_OPT_CLOSE_ON_FREE); + evcon = evhttp_connection_base_bufferevent_new( + data->base, NULL, bev, "127.0.0.1", port); + tt_assert(evcon); + + /* make two requests to check the keepalive behavior */ + for (i = 0; i < 2; i++) { + test_ok = 0; + req = evhttp_request_new(http_chunked_request_done, data->base); + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost"); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/chunked") == -1) { + tt_abort_msg("Couldn't make request"); + } + + event_base_dispatch(data->base); + + tt_assert(test_ok == 1); + } + + end: + if (evcon) + evhttp_connection_free(evcon); + if (http) + evhttp_free(http); +} +static void http_chunk_out_test(void *arg) +{ http_chunk_out_test_impl(arg, 0); } + +static void +http_stream_out_test_impl(void *arg, int ssl) +{ + struct basic_test_data *data = arg; + ev_uint16_t port = 0; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req = NULL; + struct bufferevent *bev; + struct evhttp *http = http_setup(&port, data->base, ssl ? HTTP_BIND_SSL : 0); + + test_ok = 0; + exit_base = data->base; + + bev = create_bev(data->base, -1, ssl, 0); + evcon = evhttp_connection_base_bufferevent_new( + data->base, NULL, bev, "127.0.0.1", port); + tt_assert(evcon); + + /* + * At this point, we want to schedule a request to the HTTP + * server using our make request method. + */ + + req = evhttp_request_new(http_request_done, + (void *)"This is funnybut not hilarious.bwv 1052"); + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost"); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/streamed") + == -1) { + tt_abort_msg("Couldn't make request"); + } + + event_base_dispatch(data->base); + + end: + if (evcon) + evhttp_connection_free(evcon); + if (http) + evhttp_free(http); +} +static void http_stream_out_test(void *arg) +{ http_stream_out_test_impl(arg, 0); } + +static void +http_stream_in_chunk(struct evhttp_request *req, void *arg) +{ + struct evbuffer *reply = arg; + + if (evhttp_request_get_response_code(req) != HTTP_OK) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + evbuffer_add_buffer(reply, evhttp_request_get_input_buffer(req)); +} + +static void +http_stream_in_done(struct evhttp_request *req, void *arg) +{ + if (evbuffer_get_length(evhttp_request_get_input_buffer(req)) != 0) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + event_base_loopexit(exit_base, NULL); +} + +/** + * Makes a request and reads the response in chunks. + */ +static void +http_stream_in_test_(struct basic_test_data *data, char const *url, + size_t expected_len, char const *expected) +{ + struct evhttp_connection *evcon; + struct evbuffer *reply = evbuffer_new(); + struct evhttp_request *req = NULL; + ev_uint16_t port = 0; + struct evhttp *http = http_setup(&port, data->base, 0); + + exit_base = data->base; + + evcon = evhttp_connection_base_new(data->base, NULL,"127.0.0.1", port); + tt_assert(evcon); + + req = evhttp_request_new(http_stream_in_done, reply); + evhttp_request_set_chunked_cb(req, http_stream_in_chunk); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, url) == -1) { + tt_abort_msg("Couldn't make request"); + } + + event_base_dispatch(data->base); + + if (evbuffer_get_length(reply) != expected_len) { + TT_DIE(("reply length %lu; expected %lu; FAILED (%s)\n", + (unsigned long)evbuffer_get_length(reply), + (unsigned long)expected_len, + (char*)evbuffer_pullup(reply, -1))); + } + + if (memcmp(evbuffer_pullup(reply, -1), expected, expected_len) != 0) { + tt_abort_msg("Memory mismatch"); + } + + test_ok = 1; + end: + if (reply) + evbuffer_free(reply); + if (evcon) + evhttp_connection_free(evcon); + if (http) + evhttp_free(http); +} + +static void +http_stream_in_test(void *arg) +{ + http_stream_in_test_(arg, "/chunked", 13 + 18 + 8, + "This is funnybut not hilarious.bwv 1052"); + + http_stream_in_test_(arg, "/test", strlen(BASIC_REQUEST_BODY), + BASIC_REQUEST_BODY); +} + +static void +http_stream_in_cancel_chunk(struct evhttp_request *req, void *arg) +{ + tt_int_op(evhttp_request_get_response_code(req), ==, HTTP_OK); + + end: + evhttp_cancel_request(req); + event_base_loopexit(arg, NULL); +} + +static void +http_stream_in_cancel_done(struct evhttp_request *req, void *arg) +{ + /* should never be called */ + tt_fail_msg("In cancel done"); +} + +static void +http_stream_in_cancel_test(void *arg) +{ + struct basic_test_data *data = arg; + struct evhttp_connection *evcon; + struct evhttp_request *req = NULL; + ev_uint16_t port = 0; + struct evhttp *http = http_setup(&port, data->base, 0); + + evcon = evhttp_connection_base_new(data->base, NULL, "127.0.0.1", port); + tt_assert(evcon); + + req = evhttp_request_new(http_stream_in_cancel_done, data->base); + evhttp_request_set_chunked_cb(req, http_stream_in_cancel_chunk); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/chunked") == -1) { + tt_abort_msg("Couldn't make request"); + } + + event_base_dispatch(data->base); + + test_ok = 1; + end: + evhttp_connection_free(evcon); + evhttp_free(http); + +} + +static void +http_connection_fail_done(struct evhttp_request *req, void *arg) +{ + struct evhttp_connection *evcon = arg; + struct event_base *base = evhttp_connection_get_base(evcon); + + /* An ENETUNREACH error results in an unrecoverable + * evhttp_connection error (see evhttp_connection_fail_()). The + * connection will be reset, and the user will be notified with a NULL + * req parameter. */ + tt_assert(!req); + + evhttp_connection_free(evcon); + + test_ok = 1; + + end: + event_base_loopexit(base, NULL); +} + +/* Test unrecoverable evhttp_connection errors by generating an ENETUNREACH + * error on connection. */ +static void +http_connection_fail_test_impl(void *arg, int ssl) +{ + struct basic_test_data *data = arg; + ev_uint16_t port = 0; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req = NULL; + struct bufferevent *bev; + struct evhttp *http = http_setup(&port, data->base, ssl ? HTTP_BIND_SSL : 0); + + exit_base = data->base; + test_ok = 0; + + /* auto detect a port */ + evhttp_free(http); + + bev = create_bev(data->base, -1, ssl, 0); + /* Pick an unroutable address. This administratively scoped multicast + * address should do when working with TCP. */ + evcon = evhttp_connection_base_bufferevent_new( + data->base, NULL, bev, "239.10.20.30", 80); + tt_assert(evcon); + + /* + * At this point, we want to schedule an HTTP GET request + * server using our make request method. + */ + + req = evhttp_request_new(http_connection_fail_done, evcon); + tt_assert(req); + + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/") == -1) { + tt_abort_msg("Couldn't make request"); + } + + event_base_dispatch(data->base); + + tt_int_op(test_ok, ==, 1); + + end: + ; +} +static void http_connection_fail_test(void *arg) +{ http_connection_fail_test_impl(arg, 0); } + +static void +http_connection_retry_done(struct evhttp_request *req, void *arg) +{ + tt_assert(req); + tt_int_op(evhttp_request_get_response_code(req), !=, HTTP_OK); + if (evhttp_find_header(evhttp_request_get_input_headers(req), "Content-Type") != NULL) { + tt_abort_msg("(content type)\n"); + } + + tt_uint_op(evbuffer_get_length(evhttp_request_get_input_buffer(req)), ==, 0); + + test_ok = 1; + end: + event_base_loopexit(arg,NULL); +} + +struct http_server +{ + ev_uint16_t port; + int ssl; + struct evhttp *http; +}; +static struct event_base *http_make_web_server_base=NULL; +static void +http_make_web_server(evutil_socket_t fd, short what, void *arg) +{ + struct http_server *hs = (struct http_server *)arg; + hs->http = http_setup(&hs->port, http_make_web_server_base, hs->ssl ? HTTP_BIND_SSL : 0); +} + +static void +http_simple_test_impl(void *arg, int ssl, int dirty, const char *uri) +{ + struct basic_test_data *data = arg; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req = NULL; + struct bufferevent *bev; + struct http_server hs = { 0, ssl, NULL, }; + struct evhttp *http = http_setup(&hs.port, data->base, ssl ? HTTP_BIND_SSL : 0); + + exit_base = data->base; + test_ok = 0; + + bev = create_bev(data->base, -1, ssl, 0); +#ifdef EVENT__HAVE_OPENSSL + bufferevent_openssl_set_allow_dirty_shutdown(bev, dirty); +#endif + + evcon = evhttp_connection_base_bufferevent_new( + data->base, NULL, bev, "127.0.0.1", hs.port); + tt_assert(evcon); + evhttp_connection_set_local_address(evcon, "127.0.0.1"); + + req = evhttp_request_new(http_request_done, (void*) BASIC_REQUEST_BODY); + tt_assert(req); + + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, uri) == -1) + tt_abort_msg("Couldn't make request"); + + event_base_dispatch(data->base); + tt_int_op(test_ok, ==, 1); + + end: + if (evcon) + evhttp_connection_free(evcon); + if (http) + evhttp_free(http); +} +static void http_simple_test(void *arg) +{ http_simple_test_impl(arg, 0, 0, "/test"); } +static void http_simple_nonconformant_test(void *arg) +{ http_simple_test_impl(arg, 0, 0, "/test nonconformant"); } + +static void +http_connection_retry_test_basic(void *arg, const char *addr, struct evdns_base *dns_base, int ssl) +{ + struct basic_test_data *data = arg; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req = NULL; + struct timeval tv, tv_start, tv_end; + struct bufferevent *bev; + struct http_server hs = { 0, ssl, NULL, }; + struct evhttp *http = http_setup(&hs.port, data->base, ssl ? HTTP_BIND_SSL : 0); + + exit_base = data->base; + test_ok = 0; + + /* auto detect a port */ + evhttp_free(http); + + bev = create_bev(data->base, -1, ssl, 0); + evcon = evhttp_connection_base_bufferevent_new(data->base, dns_base, bev, addr, hs.port); + tt_assert(evcon); + if (dns_base) + tt_assert(!evhttp_connection_set_flags(evcon, EVHTTP_CON_REUSE_CONNECTED_ADDR)); + + evhttp_connection_set_timeout(evcon, 1); + /* also bind to local host */ + evhttp_connection_set_local_address(evcon, "127.0.0.1"); + + /* + * At this point, we want to schedule an HTTP GET request + * server using our make request method. + */ + + req = evhttp_request_new(http_connection_retry_done, data->base); + tt_assert(req); + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost"); + + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, + "/?arg=val") == -1) { + tt_abort_msg("Couldn't make request"); + } + + evutil_gettimeofday(&tv_start, NULL); + event_base_dispatch(data->base); + evutil_gettimeofday(&tv_end, NULL); + evutil_timersub(&tv_end, &tv_start, &tv_end); + tt_int_op(tv_end.tv_sec, <, 1); + + tt_int_op(test_ok, ==, 1); + + /* + * now test the same but with retries + */ + test_ok = 0; + /** Shutdown dns server, to test conn_address reusing */ + if (dns_base) + regress_clean_dnsserver(); + + { + const struct timeval tv_timeout = { 0, 500000 }; + const struct timeval tv_retry = { 0, 500000 }; + evhttp_connection_set_timeout_tv(evcon, &tv_timeout); + evhttp_connection_set_initial_retry_tv(evcon, &tv_retry); + } + evhttp_connection_set_retries(evcon, 1); + + req = evhttp_request_new(http_connection_retry_done, data->base); + tt_assert(req); + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost"); + + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, + "/?arg=val") == -1) { + tt_abort_msg("Couldn't make request"); + } + + evutil_gettimeofday(&tv_start, NULL); + event_base_dispatch(data->base); + evutil_gettimeofday(&tv_end, NULL); + + /* fails fast, .5 sec to wait to retry, fails fast again. */ + test_timeval_diff_leq(&tv_start, &tv_end, 500, 200); + + tt_assert(test_ok == 1); + + /* + * now test the same but with retries and give it a web server + * at the end + */ + test_ok = 0; + + evhttp_connection_set_timeout(evcon, 1); + evhttp_connection_set_retries(evcon, 3); + + req = evhttp_request_new(http_dispatcher_test_done, data->base); + tt_assert(req); + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost"); + + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, + "/?arg=val") == -1) { + tt_abort_msg("Couldn't make request"); + } + + /* start up a web server .2 seconds after the connection tried + * to send a request + */ + evutil_timerclear(&tv); + tv.tv_usec = 200000; + http_make_web_server_base = data->base; + event_base_once(data->base, -1, EV_TIMEOUT, http_make_web_server, &hs, &tv); + + evutil_gettimeofday(&tv_start, NULL); + event_base_dispatch(data->base); + evutil_gettimeofday(&tv_end, NULL); + /* We'll wait twice as long as we did last time. */ + test_timeval_diff_leq(&tv_start, &tv_end, 1000, 400); + + tt_int_op(test_ok, ==, 1); + + end: + if (evcon) + evhttp_connection_free(evcon); + if (http) + evhttp_free(hs.http); +} + +static void +http_connection_retry_conn_address_test_impl(void *arg, int ssl) +{ + struct basic_test_data *data = arg; + ev_uint16_t portnum = 0; + struct evdns_base *dns_base = NULL; + char address[64]; + + tt_assert(regress_dnsserver(data->base, &portnum, search_table)); + dns_base = evdns_base_new(data->base, 0/* init name servers */); + tt_assert(dns_base); + + /* Add ourself as the only nameserver, and make sure we really are + * the only nameserver. */ + evutil_snprintf(address, sizeof(address), "127.0.0.1:%d", portnum); + evdns_base_nameserver_ip_add(dns_base, address); + + http_connection_retry_test_basic(arg, "localhost", dns_base, ssl); + + end: + if (dns_base) + evdns_base_free(dns_base, 0); + /** dnsserver will be cleaned in http_connection_retry_test_basic() */ +} +static void http_connection_retry_conn_address_test(void *arg) +{ http_connection_retry_conn_address_test_impl(arg, 0); } + +static void +http_connection_retry_test_impl(void *arg, int ssl) +{ + http_connection_retry_test_basic(arg, "127.0.0.1", NULL, ssl); +} +static void +http_connection_retry_test(void *arg) +{ http_connection_retry_test_impl(arg, 0); } + +static void +http_primitives(void *ptr) +{ + char *escaped = NULL; + struct evhttp *http = NULL; + + escaped = evhttp_htmlescape("