src/server/test/io.c

Sat, 22 Nov 2025 14:27:01 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sat, 22 Nov 2025 14:27:01 +0100
changeset 633
392ec9026b07
parent 550
77241b3ba544
permissions
-rw-r--r--

port old ucx2 tests to ucx3

/*
 * 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"


CX_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);
    
    CX_TEST_DO {
    
        int64_t chunklen;
        int ret = http_stream_parse_chunk_header(str, len, TRUE, &chunklen);
        CX_TEST_ASSERT(ret == 5);
        CX_TEST_ASSERT(chunklen == 0x100);

        // test 2
        ret = http_stream_parse_chunk_header(str2, len2, TRUE, &chunklen);
        CX_TEST_ASSERT(ret == 7);
        CX_TEST_ASSERT(chunklen == 0x12345);

        // test 3: hex test
        ret = http_stream_parse_chunk_header(str3, len3, TRUE, &chunklen);
        CX_TEST_ASSERT(ret == 4);
        CX_TEST_ASSERT(chunklen == 0xFF);
            
    }
    free(str);
    free(str2);
}

CX_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);
    
    CX_TEST_DO {
    
        int64_t chunklen;
        int ret = http_stream_parse_chunk_header(str, len, FALSE, &chunklen);
        CX_TEST_ASSERT(ret == 7);
        CX_TEST_ASSERT(chunklen == 0x100);

        // test 2 with just \n as line break
        ret = http_stream_parse_chunk_header(str2, len2, FALSE, &chunklen);
        CX_TEST_ASSERT(ret == 4);
        CX_TEST_ASSERT(chunklen == 0xab);
            
    }
    free(str);
    free(str2);
}

CX_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);
    
    CX_TEST_DO {
    
        int64_t chunklen;
        int ret = http_stream_parse_chunk_header(str, len, FALSE, &chunklen);
        CX_TEST_ASSERT(ret == -1);
            
    }
    free(str);
}

CX_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);
    
    CX_TEST_DO {
    
        int64_t chunklen;
        int ret = http_stream_parse_chunk_header(str, len, FALSE, &chunklen);
        CX_TEST_ASSERT(ret == 5);
            
    }
    free(str);
}

CX_TEST(test_io_http_stream_parse_chunk_header_empty) {
    char *str = "";
    size_t len = strlen(str);
    
    CX_TEST_DO {
    
        int64_t chunklen;
        int ret = http_stream_parse_chunk_header(str, len, FALSE, &chunklen);
        CX_TEST_ASSERT(ret == 0);

        ret = http_stream_parse_chunk_header(str, len, TRUE, &chunklen);
        CX_TEST_ASSERT(ret == 0);
            
    }
}

CX_TEST(test_io_http_stream_parse_chunk_header_partial_first) {
    char *str = strdup("123");
    size_t len = strlen(str);
    
    CX_TEST_DO {
         
        int64_t chunklen;
        int ret = http_stream_parse_chunk_header(str, len, TRUE, &chunklen);
        CX_TEST_ASSERT(ret == 0);
    
    }
    free(str);
}

CX_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);
    
    CX_TEST_DO {
         
        int64_t chunklen;
        int ret = http_stream_parse_chunk_header(str, len, TRUE, &chunklen);
        CX_TEST_ASSERT(ret == 0);
        ret = http_stream_parse_chunk_header(str2, len2, FALSE, &chunklen);
        CX_TEST_ASSERT(ret == 0);
        ret = http_stream_parse_chunk_header(str3, len3, FALSE, &chunklen);
        CX_TEST_ASSERT(ret == 0);
        ret = http_stream_parse_chunk_header(str4, len4, FALSE, &chunklen);
        CX_TEST_ASSERT(ret == 0);
    
    }
    free(str);
    free(str2);
    free(str3);
    free(str4);
}

CX_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);
    
    CX_TEST_DO {
        int64_t chunklen;
        int ret;

        ret = http_stream_parse_chunk_header(str, len, TRUE, &chunklen);
        CX_TEST_ASSERT(ret == -1);
        ret = http_stream_parse_chunk_header(str, len, FALSE, &chunklen);
        CX_TEST_ASSERT(ret == -1);

        ret = http_stream_parse_chunk_header(str2, len2, TRUE, &chunklen);
        CX_TEST_ASSERT(ret == -1);
        ret = http_stream_parse_chunk_header(str2, len2, FALSE, &chunklen);
        CX_TEST_ASSERT(ret == -1);

        ret = http_stream_parse_chunk_header(str3, len3, TRUE, &chunklen);
        CX_TEST_ASSERT(ret == -1);
        ret = http_stream_parse_chunk_header(str3, len3, FALSE, &chunklen);
        CX_TEST_ASSERT(ret == -1);

        ret = http_stream_parse_chunk_header(str4, len4, TRUE, &chunklen);
        CX_TEST_ASSERT(ret == -1);
        ret = http_stream_parse_chunk_header(str4, len4, FALSE, &chunklen);
        CX_TEST_ASSERT(ret == -1);

        ret = http_stream_parse_chunk_header(str5, len5, TRUE, &chunklen);
        CX_TEST_ASSERT(ret == -1);
        ret = http_stream_parse_chunk_header(str5, len5, FALSE, &chunklen);
        CX_TEST_ASSERT(ret == -1);

        ret = http_stream_parse_chunk_header(str6, len6, TRUE, &chunklen);
        CX_TEST_ASSERT(ret == -1);
        ret = http_stream_parse_chunk_header(str6, len6, FALSE, &chunklen);
        CX_TEST_ASSERT(ret == -1);
    }
    
    free(str);
    free(str2);
    free(str3);
    free(str4);
    free(str5);
    free(str6);
}

CX_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);
    
    
    CX_TEST_DO {
         
        int64_t chunklen = -1;
        int ret = http_stream_parse_chunk_header(str, len, FALSE, &chunklen);
        CX_TEST_ASSERT(ret == 7);
        CX_TEST_ASSERT(chunklen == 0);

        chunklen = -1;
        ret = http_stream_parse_chunk_header(str2, len2, TRUE, &chunklen);
        CX_TEST_ASSERT(ret == 5);
        CX_TEST_ASSERT(chunklen == 0);

        // expect 0 (incomplete)
        ret = http_stream_parse_chunk_header(str3, len3, FALSE, &chunklen);
        CX_TEST_ASSERT(ret == 0);

        ret = http_stream_parse_chunk_header(str4, len4, FALSE, &chunklen);
        CX_TEST_ASSERT(ret == 0);
    
    }
    free(str);
    free(str2);
    free(str3);
    free(str4);
}


CX_TEST(test_io_httpstream_write) {
    Session *sn = testutil_session();
    
    TestIOStream *st = testutil_iostream(2048, TRUE);
    IOStream *http = httpstream_new(sn->pool, (IOStream*)st);
    
    CX_TEST_DO {
    
        char *msg = "hello world!";
        size_t msglen = strlen(msg);

        ssize_t w = net_write(http, msg, msglen);

        CX_TEST_ASSERT(w == msglen);
        CX_TEST_ASSERT(st->buf->size == msglen);
        CX_TEST_ASSERT(!memcmp(st->buf->space, msg, msglen));

        // 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);

        CX_TEST_ASSERT(w == msglen2);
        CX_TEST_ASSERT(st->buf->size == msglen+msglen2);
        CX_TEST_ASSERT(!memcmp(st->buf->space + msglen, msg2, msglen2));
    
    }
    
    testutil_destroy_session(sn);
}

CX_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);
    
    CX_TEST_DO {
    
        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);

        CX_TEST_ASSERT(w == msglen);
        CX_TEST_ASSERT(st->buf->size == bufmsglen);
        CX_TEST_ASSERT(!cx_strcasecmp(s1, s2));

        // write again
        w = net_write(http, msg, msglen);
        CX_TEST_ASSERT(w == msglen);
        CX_TEST_ASSERT(st->buf->size == 2*bufmsglen);

        cxstring s3 = cx_strn(st->buf->space+bufmsglen, bufmsglen);
        CX_TEST_ASSERT(!cx_strcasecmp(s2, s3));
    
    }
}

CX_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);
    
    CX_TEST_DO {
    
        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);

        CX_TEST_ASSERT(w == msglen);
        CX_TEST_ASSERT(st->buf->size == bufmsglen);
        CX_TEST_ASSERT(!cx_strcasecmp(s1, s2));
    
    }
}

CX_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);
    
    CX_TEST_DO {
    
        // 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);

            CX_TEST_ASSERT(w == len);
            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)
            CX_TEST_ASSERT(ret > 0);
            if(chunklen == 0) {
                CX_TEST_ASSERT(src_remaining == 0);
                break;
            }

            CX_TEST_ASSERT(chunklen <= src_remaining);

            char *src_chunk = testdata->space+srcpos;
            char *buf_chunk = buf+pos+ret;

            CX_TEST_ASSERT(!memcmp(buf_chunk, src_chunk, chunklen));

            pos += ret + chunklen;
            srcpos += chunklen;

            debug_counter++;
        }

        cxBufferFree(testdata);
        testutil_destroy_session(sn);
        testutil_iostream_destroy(st);
    
    }
}

CX_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);
    
    CX_TEST_DO {
    
    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);
    CX_TEST_ASSERT(w == 0);
    CX_TEST_ASSERT(st->buf->size == 1);
    CX_TEST_ASSERT(tolower(st->buf->space[0]) == 'c');
    
    // next header byte: '\r'
    w = net_write(http, msg, msglen);
    CX_TEST_ASSERT(w == 0);
    CX_TEST_ASSERT(st->buf->size == 2);
    CX_TEST_ASSERT(st->buf->space[1] == '\r');
    
    // next header byte: '\n'
    w = net_write(http, msg, msglen);
    CX_TEST_ASSERT(w == 0);
    CX_TEST_ASSERT(st->buf->size == 3);
    CX_TEST_ASSERT(st->buf->space[2] == '\n');
    
    // next: content
    w = net_write(http, msg, msglen);
    CX_TEST_ASSERT(w == 1);
    CX_TEST_ASSERT(st->buf->size == 4);
    CX_TEST_ASSERT(st->buf->space[3] == msg[0]);
    
    testutil_destroy_session(sn);
    testutil_iostream_destroy(st);
    
    }
}

CX_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);
    
    CX_TEST_DO {
    
        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);

        CX_TEST_ASSERT(w == 0);
        CX_TEST_ASSERT(st->buf->size == 3);
        CX_TEST_ASSERT(st->buf->space[0] == 'c');
        CX_TEST_ASSERT(st->buf->space[2] == '\n');

        w = net_write(http, msg, msglen);
        CX_TEST_ASSERT(w == 3);
        CX_TEST_ASSERT(st->buf->size == 6);
        CX_TEST_ASSERT(!memcmp(st->buf->space, "c\r\nhel\0", 7));

        msg += w;
        msglen -= w;

        w = net_write(http, msg, msglen);
        CX_TEST_ASSERT(w == 3);
        CX_TEST_ASSERT(st->buf->size == 9);
        CX_TEST_ASSERT(!memcmp(st->buf->space, "c\r\nhello \0", 10));

        st->max_write = 1024;
        msg += w;
        msglen -= w;

        w = net_write(http, msg, msglen);
        CX_TEST_ASSERT(w == msglen);
        CX_TEST_ASSERT(st->buf->size == 3 + msglen_orig + 2);
        CX_TEST_ASSERT(!memcmp(st->buf->space, "c\r\nhello world!\r\n", st->buf->size));

        testutil_destroy_session(sn);
        testutil_iostream_destroy(st);
    
    }
}

CX_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);
    
    CX_TEST_DO {
    
        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);

        CX_TEST_ASSERT(w == msglen);
        CX_TEST_ASSERT(st->buf->size == 3 + msglen);
        CX_TEST_ASSERT(!memcmp(st->buf->space, "c\r\nhello world!\0", st->buf->size + 1));

        st->max_write = 2 + 3 + msglen2; // trailer + new header + new msg, without new trailer

        w = net_write(http, msg2, msglen2);
        CX_TEST_ASSERT(w == msglen2);
        CX_TEST_ASSERT(st->buf->size == 3 + msglen + 2 + 3 + msglen2);
        CX_TEST_ASSERT(!memcmp(st->buf->space, "c\r\nhello world!\r\n6\r\nnewmsg\0", st->buf->size + 1));

        // 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);
        CX_TEST_ASSERT(w == 0);

        w = net_write(http, "dummymsg", 8);
        CX_TEST_ASSERT(w == 0);
        CX_TEST_ASSERT(st->buf->size == 3 + msglen + 2 + 3 + msglen2 + 2);
        CX_TEST_ASSERT(!memcmp(st->buf->space, "c\r\nhello world!\r\n6\r\nnewmsg\r\n\0", st->buf->size + 1));

        st->max_write = 1024;
        w = net_write(http, msg3, msglen3);

        CX_TEST_ASSERT(w == msglen3);
        CX_TEST_ASSERT(st->buf->size == 3 + msglen + 2 + 3 + msglen2 + 2 + 3 + msglen3 + 2);
        CX_TEST_ASSERT(!memcmp(st->buf->space, "c\r\nhello world!\r\n6\r\nnewmsg\r\n4\r\nmsg3\r\n", st->buf->size + 1));


        testutil_destroy_session(sn);
        testutil_iostream_destroy(st);
    
    }
}

CX_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);
    
    CX_TEST_DO {
    
        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);

        CX_TEST_ASSERT(w == msglen);
        CX_TEST_ASSERT(st->buf->size == 3 + msglen + 1);
        CX_TEST_ASSERT(!memcmp(st->buf->space, "c\r\nhello world!\r\0", st->buf->size + 1));

        st->max_write = 2; // write 1 trailer byte and 1 header byte

        w = net_write(http, msg2, msglen2);

        CX_TEST_ASSERT(w == 0);
        CX_TEST_ASSERT(st->buf->size == 3 + msglen + 2 + 1);
        CX_TEST_ASSERT(!memcmp(st->buf->space, "c\r\nhello world!\r\n6\0", st->buf->size + 1));

        // force partial header write again
        st->max_write = 1;

        w = net_write(http, msg2, msglen2);

        CX_TEST_ASSERT(w == 0);
        CX_TEST_ASSERT(st->buf->size == 3 + msglen + 2 + 2);
        CX_TEST_ASSERT(!memcmp(st->buf->space, "c\r\nhello world!\r\n6\r\0", st->buf->size + 1));

        st->max_write = 1024;

        w = net_write(http, msg2, msglen2);

        CX_TEST_ASSERT(w ==msglen2);
        CX_TEST_ASSERT(st->buf->size == 3 + msglen + 2 + 3 + msglen2 + 2);
        CX_TEST_ASSERT(!memcmp(st->buf->space, "c\r\nhello world!\r\n6\r\nnewmsg\r\n", st->buf->size + 1));


        testutil_destroy_session(sn);
        testutil_iostream_destroy(st);
    
    }
}

CX_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);
    
    CX_TEST_DO {
    
        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

        CX_TEST_ASSERT(w == 0);
        CX_TEST_ASSERT(st->buf->size == 1);

        st->max_write = 1024;

        w = net_write(http, msg_big, msglen_big); // first chunk + new chunk

        CX_TEST_ASSERT(w == msglen_big);
        CX_TEST_ASSERT(st->buf->size == 3 + msglen + 2 + 3 + msglen2 + 2);
        CX_TEST_ASSERT(!memcmp(st->buf->space, "c\r\nhello world!\r\n6\r\nnewmsg\r\n", st->buf->size + 1));


        testutil_destroy_session(sn);
        testutil_iostream_destroy(st);
    
    }
}

CX_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);
    
    CX_TEST_DO {
    
        // 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);
                CX_TEST_ASSERT(w >= 0);
                chunkpos += w;

                writes++;
                CX_TEST_ASSERT(writes < max_writes);
            }

            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)
            CX_TEST_ASSERT(ret > 0);
            if(chunklen == 0) {
                CX_TEST_ASSERT(src_remaining == 0);
                break;
            }

            CX_TEST_ASSERT(chunklen <= src_remaining);

            char *src_chunk = testdata->space+srcpos;
            char *buf_chunk = buf+pos+ret;

            CX_TEST_ASSERT(!memcmp(buf_chunk, src_chunk, chunklen));

            pos += ret + chunklen;
            srcpos += chunklen;

            debug_counter++;
        }


        testutil_destroy_session(sn);
        testutil_iostream_destroy(st);
        cxBufferFree(testdata);
    
    }
}

mercurial