src/server/test/io.c

changeset 498
0d80f8a2b29f
child 513
9a49c245a49c
--- /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;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
+    
+    // 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;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;
+}

mercurial