diff -r 8827517054ec -r 0d80f8a2b29f src/server/test/io.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/test/io.c Sun Jun 04 20:09:18 2023 +0200 @@ -0,0 +1,805 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2023 Olaf Wintermann. All rights reserved. + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT HOLDER OR CONTRIBUTORS 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 "io.h" + +#include "testutils.h" + + +UCX_TEST(test_io_http_stream_parse_chunk_header_hdronly_first) { + char *str = strdup("100\r\n"); + size_t len = strlen(str); + char *str2 = strdup("12345\r\n"); + size_t len2 = strlen(str2); + char *str3 = strdup("FF\r\n"); + size_t len3 = strlen(str3); + + UCX_TEST_BEGIN; + + int64_t chunklen; + int ret = http_stream_parse_chunk_header(str, len, TRUE, &chunklen); + UCX_TEST_ASSERT(ret == 5, "ret != 5"); + UCX_TEST_ASSERT(chunklen == 0x100, "wrong chunk length"); + + // test 2 + ret = http_stream_parse_chunk_header(str2, len2, TRUE, &chunklen); + UCX_TEST_ASSERT(ret == 7, "ret != 7 (test 2)"); + UCX_TEST_ASSERT(chunklen == 0x12345, "wrong chunk length (test 2)"); + + // test 3: hex test + ret = http_stream_parse_chunk_header(str3, len3, TRUE, &chunklen); + UCX_TEST_ASSERT(ret == 4, "ret != 7 (test 3)"); + UCX_TEST_ASSERT(chunklen == 0xFF, "wrong chunk length (test 3)"); + + UCX_TEST_END; + free(str); + free(str2); +} + +UCX_TEST(test_io_http_stream_parse_chunk_header_hdronly) { + char *str = strdup("\r\n100\r\n"); + size_t len = strlen(str); + char *str2 = strdup("\nab\n"); + size_t len2 = strlen(str2); + + UCX_TEST_BEGIN; + + int64_t chunklen; + int ret = http_stream_parse_chunk_header(str, len, FALSE, &chunklen); + UCX_TEST_ASSERT(ret == 7, "ret != 7"); + UCX_TEST_ASSERT(chunklen == 0x100, "wrong chunk length"); + + // test 2 with just \n as line break + ret = http_stream_parse_chunk_header(str2, len2, FALSE, &chunklen); + UCX_TEST_ASSERT(ret == 4, "ret != 5 (test 2)"); + UCX_TEST_ASSERT(chunklen == 0xab, "wrong chunk length (test 2)"); + + UCX_TEST_END; + free(str); + free(str2); +} + +UCX_TEST(test_io_http_stream_parse_chunk_header_hdronly_seq_fail) { + // test: after the first chunk header, \r\n is required before any new header + char *str = strdup("ff\r\n"); + size_t len = strlen(str); + + UCX_TEST_BEGIN; + + int64_t chunklen; + int ret = http_stream_parse_chunk_header(str, len, FALSE, &chunklen); + UCX_TEST_ASSERT(ret == -1, "ret != -1"); + + UCX_TEST_END; + free(str); +} + +UCX_TEST(test_io_http_stream_parse_chunk_header_hdr_data) { + char *str = strdup("\r\nb\r\nhello world\r\n"); + size_t len = strlen(str); + + UCX_TEST_BEGIN; + + int64_t chunklen; + int ret = http_stream_parse_chunk_header(str, len, FALSE, &chunklen); + UCX_TEST_ASSERT(ret == 5, "ret != 5"); + + UCX_TEST_END; + free(str); +} + +UCX_TEST(test_io_http_stream_parse_chunk_header_empty) { + char *str = ""; + size_t len = strlen(str); + + UCX_TEST_BEGIN; + + int64_t chunklen; + int ret = http_stream_parse_chunk_header(str, len, FALSE, &chunklen); + UCX_TEST_ASSERT(ret == 0, "ret != 0"); + + ret = http_stream_parse_chunk_header(str, len, TRUE, &chunklen); + UCX_TEST_ASSERT(ret == 0, "ret != 0 (test 2)"); + + UCX_TEST_END; +} + +UCX_TEST(test_io_http_stream_parse_chunk_header_partial_first) { + char *str = strdup("123"); + size_t len = strlen(str); + + UCX_TEST_BEGIN; + + int64_t chunklen; + int ret = http_stream_parse_chunk_header(str, len, TRUE, &chunklen); + UCX_TEST_ASSERT(ret == 0, "ret != 0"); + + UCX_TEST_END; + free(str); +} + +UCX_TEST(test_io_http_stream_parse_chunk_header_partial) { + char *str = strdup("123"); + size_t len = strlen(str); + char *str2 = strdup("\r\n"); + size_t len2 = strlen(str2); + char *str3 = strdup("\r"); + size_t len3 = strlen(str3); + char *str4 = strdup("\r\n123"); + size_t len4 = strlen(str4); + + UCX_TEST_BEGIN; + + int64_t chunklen; + int ret = http_stream_parse_chunk_header(str, len, TRUE, &chunklen); + UCX_TEST_ASSERT(ret == 0, "ret != 0"); + ret = http_stream_parse_chunk_header(str2, len2, FALSE, &chunklen); + UCX_TEST_ASSERT(ret == 0, "ret != 0"); + ret = http_stream_parse_chunk_header(str3, len3, FALSE, &chunklen); + UCX_TEST_ASSERT(ret == 0, "ret != 0"); + ret = http_stream_parse_chunk_header(str4, len4, FALSE, &chunklen); + UCX_TEST_ASSERT(ret == 0, "ret != 0"); + + UCX_TEST_END; + free(str); + free(str2); + free(str3); + free(str4); +} + +UCX_TEST(test_io_http_stream_parse_chunk_header_invalid) { + char *str = strdup("hello\r\n"); + size_t len = strlen(str); + char *str2 = strdup("x4\r\n\r\n123\r\n"); + size_t len2 = strlen(str2); + char *str3 = strdup("\r\n\r\n123\r\n"); + size_t len3 = strlen(str3); + + UCX_TEST_BEGIN; + + int64_t chunklen; + int ret; + + ret = http_stream_parse_chunk_header(str, len, TRUE, &chunklen); + UCX_TEST_ASSERT(ret == -1, "ret != -1 (test 1a)"); + ret = http_stream_parse_chunk_header(str, len, FALSE, &chunklen); + UCX_TEST_ASSERT(ret == -1, "ret != -1 (test 1b)"); + + ret = http_stream_parse_chunk_header(str2, len2, TRUE, &chunklen); + UCX_TEST_ASSERT(ret == -1, "ret != -1 (test 1a)"); + ret = http_stream_parse_chunk_header(str2, len2, FALSE, &chunklen); + UCX_TEST_ASSERT(ret == -1, "ret != -1 (test 1b)"); + + ret = http_stream_parse_chunk_header(str3, len3, TRUE, &chunklen); + UCX_TEST_ASSERT(ret == -1, "ret != -1 (test 1a)"); + ret = http_stream_parse_chunk_header(str3, len3, FALSE, &chunklen); + UCX_TEST_ASSERT(ret == -1, "ret != -1 (test 1b)"); + + UCX_TEST_END; + free(str); + free(str2); + free(str3); +} + +UCX_TEST(test_io_http_stream_parse_chunk_header_zero) { + char *str = strdup("\r\n0\r\n\r\n"); + size_t len = strlen(str); + char *str2 = strdup("0\r\n\r\n"); + size_t len2 = strlen(str2); + + // incomplete + char *str3 = strdup("\r\n0\r\n"); + size_t len3 = strlen(str3); + char *str4 = strdup("\r\n0"); + size_t len4 = strlen(str4); + + + UCX_TEST_BEGIN; + + int64_t chunklen = -1; + int ret = http_stream_parse_chunk_header(str, len, FALSE, &chunklen); + UCX_TEST_ASSERT(ret == 7, "ret != 7"); + UCX_TEST_ASSERT(chunklen == 0, "chunklen != 0"); + + chunklen = -1; + ret = http_stream_parse_chunk_header(str2, len2, TRUE, &chunklen); + UCX_TEST_ASSERT(ret == 5, "ret != 5 (test 2)"); + UCX_TEST_ASSERT(chunklen == 0, "chunklen != 0 (test 2)"); + + // expect 0 (incomplete) + ret = http_stream_parse_chunk_header(str3, len3, FALSE, &chunklen); + UCX_TEST_ASSERT(ret == 0, "ret != 3 (test 3)"); + + ret = http_stream_parse_chunk_header(str4, len4, FALSE, &chunklen); + UCX_TEST_ASSERT(ret == 0, "ret != 3 (test 4)"); + + UCX_TEST_END; + free(str); + free(str2); + free(str3); + free(str4); +} + + +UCX_TEST(test_io_httpstream_write) { + Session *sn = testutil_session(); + + TestIOStream *st = testutil_iostream(2048, TRUE); + IOStream *http = httpstream_new(sn->pool, (IOStream*)st); + + UCX_TEST_BEGIN; + + char *msg = "hello world!"; + size_t msglen = strlen(msg); + + ssize_t w = net_write(http, msg, msglen); + + UCX_TEST_ASSERT(w == msglen, "wrong size returned by net_write"); + UCX_TEST_ASSERT(st->buf->size == msglen, "wrong buffer size"); + UCX_TEST_ASSERT(!memcmp(st->buf->space, msg, msglen), "wrong buffer content"); + + // test again, make sure the second message is written directly after the wirst one + char *msg2 = "test"; + size_t msglen2 = strlen(msg2); + + w = net_write(http, msg2, msglen2); + + UCX_TEST_ASSERT(w == msglen2, "wrong size returned by net_write (2)"); + UCX_TEST_ASSERT(st->buf->size == msglen+msglen2, "wrong buffer size (2)"); + UCX_TEST_ASSERT(!memcmp(st->buf->space + msglen, msg2, msglen2), "wrong buffer content (2)"); + + UCX_TEST_END; + + testutil_destroy_session(sn); +} + +UCX_TEST(test_io_httpstream_chunked_write) { + Session *sn = testutil_session(); + + TestIOStream *st = testutil_iostream(2048, TRUE); + IOStream *http = httpstream_new(sn->pool, (IOStream*)st); + httpstream_enable_chunked_write(http); + + UCX_TEST_BEGIN; + + char *msg = "hello world!"; + size_t msglen = strlen(msg); + + char *bufmsg = "c\r\nhello world!\r\n"; + size_t bufmsglen = strlen(bufmsg); + + ssize_t w = net_write(http, msg, msglen); + + cxstring s1 = cx_strn(st->buf->space, st->buf->size); + cxstring s2 = cx_strn(bufmsg, bufmsglen); + + UCX_TEST_ASSERT(w == msglen, "wrong size returned by net_write"); + UCX_TEST_ASSERT(st->buf->size == bufmsglen, "wrong buffer size"); + UCX_TEST_ASSERT(!cx_strcasecmp(s1, s2), "wrong buffer content"); + + // write again + w = net_write(http, msg, msglen); + UCX_TEST_ASSERT(w == msglen, "write 2: wrong return value"); + UCX_TEST_ASSERT(st->buf->size == 2*bufmsglen, "write 2: wrong buf size"); + + cxstring s3 = cx_strn(st->buf->space+bufmsglen, bufmsglen); + UCX_TEST_ASSERT(!cx_strcasecmp(s2, s3), "write 2: wrong buf content"); + + + UCX_TEST_END; +} + +UCX_TEST(test_io_httpstream_chunked_write_end) { + Session *sn = testutil_session(); + + TestIOStream *st = testutil_iostream(2048, TRUE); + IOStream *http = httpstream_new(sn->pool, (IOStream*)st); + httpstream_enable_chunked_write(http); + + UCX_TEST_BEGIN; + + char *msg = "hello world!"; + size_t msglen = strlen(msg); + + char *bufmsg = "c\r\nhello world!\r\n0\r\n\r\n"; + size_t bufmsglen = strlen(bufmsg); + + ssize_t w = net_write(http, msg, msglen); + net_finish(http); + + cxstring s1 = cx_strn(st->buf->space, st->buf->size); + cxstring s2 = cx_strn(bufmsg, bufmsglen); + + UCX_TEST_ASSERT(w == msglen, "wrong size returned by net_write"); + UCX_TEST_ASSERT(st->buf->size == bufmsglen, "wrong buffer size"); + UCX_TEST_ASSERT(!cx_strcasecmp(s1, s2), "wrong buffer content"); + + + UCX_TEST_END; +} + +UCX_TEST(test_io_httpstream_chunked_write_xx) { + // This test creates a giant buffer and writes it with + // chunked transfer encoding to the http stream with varying chunk length + + Session *sn = testutil_session(); + + TestIOStream *st = testutil_iostream(2048, TRUE); + IOStream *http = httpstream_new(sn->pool, (IOStream*)st); + httpstream_enable_chunked_write(http); + + UCX_TEST_BEGIN; + + // create test data + CxBuffer *testdata = cxBufferCreate(NULL, 1024*1024*4, cxDefaultAllocator, 0); + for(size_t i=0;icapacity;i++) { + cxBufferPut(testdata, 35+(i%91)); + } + + // write chunks, start with single diget chunk length and increase + // chunk size with each step + size_t pos = 0; + int j = 0; + ssize_t i=15; + while(possize) { + char *buf = testdata->space + pos; + size_t remaining = testdata->size - pos; + ssize_t len = pos + i < remaining ? i : remaining; + pos += len; + + ssize_t w = net_write(http, buf, len); + + UCX_TEST_ASSERT(w == len, "wrong size returned by net_write"); + i+=100; // increase chunk size + j++; // debug + } + + // terminate chunk + net_finish(http); + + // code below also used in test_io_httpstream_chunked_write_xx_limit + + // make sure the output is correctly encoded + // extract chunks from st->buf by using http_stream_parse_chunk_header + // (which should be well-tested) + WSBool first_chunk = TRUE; + int64_t chunklen = 0; + + char *buf = st->buf->space; + size_t bufsize = st->buf->size; + + pos = 0; // st->buf position + size_t srcpos = 0; // testdata position + int debug_counter = 0; + while(pos < bufsize) { + ssize_t remaining = bufsize - pos; + ssize_t src_remaining = testdata->size - srcpos; + + int ret = http_stream_parse_chunk_header(buf+pos, remaining, first_chunk, &chunklen); + first_chunk = FALSE; + + // ret must always be > 0 (0: incomplete chunk header, -1: invalid syntax) + UCX_TEST_ASSERT(ret > 0, "http_stream_parse_chunk_header ret <= 0"); + if(chunklen == 0) { + UCX_TEST_ASSERT(src_remaining == 0, "stream end reached but src_remaining > 0"); + break; + } + + UCX_TEST_ASSERT(chunklen <= src_remaining, "chunklen > src_remaining"); + + char *src_chunk = testdata->space+srcpos; + char *buf_chunk = buf+pos+ret; + + UCX_TEST_ASSERT(!memcmp(buf_chunk, src_chunk, chunklen), "memcmp failed"); + + pos += ret + chunklen; + srcpos += chunklen; + + debug_counter++; + } + + cxBufferFree(testdata); + testutil_destroy_session(sn); + testutil_iostream_destroy(st); + + UCX_TEST_END; +} + +UCX_TEST(test_io_httpstream_chunked_write_partial_header) { + Session *sn = testutil_session(); + + TestIOStream *st = testutil_iostream(2048, TRUE); + IOStream *http = httpstream_new(sn->pool, (IOStream*)st); + httpstream_enable_chunked_write(http); + + UCX_TEST_BEGIN; + + memset(st->buf->space, 0, st->buf->capacity); + + char *msg = "hello world!"; + size_t msglen = strlen(msg); + + st->max_write = 1; // limit the test stream max write size + + // only 1 byte of the header is written, 0 bytes of msg + ssize_t w = net_write(http, msg, msglen); + UCX_TEST_ASSERT(w == 0, "write 1: wrong return value"); + UCX_TEST_ASSERT(st->buf->size == 1, "write 1: wrong buf size"); + UCX_TEST_ASSERT(tolower(st->buf->space[0]) == 'c', "write 1: wrong buf content"); + + // next header byte: '\r' + w = net_write(http, msg, msglen); + UCX_TEST_ASSERT(w == 0, "write 2: wrong return value"); + UCX_TEST_ASSERT(st->buf->size == 2, "write 2: wrong buf size"); + UCX_TEST_ASSERT(st->buf->space[1] == '\r', "write 2: wrong content"); + + // next header byte: '\n' + w = net_write(http, msg, msglen); + UCX_TEST_ASSERT(w == 0, "write 3: wrong return value"); + UCX_TEST_ASSERT(st->buf->size == 3, "write 3: wrong buf size"); + UCX_TEST_ASSERT(st->buf->space[2] == '\n', "write 3: wrong content"); + + // next: content + w = net_write(http, msg, msglen); + UCX_TEST_ASSERT(w == 1, "write 4: wrong return value"); + UCX_TEST_ASSERT(st->buf->size == 4, "write 3: wrong buf size"); + UCX_TEST_ASSERT(st->buf->space[3] == msg[0], "write 3: wrong content"); + + testutil_destroy_session(sn); + testutil_iostream_destroy(st); + + UCX_TEST_END; +} + +UCX_TEST(test_io_httpstream_chunked_write_partial_data) { + Session *sn = testutil_session(); + + TestIOStream *st = testutil_iostream(2048, TRUE); + IOStream *http = httpstream_new(sn->pool, (IOStream*)st); + httpstream_enable_chunked_write(http); + + UCX_TEST_BEGIN; + + memset(st->buf->space, 0, st->buf->capacity); + + char *msg = "hello world!"; + size_t msglen = strlen(msg); + size_t msglen_orig = msglen; + + // limit first write to 3 to only write the header + st->max_write = 3; + + ssize_t w = net_write(http, msg, msglen); + + UCX_TEST_ASSERT(w == 0, "write 1: wrong return value"); + UCX_TEST_ASSERT(st->buf->size == 3, "write 1: wrong buf size"); + UCX_TEST_ASSERT(st->buf->space[0] == 'c', "write 1: wrong buf content"); + UCX_TEST_ASSERT(st->buf->space[2] == '\n', "write 1: wrong buf content"); + + w = net_write(http, msg, msglen); + UCX_TEST_ASSERT(w == 3, "write 2: wrong return value"); + UCX_TEST_ASSERT(st->buf->size == 6, "write 2: wrong buf size"); + UCX_TEST_ASSERT(!memcmp(st->buf->space, "c\r\nhel\0", 7), "write 2: wrong buf content"); + + msg += w; + msglen -= w; + + w = net_write(http, msg, msglen); + UCX_TEST_ASSERT(w == 3, "write 3: wrong return value"); + UCX_TEST_ASSERT(st->buf->size == 9, "write 3: wrong buf size"); + UCX_TEST_ASSERT(!memcmp(st->buf->space, "c\r\nhello \0", 10), "write 3: wrong buf content"); + + st->max_write = 1024; + msg += w; + msglen -= w; + + w = net_write(http, msg, msglen); + UCX_TEST_ASSERT(w == msglen, "write 4: wrong return value"); + UCX_TEST_ASSERT(st->buf->size == 3 + msglen_orig + 2, "write 4: wrong buf size"); + UCX_TEST_ASSERT(!memcmp(st->buf->space, "c\r\nhello world!\r\n", st->buf->size), "write 4: wrong buf content"); + + testutil_destroy_session(sn); + testutil_iostream_destroy(st); + + UCX_TEST_END; +} + +UCX_TEST(test_io_httpstream_chunked_write_partial_trailer) { + Session *sn = testutil_session(); + + TestIOStream *st = testutil_iostream(2048, TRUE); + IOStream *http = httpstream_new(sn->pool, (IOStream*)st); + httpstream_enable_chunked_write(http); + + UCX_TEST_BEGIN; + + memset(st->buf->space, 0, st->buf->capacity); + + char *msg = "hello world!"; + size_t msglen = strlen(msg); + + char *msg2 = "newmsg"; + size_t msglen2 = strlen(msg2); + + char *msg3 = "msg3"; + size_t msglen3 = strlen(msg3); + + st->max_write = 3 + msglen; // header + msg, but without trailer + + ssize_t w = net_write(http, msg, msglen); + + UCX_TEST_ASSERT(w == msglen, "write 1: wrong return value"); + UCX_TEST_ASSERT(st->buf->size == 3 + msglen, "write 1: wrong buf size"); + UCX_TEST_ASSERT(!memcmp(st->buf->space, "c\r\nhello world!\0", st->buf->size + 1), "write 1: wrong buf content"); + + st->max_write = 2 + 3 + msglen2; // trailer + new header + new msg, without new trailer + + w = net_write(http, msg2, msglen2); + UCX_TEST_ASSERT(w == msglen2, "write 2: wrong return value"); + UCX_TEST_ASSERT(st->buf->size == 3 + msglen + 2 + 3 + msglen2, "write 2: wrong buf size"); + UCX_TEST_ASSERT(!memcmp(st->buf->space, "c\r\nhello world!\r\n6\r\nnewmsg\0", st->buf->size + 1), "write 2: wrong buf content"); + + // limit write to 1 byte: two writes required for trailer, net_write should return 0 + st->max_write = 1; + + w = net_write(http, "dummymsg", 8); + UCX_TEST_ASSERT(w == 0, "write 3: wrong return value"); + + w = net_write(http, "dummymsg", 8); + UCX_TEST_ASSERT(w == 0, "write 4: wrong return value"); + UCX_TEST_ASSERT(st->buf->size == 3 + msglen + 2 + 3 + msglen2 + 2, "write 4: wrong buf size"); + UCX_TEST_ASSERT(!memcmp(st->buf->space, "c\r\nhello world!\r\n6\r\nnewmsg\r\n\0", st->buf->size + 1), "write 4: wrong buf content"); + + st->max_write = 1024; + w = net_write(http, msg3, msglen3); + + UCX_TEST_ASSERT(w == msglen3, "write 5: wrong return value"); + UCX_TEST_ASSERT(st->buf->size == 3 + msglen + 2 + 3 + msglen2 + 2 + 3 + msglen3 + 2, "write 5: wrong buf size"); + UCX_TEST_ASSERT(!memcmp(st->buf->space, "c\r\nhello world!\r\n6\r\nnewmsg\r\n4\r\nmsg3\r\n", st->buf->size + 1), "write 5: wrong buf content"); + + + testutil_destroy_session(sn); + testutil_iostream_destroy(st); + + UCX_TEST_END; +} + +UCX_TEST(test_io_httpstream_chunked_write_partial_trailer_partial_header) { + Session *sn = testutil_session(); + + TestIOStream *st = testutil_iostream(2048, TRUE); + IOStream *http = httpstream_new(sn->pool, (IOStream*)st); + httpstream_enable_chunked_write(http); + + UCX_TEST_BEGIN; + + memset(st->buf->space, 0, st->buf->capacity); + + char *msg = "hello world!"; + size_t msglen = strlen(msg); + + char *msg2 = "newmsg"; + size_t msglen2 = strlen(msg2); + + // Test: write partial trailer followed by partial header write + + st->max_write = 3 + msglen + 1; + + ssize_t w = net_write(http, msg, msglen); + + UCX_TEST_ASSERT(w == msglen, "write 1: wrong return value"); + UCX_TEST_ASSERT(st->buf->size == 3 + msglen + 1, "write 1: wrong buf size"); + UCX_TEST_ASSERT(!memcmp(st->buf->space, "c\r\nhello world!\r\0", st->buf->size + 1), "write 1: wrong buf content"); + + st->max_write = 2; // write 1 trailer byte and 1 header byte + + w = net_write(http, msg2, msglen2); + + UCX_TEST_ASSERT(w == 0, "write 2: wrong return value"); + UCX_TEST_ASSERT(st->buf->size == 3 + msglen + 2 + 1, "write 2: wrong buf size"); + UCX_TEST_ASSERT(!memcmp(st->buf->space, "c\r\nhello world!\r\n6\0", st->buf->size + 1), "write 2: wrong buf content"); + + // force partial header write again + st->max_write = 1; + + w = net_write(http, msg2, msglen2); + + UCX_TEST_ASSERT(w == 0, "write 3: wrong return value"); + UCX_TEST_ASSERT(st->buf->size == 3 + msglen + 2 + 2, "write 3: wrong buf size"); + UCX_TEST_ASSERT(!memcmp(st->buf->space, "c\r\nhello world!\r\n6\r\0", st->buf->size + 1), "write 3: wrong buf content"); + + st->max_write = 1024; + + w = net_write(http, msg2, msglen2); + + UCX_TEST_ASSERT(w ==msglen2, "write 4: wrong return value"); + UCX_TEST_ASSERT(st->buf->size == 3 + msglen + 2 + 3 + msglen2 + 2, "write 4: wrong buf size"); + UCX_TEST_ASSERT(!memcmp(st->buf->space, "c\r\nhello world!\r\n6\r\nnewmsg\r\n", st->buf->size + 1), "write 4: wrong buf content"); + + + testutil_destroy_session(sn); + testutil_iostream_destroy(st); + + UCX_TEST_END; +} + +UCX_TEST(test_io_httpstream_chunked_write_data_2x) { + Session *sn = testutil_session(); + + TestIOStream *st = testutil_iostream(2048, TRUE); + IOStream *http = httpstream_new(sn->pool, (IOStream*)st); + httpstream_enable_chunked_write(http); + + UCX_TEST_BEGIN; + + memset(st->buf->space, 0, st->buf->capacity); + + // Test: First write a partial header, which forces a chunk with a specific + // size. After that, write a message, that is bigger than the first + // chunk, forcing a start of a second chunk, in one big writev op. + + char *msg = "hello world!"; + size_t msglen = strlen(msg); + + char *msg2 = "newmsg"; + size_t msglen2 = strlen(msg2); + + char *msg_big = "hello world!newmsg"; + size_t msglen_big = strlen(msg_big); + + st->max_write = 1; + + ssize_t w = net_write(http, msg, msglen); // first chunk: msg + + UCX_TEST_ASSERT(w == 0, "write 1: wrong return value"); + UCX_TEST_ASSERT(st->buf->size == 1, "write 1: wrong buf size"); + + st->max_write = 1024; + + w = net_write(http, msg_big, msglen_big); // first chunk + new chunk + + UCX_TEST_ASSERT(w == msglen_big, "write 2: wrong return value"); + UCX_TEST_ASSERT(st->buf->size == 3 + msglen + 2 + 3 + msglen2 + 2, "write 2: wrong buf size"); + UCX_TEST_ASSERT(!memcmp(st->buf->space, "c\r\nhello world!\r\n6\r\nnewmsg\r\n", st->buf->size + 1), "write 2: wrong buf content"); + + + testutil_destroy_session(sn); + testutil_iostream_destroy(st); + + UCX_TEST_END; +} + +UCX_TEST(test_io_httpstream_chunked_write_xx_limit) { + Session *sn = testutil_session(); + + TestIOStream *st = testutil_iostream(2048, TRUE); + IOStream *http = httpstream_new(sn->pool, (IOStream*)st); + httpstream_enable_chunked_write(http); + + UCX_TEST_BEGIN; + + // Test: create testdata and write it in varying chunk sizes, but + // limit TestIOStream to 1 to 3 byte writes + + // create test data + CxBuffer *testdata = cxBufferCreate(NULL, 1024*16, cxDefaultAllocator, 0); + for(size_t i=0;icapacity;i++) { + cxBufferPut(testdata, 35+(i%91)); + } + + st->max_write = 1; + + size_t pos = 0; + int chunksize = 1; + while(pos < testdata->size) { + size_t available = testdata->size - pos; + + char *chunk = testdata->space + pos; + size_t chunklen = chunksize > available ? available : chunksize; + + // write chunk + size_t chunkpos = 0; + int max_writes = chunklen + 24; // max number of write attempts + int writes = 0; + while(chunkpos < chunklen) { + ssize_t w = net_write(http, chunk+chunkpos, chunklen-chunkpos); + UCX_TEST_ASSERT(w >= 0, "net_write failed"); + chunkpos += w; + + writes++; + UCX_TEST_ASSERT(writes < max_writes, "max writes attempts reached"); + } + + pos += chunklen; + chunksize += 5; + + // increase max write size at some point + if(pos + chunksize >= testdata->size) { + st->max_write = INT_MAX; + } else if(pos > 1024*2) { + if(pos < 1024*8) { + st->max_write = 2; + } else { + st->max_write = 3; + } + } + } + + // terminate chunk + net_finish(http); + + + // same code as test_io_httpstream_chunked_write_xx + + // make sure the output is correctly encoded + // extract chunks from st->buf by using http_stream_parse_chunk_header + // (which should be well-tested) + + WSBool first_chunk = TRUE; + int64_t chunklen = 0; + + char *buf = st->buf->space; + size_t bufsize = st->buf->size; + + pos = 0; // st->buf position + size_t srcpos = 0; // testdata position + int debug_counter = 0; + while(pos < bufsize) { + ssize_t remaining = bufsize - pos; + ssize_t src_remaining = testdata->size - srcpos; + + int ret = http_stream_parse_chunk_header(buf+pos, remaining, first_chunk, &chunklen); + first_chunk = FALSE; + + // ret must always be > 0 (0: incomplete chunk header, -1: invalid syntax) + UCX_TEST_ASSERT(ret > 0, "http_stream_parse_chunk_header ret <= 0"); + if(chunklen == 0) { + UCX_TEST_ASSERT(src_remaining == 0, "stream end reached but src_remaining > 0"); + break; + } + + UCX_TEST_ASSERT(chunklen <= src_remaining, "chunklen > src_remaining"); + + char *src_chunk = testdata->space+srcpos; + char *buf_chunk = buf+pos+ret; + + UCX_TEST_ASSERT(!memcmp(buf_chunk, src_chunk, chunklen), "memcmp failed"); + + pos += ret + chunklen; + srcpos += chunklen; + + debug_counter++; + } + + + testutil_destroy_session(sn); + testutil_iostream_destroy(st); + cxBufferFree(testdata); + + UCX_TEST_END; +}