UNIXworkcode

1 /* 2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 3 * 4 * Copyright 2023 Olaf Wintermann. All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 * POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include "io.h" 30 31 #include "testutils.h" 32 33 34 UCX_TEST(test_io_http_stream_parse_chunk_header_hdronly_first) { 35 char *str = strdup("100\r\n"); 36 size_t len = strlen(str); 37 char *str2 = strdup("12345\r\n"); 38 size_t len2 = strlen(str2); 39 char *str3 = strdup("FF\r\n"); 40 size_t len3 = strlen(str3); 41 42 UCX_TEST_BEGIN; 43 44 int64_t chunklen; 45 int ret = http_stream_parse_chunk_header(str, len, TRUE, &chunklen); 46 UCX_TEST_ASSERT(ret == 5, "ret != 5"); 47 UCX_TEST_ASSERT(chunklen == 0x100, "wrong chunk length"); 48 49 // test 2 50 ret = http_stream_parse_chunk_header(str2, len2, TRUE, &chunklen); 51 UCX_TEST_ASSERT(ret == 7, "ret != 7 (test 2)"); 52 UCX_TEST_ASSERT(chunklen == 0x12345, "wrong chunk length (test 2)"); 53 54 // test 3: hex test 55 ret = http_stream_parse_chunk_header(str3, len3, TRUE, &chunklen); 56 UCX_TEST_ASSERT(ret == 4, "ret != 7 (test 3)"); 57 UCX_TEST_ASSERT(chunklen == 0xFF, "wrong chunk length (test 3)"); 58 59 UCX_TEST_END; 60 free(str); 61 free(str2); 62 } 63 64 UCX_TEST(test_io_http_stream_parse_chunk_header_hdronly) { 65 char *str = strdup("\r\n100\r\n"); 66 size_t len = strlen(str); 67 char *str2 = strdup("\nab\n"); 68 size_t len2 = strlen(str2); 69 70 UCX_TEST_BEGIN; 71 72 int64_t chunklen; 73 int ret = http_stream_parse_chunk_header(str, len, FALSE, &chunklen); 74 UCX_TEST_ASSERT(ret == 7, "ret != 7"); 75 UCX_TEST_ASSERT(chunklen == 0x100, "wrong chunk length"); 76 77 // test 2 with just \n as line break 78 ret = http_stream_parse_chunk_header(str2, len2, FALSE, &chunklen); 79 UCX_TEST_ASSERT(ret == 4, "ret != 5 (test 2)"); 80 UCX_TEST_ASSERT(chunklen == 0xab, "wrong chunk length (test 2)"); 81 82 UCX_TEST_END; 83 free(str); 84 free(str2); 85 } 86 87 UCX_TEST(test_io_http_stream_parse_chunk_header_hdronly_seq_fail) { 88 // test: after the first chunk header, \r\n is required before any new header 89 char *str = strdup("ff\r\n"); 90 size_t len = strlen(str); 91 92 UCX_TEST_BEGIN; 93 94 int64_t chunklen; 95 int ret = http_stream_parse_chunk_header(str, len, FALSE, &chunklen); 96 UCX_TEST_ASSERT(ret == -1, "ret != -1"); 97 98 UCX_TEST_END; 99 free(str); 100 } 101 102 UCX_TEST(test_io_http_stream_parse_chunk_header_hdr_data) { 103 char *str = strdup("\r\nb\r\nhello world\r\n"); 104 size_t len = strlen(str); 105 106 UCX_TEST_BEGIN; 107 108 int64_t chunklen; 109 int ret = http_stream_parse_chunk_header(str, len, FALSE, &chunklen); 110 UCX_TEST_ASSERT(ret == 5, "ret != 5"); 111 112 UCX_TEST_END; 113 free(str); 114 } 115 116 UCX_TEST(test_io_http_stream_parse_chunk_header_empty) { 117 char *str = ""; 118 size_t len = strlen(str); 119 120 UCX_TEST_BEGIN; 121 122 int64_t chunklen; 123 int ret = http_stream_parse_chunk_header(str, len, FALSE, &chunklen); 124 UCX_TEST_ASSERT(ret == 0, "ret != 0"); 125 126 ret = http_stream_parse_chunk_header(str, len, TRUE, &chunklen); 127 UCX_TEST_ASSERT(ret == 0, "ret != 0 (test 2)"); 128 129 UCX_TEST_END; 130 } 131 132 UCX_TEST(test_io_http_stream_parse_chunk_header_partial_first) { 133 char *str = strdup("123"); 134 size_t len = strlen(str); 135 136 UCX_TEST_BEGIN; 137 138 int64_t chunklen; 139 int ret = http_stream_parse_chunk_header(str, len, TRUE, &chunklen); 140 UCX_TEST_ASSERT(ret == 0, "ret != 0"); 141 142 UCX_TEST_END; 143 free(str); 144 } 145 146 UCX_TEST(test_io_http_stream_parse_chunk_header_partial) { 147 char *str = strdup("123"); 148 size_t len = strlen(str); 149 char *str2 = strdup("\r\n"); 150 size_t len2 = strlen(str2); 151 char *str3 = strdup("\r"); 152 size_t len3 = strlen(str3); 153 char *str4 = strdup("\r\n123"); 154 size_t len4 = strlen(str4); 155 156 UCX_TEST_BEGIN; 157 158 int64_t chunklen; 159 int ret = http_stream_parse_chunk_header(str, len, TRUE, &chunklen); 160 UCX_TEST_ASSERT(ret == 0, "ret != 0"); 161 ret = http_stream_parse_chunk_header(str2, len2, FALSE, &chunklen); 162 UCX_TEST_ASSERT(ret == 0, "ret != 0"); 163 ret = http_stream_parse_chunk_header(str3, len3, FALSE, &chunklen); 164 UCX_TEST_ASSERT(ret == 0, "ret != 0"); 165 ret = http_stream_parse_chunk_header(str4, len4, FALSE, &chunklen); 166 UCX_TEST_ASSERT(ret == 0, "ret != 0"); 167 168 UCX_TEST_END; 169 free(str); 170 free(str2); 171 free(str3); 172 free(str4); 173 } 174 175 UCX_TEST(test_io_http_stream_parse_chunk_header_invalid) { 176 char *str = strdup("hello\r\n"); 177 size_t len = strlen(str); 178 char *str2 = strdup("x4\r\n\r\n123\r\n"); 179 size_t len2 = strlen(str2); 180 char *str3 = strdup("\r\n\r\n123\r\n"); 181 size_t len3 = strlen(str3); 182 char *str4 = strdup("\r\n\r\nx123\r\n"); 183 size_t len4 = strlen(str3); 184 char *str5 = strdup("\r\n\r\n1 2 3\r\n"); 185 size_t len5 = strlen(str3); 186 char *str6 = strdup("\r\n\r\n1 23\r\n"); 187 size_t len6 = strlen(str3); 188 189 UCX_TEST_BEGIN; 190 191 int64_t chunklen; 192 int ret; 193 194 ret = http_stream_parse_chunk_header(str, len, TRUE, &chunklen); 195 UCX_TEST_ASSERT(ret == -1, "ret != -1 (test 1a)"); 196 ret = http_stream_parse_chunk_header(str, len, FALSE, &chunklen); 197 UCX_TEST_ASSERT(ret == -1, "ret != -1 (test 1b)"); 198 199 ret = http_stream_parse_chunk_header(str2, len2, TRUE, &chunklen); 200 UCX_TEST_ASSERT(ret == -1, "ret != -1 (test 2a)"); 201 ret = http_stream_parse_chunk_header(str2, len2, FALSE, &chunklen); 202 UCX_TEST_ASSERT(ret == -1, "ret != -1 (test 2b)"); 203 204 ret = http_stream_parse_chunk_header(str3, len3, TRUE, &chunklen); 205 UCX_TEST_ASSERT(ret == -1, "ret != -1 (test 3a)"); 206 ret = http_stream_parse_chunk_header(str3, len3, FALSE, &chunklen); 207 UCX_TEST_ASSERT(ret == -1, "ret != -1 (test 3b)"); 208 209 ret = http_stream_parse_chunk_header(str4, len4, TRUE, &chunklen); 210 UCX_TEST_ASSERT(ret == -1, "ret != -1 (test 4a)"); 211 ret = http_stream_parse_chunk_header(str4, len4, FALSE, &chunklen); 212 UCX_TEST_ASSERT(ret == -1, "ret != -1 (test 4b)"); 213 214 ret = http_stream_parse_chunk_header(str5, len5, TRUE, &chunklen); 215 UCX_TEST_ASSERT(ret == -1, "ret != -1 (test 5a)"); 216 ret = http_stream_parse_chunk_header(str5, len5, FALSE, &chunklen); 217 UCX_TEST_ASSERT(ret == -1, "ret != -1 (test 5b)"); 218 219 ret = http_stream_parse_chunk_header(str6, len6, TRUE, &chunklen); 220 UCX_TEST_ASSERT(ret == -1, "ret != -1 (test 6a)"); 221 ret = http_stream_parse_chunk_header(str6, len6, FALSE, &chunklen); 222 UCX_TEST_ASSERT(ret == -1, "ret != -1 (test 6b)"); 223 224 UCX_TEST_END; 225 free(str); 226 free(str2); 227 free(str3); 228 free(str4); 229 free(str5); 230 free(str6); 231 } 232 233 UCX_TEST(test_io_http_stream_parse_chunk_header_zero) { 234 char *str = strdup("\r\n0\r\n\r\n"); 235 size_t len = strlen(str); 236 char *str2 = strdup("0\r\n\r\n"); 237 size_t len2 = strlen(str2); 238 239 // incomplete 240 char *str3 = strdup("\r\n0\r\n"); 241 size_t len3 = strlen(str3); 242 char *str4 = strdup("\r\n0"); 243 size_t len4 = strlen(str4); 244 245 246 UCX_TEST_BEGIN; 247 248 int64_t chunklen = -1; 249 int ret = http_stream_parse_chunk_header(str, len, FALSE, &chunklen); 250 UCX_TEST_ASSERT(ret == 7, "ret != 7"); 251 UCX_TEST_ASSERT(chunklen == 0, "chunklen != 0"); 252 253 chunklen = -1; 254 ret = http_stream_parse_chunk_header(str2, len2, TRUE, &chunklen); 255 UCX_TEST_ASSERT(ret == 5, "ret != 5 (test 2)"); 256 UCX_TEST_ASSERT(chunklen == 0, "chunklen != 0 (test 2)"); 257 258 // expect 0 (incomplete) 259 ret = http_stream_parse_chunk_header(str3, len3, FALSE, &chunklen); 260 UCX_TEST_ASSERT(ret == 0, "ret != 3 (test 3)"); 261 262 ret = http_stream_parse_chunk_header(str4, len4, FALSE, &chunklen); 263 UCX_TEST_ASSERT(ret == 0, "ret != 3 (test 4)"); 264 265 UCX_TEST_END; 266 free(str); 267 free(str2); 268 free(str3); 269 free(str4); 270 } 271 272 273 UCX_TEST(test_io_httpstream_write) { 274 Session *sn = testutil_session(); 275 276 TestIOStream *st = testutil_iostream(2048, TRUE); 277 IOStream *http = httpstream_new(sn->pool, (IOStream*)st); 278 279 UCX_TEST_BEGIN; 280 281 char *msg = "hello world!"; 282 size_t msglen = strlen(msg); 283 284 ssize_t w = net_write(http, msg, msglen); 285 286 UCX_TEST_ASSERT(w == msglen, "wrong size returned by net_write"); 287 UCX_TEST_ASSERT(st->buf->size == msglen, "wrong buffer size"); 288 UCX_TEST_ASSERT(!memcmp(st->buf->space, msg, msglen), "wrong buffer content"); 289 290 // test again, make sure the second message is written directly after the wirst one 291 char *msg2 = "test"; 292 size_t msglen2 = strlen(msg2); 293 294 w = net_write(http, msg2, msglen2); 295 296 UCX_TEST_ASSERT(w == msglen2, "wrong size returned by net_write (2)"); 297 UCX_TEST_ASSERT(st->buf->size == msglen+msglen2, "wrong buffer size (2)"); 298 UCX_TEST_ASSERT(!memcmp(st->buf->space + msglen, msg2, msglen2), "wrong buffer content (2)"); 299 300 UCX_TEST_END; 301 302 testutil_destroy_session(sn); 303 } 304 305 UCX_TEST(test_io_httpstream_chunked_write) { 306 Session *sn = testutil_session(); 307 308 TestIOStream *st = testutil_iostream(2048, TRUE); 309 IOStream *http = httpstream_new(sn->pool, (IOStream*)st); 310 httpstream_enable_chunked_write(http); 311 312 UCX_TEST_BEGIN; 313 314 char *msg = "hello world!"; 315 size_t msglen = strlen(msg); 316 317 char *bufmsg = "c\r\nhello world!\r\n"; 318 size_t bufmsglen = strlen(bufmsg); 319 320 ssize_t w = net_write(http, msg, msglen); 321 322 cxstring s1 = cx_strn(st->buf->space, st->buf->size); 323 cxstring s2 = cx_strn(bufmsg, bufmsglen); 324 325 UCX_TEST_ASSERT(w == msglen, "wrong size returned by net_write"); 326 UCX_TEST_ASSERT(st->buf->size == bufmsglen, "wrong buffer size"); 327 UCX_TEST_ASSERT(!cx_strcasecmp(s1, s2), "wrong buffer content"); 328 329 // write again 330 w = net_write(http, msg, msglen); 331 UCX_TEST_ASSERT(w == msglen, "write 2: wrong return value"); 332 UCX_TEST_ASSERT(st->buf->size == 2*bufmsglen, "write 2: wrong buf size"); 333 334 cxstring s3 = cx_strn(st->buf->space+bufmsglen, bufmsglen); 335 UCX_TEST_ASSERT(!cx_strcasecmp(s2, s3), "write 2: wrong buf content"); 336 337 338 UCX_TEST_END; 339 } 340 341 UCX_TEST(test_io_httpstream_chunked_write_end) { 342 Session *sn = testutil_session(); 343 344 TestIOStream *st = testutil_iostream(2048, TRUE); 345 IOStream *http = httpstream_new(sn->pool, (IOStream*)st); 346 httpstream_enable_chunked_write(http); 347 348 UCX_TEST_BEGIN; 349 350 char *msg = "hello world!"; 351 size_t msglen = strlen(msg); 352 353 char *bufmsg = "c\r\nhello world!\r\n0\r\n\r\n"; 354 size_t bufmsglen = strlen(bufmsg); 355 356 ssize_t w = net_write(http, msg, msglen); 357 net_finish(http); 358 359 cxstring s1 = cx_strn(st->buf->space, st->buf->size); 360 cxstring s2 = cx_strn(bufmsg, bufmsglen); 361 362 UCX_TEST_ASSERT(w == msglen, "wrong size returned by net_write"); 363 UCX_TEST_ASSERT(st->buf->size == bufmsglen, "wrong buffer size"); 364 UCX_TEST_ASSERT(!cx_strcasecmp(s1, s2), "wrong buffer content"); 365 366 367 UCX_TEST_END; 368 } 369 370 UCX_TEST(test_io_httpstream_chunked_write_xx) { 371 // This test creates a giant buffer and writes it with 372 // chunked transfer encoding to the http stream with varying chunk length 373 374 Session *sn = testutil_session(); 375 376 TestIOStream *st = testutil_iostream(2048, TRUE); 377 IOStream *http = httpstream_new(sn->pool, (IOStream*)st); 378 httpstream_enable_chunked_write(http); 379 380 UCX_TEST_BEGIN; 381 382 // create test data 383 CxBuffer *testdata = cxBufferCreate(NULL, 1024*1024*4, cxDefaultAllocator, 0); 384 for(size_t i=0;i<testdata->capacity;i++) { 385 cxBufferPut(testdata, 35+(i%91)); 386 } 387 388 // write chunks, start with single diget chunk length and increase 389 // chunk size with each step 390 size_t pos = 0; 391 int j = 0; 392 ssize_t i=15; 393 while(pos<testdata->size) { 394 char *buf = testdata->space + pos; 395 size_t remaining = testdata->size - pos; 396 ssize_t len = pos + i < remaining ? i : remaining; 397 pos += len; 398 399 ssize_t w = net_write(http, buf, len); 400 401 UCX_TEST_ASSERT(w == len, "wrong size returned by net_write"); 402 i+=100; // increase chunk size 403 j++; // debug 404 } 405 406 // terminate chunk 407 net_finish(http); 408 409 // code below also used in test_io_httpstream_chunked_write_xx_limit 410 411 // make sure the output is correctly encoded 412 // extract chunks from st->buf by using http_stream_parse_chunk_header 413 // (which should be well-tested) 414 WSBool first_chunk = TRUE; 415 int64_t chunklen = 0; 416 417 char *buf = st->buf->space; 418 size_t bufsize = st->buf->size; 419 420 pos = 0; // st->buf position 421 size_t srcpos = 0; // testdata position 422 int debug_counter = 0; 423 while(pos < bufsize) { 424 ssize_t remaining = bufsize - pos; 425 ssize_t src_remaining = testdata->size - srcpos; 426 427 int ret = http_stream_parse_chunk_header(buf+pos, remaining, first_chunk, &chunklen); 428 first_chunk = FALSE; 429 430 // ret must always be > 0 (0: incomplete chunk header, -1: invalid syntax) 431 UCX_TEST_ASSERT(ret > 0, "http_stream_parse_chunk_header ret <= 0"); 432 if(chunklen == 0) { 433 UCX_TEST_ASSERT(src_remaining == 0, "stream end reached but src_remaining > 0"); 434 break; 435 } 436 437 UCX_TEST_ASSERT(chunklen <= src_remaining, "chunklen > src_remaining"); 438 439 char *src_chunk = testdata->space+srcpos; 440 char *buf_chunk = buf+pos+ret; 441 442 UCX_TEST_ASSERT(!memcmp(buf_chunk, src_chunk, chunklen), "memcmp failed"); 443 444 pos += ret + chunklen; 445 srcpos += chunklen; 446 447 debug_counter++; 448 } 449 450 cxBufferFree(testdata); 451 testutil_destroy_session(sn); 452 testutil_iostream_destroy(st); 453 454 UCX_TEST_END; 455 } 456 457 UCX_TEST(test_io_httpstream_chunked_write_partial_header) { 458 Session *sn = testutil_session(); 459 460 TestIOStream *st = testutil_iostream(2048, TRUE); 461 IOStream *http = httpstream_new(sn->pool, (IOStream*)st); 462 httpstream_enable_chunked_write(http); 463 464 UCX_TEST_BEGIN; 465 466 memset(st->buf->space, 0, st->buf->capacity); 467 468 char *msg = "hello world!"; 469 size_t msglen = strlen(msg); 470 471 st->max_write = 1; // limit the test stream max write size 472 io_set_max_writes(1); 473 474 // only 1 byte of the header is written, 0 bytes of msg 475 ssize_t w = net_write(http, msg, msglen); 476 UCX_TEST_ASSERT(w == 0, "write 1: wrong return value"); 477 UCX_TEST_ASSERT(st->buf->size == 1, "write 1: wrong buf size"); 478 UCX_TEST_ASSERT(tolower(st->buf->space[0]) == 'c', "write 1: wrong buf content"); 479 480 // next header byte: '\r' 481 w = net_write(http, msg, msglen); 482 UCX_TEST_ASSERT(w == 0, "write 2: wrong return value"); 483 UCX_TEST_ASSERT(st->buf->size == 2, "write 2: wrong buf size"); 484 UCX_TEST_ASSERT(st->buf->space[1] == '\r', "write 2: wrong content"); 485 486 // next header byte: '\n' 487 w = net_write(http, msg, msglen); 488 UCX_TEST_ASSERT(w == 0, "write 3: wrong return value"); 489 UCX_TEST_ASSERT(st->buf->size == 3, "write 3: wrong buf size"); 490 UCX_TEST_ASSERT(st->buf->space[2] == '\n', "write 3: wrong content"); 491 492 // next: content 493 w = net_write(http, msg, msglen); 494 UCX_TEST_ASSERT(w == 1, "write 4: wrong return value"); 495 UCX_TEST_ASSERT(st->buf->size == 4, "write 3: wrong buf size"); 496 UCX_TEST_ASSERT(st->buf->space[3] == msg[0], "write 3: wrong content"); 497 498 testutil_destroy_session(sn); 499 testutil_iostream_destroy(st); 500 501 UCX_TEST_END; 502 } 503 504 UCX_TEST(test_io_httpstream_chunked_write_partial_data) { 505 Session *sn = testutil_session(); 506 507 TestIOStream *st = testutil_iostream(2048, TRUE); 508 IOStream *http = httpstream_new(sn->pool, (IOStream*)st); 509 httpstream_enable_chunked_write(http); 510 511 UCX_TEST_BEGIN; 512 513 memset(st->buf->space, 0, st->buf->capacity); 514 515 char *msg = "hello world!"; 516 size_t msglen = strlen(msg); 517 size_t msglen_orig = msglen; 518 519 // limit first write to 3 to only write the header 520 st->max_write = 3; 521 io_set_max_writes(1); 522 523 ssize_t w = net_write(http, msg, msglen); 524 525 UCX_TEST_ASSERT(w == 0, "write 1: wrong return value"); 526 UCX_TEST_ASSERT(st->buf->size == 3, "write 1: wrong buf size"); 527 UCX_TEST_ASSERT(st->buf->space[0] == 'c', "write 1: wrong buf content"); 528 UCX_TEST_ASSERT(st->buf->space[2] == '\n', "write 1: wrong buf content"); 529 530 w = net_write(http, msg, msglen); 531 UCX_TEST_ASSERT(w == 3, "write 2: wrong return value"); 532 UCX_TEST_ASSERT(st->buf->size == 6, "write 2: wrong buf size"); 533 UCX_TEST_ASSERT(!memcmp(st->buf->space, "c\r\nhel\0", 7), "write 2: wrong buf content"); 534 535 msg += w; 536 msglen -= w; 537 538 w = net_write(http, msg, msglen); 539 UCX_TEST_ASSERT(w == 3, "write 3: wrong return value"); 540 UCX_TEST_ASSERT(st->buf->size == 9, "write 3: wrong buf size"); 541 UCX_TEST_ASSERT(!memcmp(st->buf->space, "c\r\nhello \0", 10), "write 3: wrong buf content"); 542 543 st->max_write = 1024; 544 msg += w; 545 msglen -= w; 546 547 w = net_write(http, msg, msglen); 548 UCX_TEST_ASSERT(w == msglen, "write 4: wrong return value"); 549 UCX_TEST_ASSERT(st->buf->size == 3 + msglen_orig + 2, "write 4: wrong buf size"); 550 UCX_TEST_ASSERT(!memcmp(st->buf->space, "c\r\nhello world!\r\n", st->buf->size), "write 4: wrong buf content"); 551 552 testutil_destroy_session(sn); 553 testutil_iostream_destroy(st); 554 555 UCX_TEST_END; 556 } 557 558 UCX_TEST(test_io_httpstream_chunked_write_partial_trailer) { 559 Session *sn = testutil_session(); 560 561 TestIOStream *st = testutil_iostream(2048, TRUE); 562 IOStream *http = httpstream_new(sn->pool, (IOStream*)st); 563 httpstream_enable_chunked_write(http); 564 io_set_max_writes(1); 565 566 UCX_TEST_BEGIN; 567 568 memset(st->buf->space, 0, st->buf->capacity); 569 570 char *msg = "hello world!"; 571 size_t msglen = strlen(msg); 572 573 char *msg2 = "newmsg"; 574 size_t msglen2 = strlen(msg2); 575 576 char *msg3 = "msg3"; 577 size_t msglen3 = strlen(msg3); 578 579 st->max_write = 3 + msglen; // header + msg, but without trailer 580 581 ssize_t w = net_write(http, msg, msglen); 582 583 UCX_TEST_ASSERT(w == msglen, "write 1: wrong return value"); 584 UCX_TEST_ASSERT(st->buf->size == 3 + msglen, "write 1: wrong buf size"); 585 UCX_TEST_ASSERT(!memcmp(st->buf->space, "c\r\nhello world!\0", st->buf->size + 1), "write 1: wrong buf content"); 586 587 st->max_write = 2 + 3 + msglen2; // trailer + new header + new msg, without new trailer 588 589 w = net_write(http, msg2, msglen2); 590 UCX_TEST_ASSERT(w == msglen2, "write 2: wrong return value"); 591 UCX_TEST_ASSERT(st->buf->size == 3 + msglen + 2 + 3 + msglen2, "write 2: wrong buf size"); 592 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"); 593 594 // limit write to 1 byte: two writes required for trailer, net_write should return 0 595 st->max_write = 1; 596 597 w = net_write(http, "dummymsg", 8); 598 UCX_TEST_ASSERT(w == 0, "write 3: wrong return value"); 599 600 w = net_write(http, "dummymsg", 8); 601 UCX_TEST_ASSERT(w == 0, "write 4: wrong return value"); 602 UCX_TEST_ASSERT(st->buf->size == 3 + msglen + 2 + 3 + msglen2 + 2, "write 4: wrong buf size"); 603 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"); 604 605 st->max_write = 1024; 606 w = net_write(http, msg3, msglen3); 607 608 UCX_TEST_ASSERT(w == msglen3, "write 5: wrong return value"); 609 UCX_TEST_ASSERT(st->buf->size == 3 + msglen + 2 + 3 + msglen2 + 2 + 3 + msglen3 + 2, "write 5: wrong buf size"); 610 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"); 611 612 613 testutil_destroy_session(sn); 614 testutil_iostream_destroy(st); 615 616 UCX_TEST_END; 617 } 618 619 UCX_TEST(test_io_httpstream_chunked_write_partial_trailer_partial_header) { 620 Session *sn = testutil_session(); 621 622 TestIOStream *st = testutil_iostream(2048, TRUE); 623 IOStream *http = httpstream_new(sn->pool, (IOStream*)st); 624 httpstream_enable_chunked_write(http); 625 io_set_max_writes(1); 626 627 UCX_TEST_BEGIN; 628 629 memset(st->buf->space, 0, st->buf->capacity); 630 631 char *msg = "hello world!"; 632 size_t msglen = strlen(msg); 633 634 char *msg2 = "newmsg"; 635 size_t msglen2 = strlen(msg2); 636 637 // Test: write partial trailer followed by partial header write 638 639 st->max_write = 3 + msglen + 1; 640 641 ssize_t w = net_write(http, msg, msglen); 642 643 UCX_TEST_ASSERT(w == msglen, "write 1: wrong return value"); 644 UCX_TEST_ASSERT(st->buf->size == 3 + msglen + 1, "write 1: wrong buf size"); 645 UCX_TEST_ASSERT(!memcmp(st->buf->space, "c\r\nhello world!\r\0", st->buf->size + 1), "write 1: wrong buf content"); 646 647 st->max_write = 2; // write 1 trailer byte and 1 header byte 648 649 w = net_write(http, msg2, msglen2); 650 651 UCX_TEST_ASSERT(w == 0, "write 2: wrong return value"); 652 UCX_TEST_ASSERT(st->buf->size == 3 + msglen + 2 + 1, "write 2: wrong buf size"); 653 UCX_TEST_ASSERT(!memcmp(st->buf->space, "c\r\nhello world!\r\n6\0", st->buf->size + 1), "write 2: wrong buf content"); 654 655 // force partial header write again 656 st->max_write = 1; 657 658 w = net_write(http, msg2, msglen2); 659 660 UCX_TEST_ASSERT(w == 0, "write 3: wrong return value"); 661 UCX_TEST_ASSERT(st->buf->size == 3 + msglen + 2 + 2, "write 3: wrong buf size"); 662 UCX_TEST_ASSERT(!memcmp(st->buf->space, "c\r\nhello world!\r\n6\r\0", st->buf->size + 1), "write 3: wrong buf content"); 663 664 st->max_write = 1024; 665 666 w = net_write(http, msg2, msglen2); 667 668 UCX_TEST_ASSERT(w ==msglen2, "write 4: wrong return value"); 669 UCX_TEST_ASSERT(st->buf->size == 3 + msglen + 2 + 3 + msglen2 + 2, "write 4: wrong buf size"); 670 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"); 671 672 673 testutil_destroy_session(sn); 674 testutil_iostream_destroy(st); 675 676 UCX_TEST_END; 677 } 678 679 UCX_TEST(test_io_httpstream_chunked_write_data_2x) { 680 Session *sn = testutil_session(); 681 682 TestIOStream *st = testutil_iostream(2048, TRUE); 683 IOStream *http = httpstream_new(sn->pool, (IOStream*)st); 684 httpstream_enable_chunked_write(http); 685 io_set_max_writes(1); 686 687 UCX_TEST_BEGIN; 688 689 memset(st->buf->space, 0, st->buf->capacity); 690 691 // Test: First write a partial header, which forces a chunk with a specific 692 // size. After that, write a message, that is bigger than the first 693 // chunk, forcing a start of a second chunk, in one big writev op. 694 695 char *msg = "hello world!"; 696 size_t msglen = strlen(msg); 697 698 char *msg2 = "newmsg"; 699 size_t msglen2 = strlen(msg2); 700 701 char *msg_big = "hello world!newmsg"; 702 size_t msglen_big = strlen(msg_big); 703 704 st->max_write = 1; 705 706 ssize_t w = net_write(http, msg, msglen); // first chunk: msg 707 708 UCX_TEST_ASSERT(w == 0, "write 1: wrong return value"); 709 UCX_TEST_ASSERT(st->buf->size == 1, "write 1: wrong buf size"); 710 711 st->max_write = 1024; 712 713 w = net_write(http, msg_big, msglen_big); // first chunk + new chunk 714 715 UCX_TEST_ASSERT(w == msglen_big, "write 2: wrong return value"); 716 UCX_TEST_ASSERT(st->buf->size == 3 + msglen + 2 + 3 + msglen2 + 2, "write 2: wrong buf size"); 717 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"); 718 719 720 testutil_destroy_session(sn); 721 testutil_iostream_destroy(st); 722 723 UCX_TEST_END; 724 } 725 726 UCX_TEST(test_io_httpstream_chunked_write_xx_limit) { 727 Session *sn = testutil_session(); 728 729 TestIOStream *st = testutil_iostream(2048, TRUE); 730 IOStream *http = httpstream_new(sn->pool, (IOStream*)st); 731 httpstream_enable_chunked_write(http); 732 io_set_max_writes(1); 733 734 UCX_TEST_BEGIN; 735 736 // Test: create testdata and write it in varying chunk sizes, but 737 // limit TestIOStream to 1 to 3 byte writes 738 739 // create test data 740 CxBuffer *testdata = cxBufferCreate(NULL, 1024*16, cxDefaultAllocator, 0); 741 for(size_t i=0;i<testdata->capacity;i++) { 742 cxBufferPut(testdata, 35+(i%91)); 743 } 744 745 st->max_write = 1; 746 747 size_t pos = 0; 748 int chunksize = 1; 749 while(pos < testdata->size) { 750 size_t available = testdata->size - pos; 751 752 char *chunk = testdata->space + pos; 753 size_t chunklen = chunksize > available ? available : chunksize; 754 755 // write chunk 756 size_t chunkpos = 0; 757 int max_writes = chunklen + 24; // max number of write attempts 758 int writes = 0; 759 while(chunkpos < chunklen) { 760 ssize_t w = net_write(http, chunk+chunkpos, chunklen-chunkpos); 761 UCX_TEST_ASSERT(w >= 0, "net_write failed"); 762 chunkpos += w; 763 764 writes++; 765 UCX_TEST_ASSERT(writes < max_writes, "max writes attempts reached"); 766 } 767 768 pos += chunklen; 769 chunksize += 5; 770 771 // increase max write size at some point 772 if(pos + chunksize >= testdata->size) { 773 st->max_write = INT_MAX; 774 } else if(pos > 1024*2) { 775 if(pos < 1024*8) { 776 st->max_write = 2; 777 } else { 778 st->max_write = 3; 779 } 780 } 781 } 782 783 // terminate chunk 784 net_finish(http); 785 786 787 // same code as test_io_httpstream_chunked_write_xx 788 789 // make sure the output is correctly encoded 790 // extract chunks from st->buf by using http_stream_parse_chunk_header 791 // (which should be well-tested) 792 793 WSBool first_chunk = TRUE; 794 int64_t chunklen = 0; 795 796 char *buf = st->buf->space; 797 size_t bufsize = st->buf->size; 798 799 pos = 0; // st->buf position 800 size_t srcpos = 0; // testdata position 801 int debug_counter = 0; 802 while(pos < bufsize) { 803 ssize_t remaining = bufsize - pos; 804 ssize_t src_remaining = testdata->size - srcpos; 805 806 int ret = http_stream_parse_chunk_header(buf+pos, remaining, first_chunk, &chunklen); 807 first_chunk = FALSE; 808 809 // ret must always be > 0 (0: incomplete chunk header, -1: invalid syntax) 810 UCX_TEST_ASSERT(ret > 0, "http_stream_parse_chunk_header ret <= 0"); 811 if(chunklen == 0) { 812 UCX_TEST_ASSERT(src_remaining == 0, "stream end reached but src_remaining > 0"); 813 break; 814 } 815 816 UCX_TEST_ASSERT(chunklen <= src_remaining, "chunklen > src_remaining"); 817 818 char *src_chunk = testdata->space+srcpos; 819 char *buf_chunk = buf+pos+ret; 820 821 UCX_TEST_ASSERT(!memcmp(buf_chunk, src_chunk, chunklen), "memcmp failed"); 822 823 pos += ret + chunklen; 824 srcpos += chunklen; 825 826 debug_counter++; 827 } 828 829 830 testutil_destroy_session(sn); 831 testutil_iostream_destroy(st); 832 cxBufferFree(testdata); 833 834 UCX_TEST_END; 835 } 836