Sat, 24 Aug 2024 12:13:01 +0200
add request timeout handler
/* * 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); char *str4 = strdup("\r\n\r\nx123\r\n"); size_t len4 = strlen(str3); char *str5 = strdup("\r\n\r\n1 2 3\r\n"); size_t len5 = strlen(str3); char *str6 = strdup("\r\n\r\n1 23\r\n"); size_t len6 = 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 2a)"); ret = http_stream_parse_chunk_header(str2, len2, FALSE, &chunklen); UCX_TEST_ASSERT(ret == -1, "ret != -1 (test 2b)"); ret = http_stream_parse_chunk_header(str3, len3, TRUE, &chunklen); UCX_TEST_ASSERT(ret == -1, "ret != -1 (test 3a)"); ret = http_stream_parse_chunk_header(str3, len3, FALSE, &chunklen); UCX_TEST_ASSERT(ret == -1, "ret != -1 (test 3b)"); ret = http_stream_parse_chunk_header(str4, len4, TRUE, &chunklen); UCX_TEST_ASSERT(ret == -1, "ret != -1 (test 4a)"); ret = http_stream_parse_chunk_header(str4, len4, FALSE, &chunklen); UCX_TEST_ASSERT(ret == -1, "ret != -1 (test 4b)"); ret = http_stream_parse_chunk_header(str5, len5, TRUE, &chunklen); UCX_TEST_ASSERT(ret == -1, "ret != -1 (test 5a)"); ret = http_stream_parse_chunk_header(str5, len5, FALSE, &chunklen); UCX_TEST_ASSERT(ret == -1, "ret != -1 (test 5b)"); ret = http_stream_parse_chunk_header(str6, len6, TRUE, &chunklen); UCX_TEST_ASSERT(ret == -1, "ret != -1 (test 6a)"); ret = http_stream_parse_chunk_header(str6, len6, FALSE, &chunklen); UCX_TEST_ASSERT(ret == -1, "ret != -1 (test 6b)"); UCX_TEST_END; free(str); free(str2); free(str3); free(str4); free(str5); free(str6); } 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;i<testdata->capacity;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(pos<testdata->size) { 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 io_set_max_writes(1); // 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; io_set_max_writes(1); 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); io_set_max_writes(1); 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); io_set_max_writes(1); 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); io_set_max_writes(1); 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); io_set_max_writes(1); 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;i<testdata->capacity;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; }