diff --git a/CMakeLists.txt b/CMakeLists.txt index 7cfb14f54..5f64b0999 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ else() endif() project(picoquic - VERSION 1.1.28.5 + VERSION 1.1.28.6 DESCRIPTION "picoquic library" LANGUAGES C CXX) diff --git a/UnitTest1/unittest1.cpp b/UnitTest1/unittest1.cpp index 7eeb0f8ce..a8710d982 100644 --- a/UnitTest1/unittest1.cpp +++ b/UnitTest1/unittest1.cpp @@ -2772,6 +2772,24 @@ namespace UnitTest1 Assert::AreEqual(ret, 0); } + TEST_METHOD(h3zero_unidir_error) { + int ret = h3zero_unidir_error_test(); + + Assert::AreEqual(ret, 0); + } + + TEST_METHOD(h3zero_setting_error) { + int ret = h3zero_setting_error_test(); + + Assert::AreEqual(ret, 0); + } + + TEST_METHOD(h3zero_client_data) { + int ret = h3zero_client_data_test(); + + Assert::AreEqual(ret, 0); + } + TEST_METHOD(qpack_huffman) { int ret = qpack_huffman_test(); diff --git a/picohttp/h3zero_common.c b/picohttp/h3zero_common.c index 02b53852b..068004cb7 100644 --- a/picohttp/h3zero_common.c +++ b/picohttp/h3zero_common.c @@ -63,6 +63,9 @@ static void picohttp_clear_stream_ctx(h3zero_stream_ctx_t* stream_ctx) if (stream_ctx->F != NULL) { stream_ctx->F = picoquic_file_close(stream_ctx->F); } + if (stream_ctx->F != NULL) { + stream_ctx->F = picoquic_file_close(stream_ctx->F); + } if (stream_ctx->path_callback != NULL) { (void)stream_ctx->path_callback(stream_ctx->cnx, NULL, 0, picohttp_callback_free, stream_ctx, stream_ctx->path_callback_ctx); @@ -254,6 +257,8 @@ void h3zero_delete_all_stream_prefixes(picoquic_cnx_t * cnx, h3zero_callback_ctx } } +#if 0 +/* Unused code */ uint64_t h3zero_parse_stream_prefix(uint8_t* buffer_8, size_t* nb_in_buffer, uint8_t* data, size_t data_length, size_t * nb_read) { uint64_t prefix = UINT64_MAX; @@ -274,6 +279,7 @@ uint64_t h3zero_parse_stream_prefix(uint8_t* buffer_8, size_t* nb_in_buffer, uin return prefix; } +#endif int h3zero_protocol_init(picoquic_cnx_t* cnx) { @@ -475,7 +481,7 @@ static uint8_t* h3zero_parse_control_stream(uint8_t* bytes, uint8_t* bytes_max, return bytes; } -uint8_t* h3zero_parse_control_stream_id( +uint8_t* h3zero_wt_parse_control_stream_id( uint8_t* bytes, uint8_t* bytes_max, h3zero_data_stream_state_t* stream_state, h3zero_stream_ctx_t* stream_ctx, @@ -525,7 +531,7 @@ uint8_t* h3zero_parse_remote_bidir_stream( } } if (stream_state->stream_type == h3zero_frame_webtransport_stream) { - bytes = h3zero_parse_control_stream_id(bytes, bytes_max, stream_state, stream_ctx, ctx); + bytes = h3zero_wt_parse_control_stream_id(bytes, bytes_max, stream_state, stream_ctx, ctx); } else { /* Not and expected stream */ @@ -567,7 +573,7 @@ uint8_t* h3zero_parse_remote_unidir_stream( bytes = bytes_max; break; case h3zero_stream_type_webtransport: /* unidir stream is used as specified in web transport */ - bytes = h3zero_parse_control_stream_id(bytes, bytes_max, stream_state, stream_ctx, ctx); + bytes = h3zero_wt_parse_control_stream_id(bytes, bytes_max, stream_state, stream_ctx, ctx); break; default: /* Per section 6.2 of RFC 9114, unknown stream types are just ignored */ @@ -1152,17 +1158,16 @@ int h3zero_client_open_stream_file(picoquic_cnx_t* cnx, h3zero_callback_ctx_t* c { int ret = 0; - if (!stream_ctx->is_file_open && ctx->no_disk == 0) { + if (stream_ctx->F == NULL && ctx->no_disk == 0) { int last_err = 0; - stream_ctx->F = picoquic_file_open_ex(stream_ctx->f_name, "wb", &last_err); - if (stream_ctx->F == NULL) { + stream_ctx->F = picoquic_file_open_ex(stream_ctx->file_path, "wb", &last_err); + if (stream_ctx->F== NULL) { picoquic_log_app_message(cnx, - "Could not open file <%s> for stream %" PRIu64 ", error %d (0x%x)\n", stream_ctx->f_name, stream_ctx->stream_id, last_err, last_err); - DBG_PRINTF("Could not open file <%s> for stream %" PRIu64 ", error %d (0x%x)", stream_ctx->f_name, stream_ctx->stream_id, last_err, last_err); + "Could not open file <%s> for stream %" PRIu64 ", error %d (0x%x)\n", stream_ctx->file_path, stream_ctx->stream_id, last_err, last_err); + DBG_PRINTF("Could not open file <%s> for stream %" PRIu64 ", error %d (0x%x)", stream_ctx->file_path, stream_ctx->stream_id, last_err, last_err); ret = -1; } else { - stream_ctx->is_file_open = 1; ctx->nb_open_files++; } } @@ -1175,16 +1180,16 @@ int h3zero_client_close_stream(picoquic_cnx_t * cnx, h3zero_callback_ctx_t* ctx, h3zero_stream_ctx_t* stream_ctx) { int ret = 0; - if (stream_ctx != NULL && stream_ctx->is_open) { + if (stream_ctx != NULL) { picoquic_unlink_app_stream_ctx(cnx, stream_ctx->stream_id); - if (stream_ctx->f_name != NULL) { - free(stream_ctx->f_name); - stream_ctx->f_name = NULL; + + if (stream_ctx->file_path != NULL) { + free(stream_ctx->file_path); + stream_ctx->file_path = NULL; } - stream_ctx->F = picoquic_file_close(stream_ctx->F); - if (stream_ctx->is_file_open) { + if (stream_ctx->F != NULL) { + stream_ctx->F = picoquic_file_close(stream_ctx->F); ctx->nb_open_files--; - stream_ctx->is_file_open = 0; } stream_ctx->is_open = 0; ctx->nb_open_streams--; @@ -1295,7 +1300,7 @@ int h3zero_process_h3_client_data(picoquic_cnx_t* cnx, h3zero_stream_ctx_t* stream_ctx, uint64_t* fin_stream_id) { int ret = 0; - if (!stream_ctx->is_file_open && ctx->no_disk == 0 && stream_ctx->file_path != NULL) { + if (stream_ctx->F == NULL && ctx->no_disk == 0 && stream_ctx->file_path != NULL) { ret = h3zero_client_open_stream_file(cnx, ctx, stream_ctx); } if (ret == 0 && length > 0) { @@ -1355,7 +1360,12 @@ int h3zero_process_h3_client_data(picoquic_cnx_t* cnx, stream_ctx->path_callback(cnx, NULL, 0, picohttp_callback_post_fin, stream_ctx, stream_ctx->path_callback_ctx); } else { - if (h3zero_client_close_stream(cnx, ctx, stream_ctx)) { + if (stream_ctx->ps.stream_state.current_frame_read < stream_ctx->ps.stream_state.current_frame_length) { + ret = picoquic_close(cnx, H3ZERO_FRAME_ERROR); + picoquic_log_app_message(cnx, + "Stream %" PRIu64 " closed when a frame is not complete, error 0x%x", stream_id, H3ZERO_FRAME_ERROR); + } + else if (h3zero_client_close_stream(cnx, ctx, stream_ctx)) { *fin_stream_id = stream_id; if (stream_id <= 64 && !ctx->no_print) { fprintf(stdout, "Stream %" PRIu64 " ended after %" PRIu64 " bytes\n", diff --git a/picohttp/h3zero_common.h b/picohttp/h3zero_common.h index 629bcd2a1..175540267 100644 --- a/picohttp/h3zero_common.h +++ b/picohttp/h3zero_common.h @@ -101,20 +101,20 @@ extern "C" { uint64_t echo_length; uint64_t echo_sent; uint64_t post_received; - /* Client state file management */ - unsigned int is_open : 1; - unsigned int is_file_open : 1; - unsigned int flow_opened : 1; + picohttp_post_data_cb_fn path_callback; + void* path_callback_ctx; + uint8_t frame[PICOHTTP_SERVER_FRAME_MAX]; + /* Client state management */ + unsigned int is_open : 1; /* The client has initiated this stream */ + unsigned int flow_opened : 1; /* Flow control parameters updated to allow receiving expected data */ uint64_t received_length; uint64_t post_size; uint64_t post_sent; - char* f_name; - /* Global state variables */ - uint8_t frame[PICOHTTP_SERVER_FRAME_MAX]; + //char* f_name; + //FILE* FC; + /* File state variables, used by both cclient and server */ char* file_path; FILE* F; - picohttp_post_data_cb_fn path_callback; - void* path_callback_ctx; } h3zero_stream_ctx_t; /* Parsing of a data stream. This is implemented as a filter, with a set of states: diff --git a/picohttp_t/picohttp_t.c b/picohttp_t/picohttp_t.c index 682b05839..3127e9077 100644 --- a/picohttp_t/picohttp_t.c +++ b/picohttp_t/picohttp_t.c @@ -48,6 +48,9 @@ static const picoquic_test_def_t test_table[] = { { "h3zero_integer", h3zero_integer_test }, { "h3zero_varint_stream", h3zero_varint_stream_test }, { "h3zero_incoming_unidir", h3zero_incoming_unidir_test }, + { "h3zero_unidir_error", h3zero_unidir_error_test }, + { "h3zero_setting_error", h3zero_setting_error_test }, + { "h3zero_client_data", h3zero_client_data_test }, { "qpack_huffman", qpack_huffman_test }, { "qpack_huffman_base", qpack_huffman_base_test}, { "h3zero_parse_qpack", h3zero_parse_qpack_test }, diff --git a/picoquic/picoquic.h b/picoquic/picoquic.h index 89dfa0a16..1c4ed286a 100644 --- a/picoquic/picoquic.h +++ b/picoquic/picoquic.h @@ -40,7 +40,7 @@ extern "C" { #endif -#define PICOQUIC_VERSION "1.1.28.5" +#define PICOQUIC_VERSION "1.1.28.6" #define PICOQUIC_ERROR_CLASS 0x400 #define PICOQUIC_ERROR_DUPLICATE (PICOQUIC_ERROR_CLASS + 1) #define PICOQUIC_ERROR_AEAD_CHECK (PICOQUIC_ERROR_CLASS + 3) diff --git a/picoquictest/h3zero_stream_test.c b/picoquictest/h3zero_stream_test.c index c158d8cd5..1d22dcd0c 100644 --- a/picoquictest/h3zero_stream_test.c +++ b/picoquictest/h3zero_stream_test.c @@ -185,7 +185,6 @@ int h3zero_varint_stream_test() * The test requires that a valid context is defined: * * h3zero_stream_ctx_t: incoming stream context. - * */ int incoming_unidir_test_fn(picoquic_cnx_t* cnx, @@ -197,27 +196,34 @@ int incoming_unidir_test_fn(picoquic_cnx_t* cnx, return 0; } +int h3zero_set_test_context(picoquic_quic_t** quic, picoquic_cnx_t** cnx, h3zero_callback_ctx_t** h3_ctx) +{ + int ret = picoquic_test_set_minimal_cnx(quic, cnx); + + if (ret == 0) { + *h3_ctx = h3zero_callback_create_context(NULL); + if (*h3_ctx == NULL) { + ret = -1; + } + else { + picoquic_set_callback(*cnx, h3zero_callback, *h3_ctx); + } + } + + return ret; +} + int h3zero_incoming_unidir_test() { picoquic_quic_t* quic = NULL; picoquic_cnx_t* cnx = NULL; - int ret = picoquic_test_set_minimal_cnx(&quic, &cnx); + h3zero_callback_ctx_t* h3_ctx = NULL; + int ret = h3zero_set_test_context(&quic, &cnx, &h3_ctx); uint64_t stream_id = 3; h3zero_stream_ctx_t* control_stream_ctx; h3zero_stream_ctx_t* stream_ctx = NULL; - h3zero_callback_ctx_t* h3_ctx = NULL; uint8_t unidir_input[] = { 0x40, 0x54, 0x04, 0xf0 }; - if (ret == 0) { - h3_ctx = h3zero_callback_create_context(NULL); - if (h3_ctx == NULL) { - ret = -1; - } - else { - picoquic_set_callback(cnx, h3zero_callback, h3_ctx); - } - } - if (ret == 0) { control_stream_ctx = picowt_set_control_stream(cnx, h3_ctx); if (control_stream_ctx == NULL) { @@ -267,5 +273,507 @@ int h3zero_incoming_unidir_test() h3zero_callback_delete_context(cnx, h3_ctx); picoquic_test_delete_minimal_cnx(&quic, &cnx); + return ret; +} + +/* +* A fraction of the control stream parsing is covered by normal usage : +* -receive h3 settings on control stream, +* -receive web transport control stream. +* This leaves testing gaps : +* -Data received on setting streams after the setting frame +* -Data received on streams that should be ignored. +* +* The interesting stream types are: +* +* h3zero_stream_type_control: settings stream +* h3zero_stream_type_push (ignored) +* h3zero_stream_type_qpack_encoder (ignored) +* h3zero_stream_type_qpack_decoder (ignored) +* some random type (ignored) +* +* The test data on the streams is made of frames. Supported frame types +* are: +* - h3zero_frame_settings +* - h3zero_frame_data +* - h3zero_frame_header +* - h3zero_frame_push_promise +* - h3zero_frame_webtransport_stream +*/ + +uint8_t* h3zero_parse_remote_unidir_stream( + uint8_t* bytes, uint8_t* bytes_max, + h3zero_stream_ctx_t* stream_ctx, + h3zero_callback_ctx_t* ctx, + uint64_t* error_found); + +uint8_t* h3zero_test_get_setting_frame(uint8_t* bytes, uint8_t* bytes_max) +{ + h3zero_settings_t settings = { 0 }; + + bytes = h3zero_settings_encode(bytes, bytes_max, &settings); + + return bytes; +} + +uint8_t* h3zero_get_pretend_frame(uint8_t* bytes, uint8_t* bytes_max, uint64_t frame_type) +{ + if ((bytes = picoquic_frames_varint_encode(bytes, bytes_max, frame_type)) == NULL || + bytes + 2 >= bytes_max) { + bytes = NULL; + } + else { + size_t len = bytes_max - bytes - 2; + if (len > 16) { + len = 16; + } + *bytes++ = (uint8_t)len; + memset(bytes, 0xaa, len); + bytes += len; + } + + return bytes; +} + +uint8_t* h3zero_test_submit_frame(uint8_t* bytes, uint8_t* bytes_max, h3zero_stream_ctx_t* stream_ctx, h3zero_callback_ctx_t* h3_ctx, uint64_t* error_found) +{ + uint8_t* next_bytes = NULL; + for (int i = 0; i < 16 && next_bytes < bytes_max; i++) { + next_bytes = (i == 7) ? bytes_max : bytes + 1; + if (next_bytes > bytes_max) { + next_bytes = bytes_max; + } + if ((bytes = h3zero_parse_remote_unidir_stream(bytes, next_bytes, stream_ctx, h3_ctx, error_found)) != next_bytes) { + bytes = NULL; + break; + } + } + return bytes; +} + +int h3zero_unidir_error_test() +{ + picoquic_quic_t* quic = NULL; + picoquic_cnx_t* cnx = NULL; + h3zero_callback_ctx_t* h3_ctx = NULL; + int ret = h3zero_set_test_context(&quic, &cnx, &h3_ctx); + const uint64_t stream_id[5] = { 3, 7, 11, 13, 17 }; + h3zero_stream_ctx_t * stream_ctx[5] = { NULL, NULL, NULL, NULL, NULL }; + uint64_t stream_type[5] = { h3zero_stream_type_control, h3zero_stream_type_push, + h3zero_stream_type_qpack_encoder, h3zero_stream_type_qpack_decoder, + 123456789 }; + uint64_t frame_type[5] = { h3zero_frame_settings, h3zero_frame_data, + h3zero_frame_header, h3zero_frame_push_promise, 123456789 }; + uint8_t buffer[256]; + uint8_t* bytes = NULL; + uint8_t* last_byte = NULL; + uint8_t* bytes_max = buffer + sizeof(buffer); + uint64_t error_found = 0; + + for (int i = 0; ret == 0 && i < 5; i++) { + if ((stream_ctx[i] = h3zero_find_or_create_stream(cnx, stream_id[i], h3_ctx, 1, 1)) == NULL) { + ret = -1; + } + else if ((bytes = picoquic_frames_varint_encode(buffer, bytes_max, stream_type[i])) != NULL) { + if (i == 0) { + bytes = h3zero_test_get_setting_frame(bytes, bytes_max); + } + else { + bytes = h3zero_get_pretend_frame(bytes, bytes_max, frame_type[i]); + } + } + if (bytes == NULL) { + ret = -1; + } + else { + last_byte = bytes; + bytes = h3zero_test_submit_frame(buffer, last_byte, stream_ctx[i], h3_ctx, &error_found); + if (bytes != last_byte || + error_found != 0 || !h3_ctx->settings.settings_received) { + ret = -1; + } + } + } + /* add random frame to settings, after settings received */ + + /* receive a settings frame again, after settings received. */ + + /* clean up everything */ + picoquic_set_callback(cnx, NULL, NULL); + h3zero_callback_delete_context(cnx, h3_ctx); + picoquic_test_delete_minimal_cnx(&quic, &cnx); + + return ret; +} + +int h3zero_setting_submit(int is_after_settings, uint64_t frame_type, int expect_skip) +{ + picoquic_quic_t* quic = NULL; + picoquic_cnx_t* cnx = NULL; + h3zero_callback_ctx_t* h3_ctx = NULL; + int ret = h3zero_set_test_context(&quic, &cnx, &h3_ctx); + uint8_t buffer[256]; + uint8_t* bytes = NULL; + uint8_t* last_byte = NULL; + uint8_t* bytes_max = buffer + sizeof(buffer); + uint64_t error_found = 0; + h3zero_stream_ctx_t* stream_ctx; + + if (ret != 0 || + (stream_ctx = h3zero_find_or_create_stream(cnx, 3, h3_ctx, 1, 1)) == NULL || + (bytes = picoquic_frames_varint_encode(buffer, bytes_max, h3zero_stream_type_control)) == NULL || + (is_after_settings && + (bytes = h3zero_test_get_setting_frame(bytes, bytes_max)) == NULL) || + (bytes = h3zero_get_pretend_frame(bytes, bytes_max, frame_type)) == NULL){ + ret = -1; /* format error */ + } + + else { + last_byte = bytes; + bytes = h3zero_test_submit_frame(buffer, last_byte, stream_ctx, h3_ctx, &error_found); + if (expect_skip) { + if (bytes == NULL || error_found != 0) { + ret = -1; + } + } + else { + if (bytes != NULL || error_found == 0) { + ret = -1; + } + } + } + + /* clean up everything */ + picoquic_set_callback(cnx, NULL, NULL); + h3zero_callback_delete_context(cnx, h3_ctx); + picoquic_test_delete_minimal_cnx(&quic, &cnx); + + return ret; +} + + +int h3zero_setting_error_test() +{ + uint64_t unexpected_frames[4] = { h3zero_frame_settings, h3zero_frame_data, + h3zero_frame_header, h3zero_frame_push_promise }; + + /* send a frame that is not a setting frames. This is an error */ + int ret = h3zero_setting_submit(0, 1234567, 0); + /* Add unexpected frame after setting */ + for (int i = 0; ret == 0 && i < 4; i++) { + ret = h3zero_setting_submit(1, unexpected_frames[i], 0); + } + /* add random frame to settings, after settings received */ + if (ret == 0) { + ret = h3zero_setting_submit(1, 12345678, 1); + } + + return ret; +} + +/* Unit test of data callback. +* +* we want to exercise `h3zero_callback_data` without actually setting up connections. +* The client will have started a bidir stream context, properly initialized. +* The test program will simulate arrival of frames in this context, until +* FIN or Reset of the stream. + +int h3zero_callback_data(picoquic_cnx_t* cnx, + uint64_t stream_id, uint8_t* bytes, size_t length, + picoquic_call_back_event_t fin_or_event, h3zero_callback_ctx_t* ctx, + h3zero_stream_ctx_t* stream_ctx, uint64_t* fin_stream_id) +* +* The client when sending the command initialized the name of the file +* in stream_ctx->file_path. +* After that, the client will receive header frame and data frame, +* until the FIN. + */ +int h3zero_process_h3_client_data(picoquic_cnx_t* cnx, + uint64_t stream_id, uint8_t* bytes, size_t length, + picoquic_call_back_event_t fin_or_event, h3zero_callback_ctx_t* ctx, + h3zero_stream_ctx_t* stream_ctx, uint64_t* fin_stream_id); + +typedef struct st_client_data_test_spec { + uint64_t stream_type; + unsigned int expect_error : 1; + unsigned int skip_header : 1; + unsigned int trailer_after_header : 1; + unsigned int add_trailer : 1; + unsigned int data_after_trailer : 1; + unsigned int split_data : 1; + unsigned int split_submit : 1; + unsigned int split_fin : 1; + unsigned int short_length : 1; + +} client_data_test_spec_t; + +int h3zero_client_data_set_file_name(h3zero_stream_ctx_t* stream_ctx, char const* path_name) +{ + int ret = 0; + if ((stream_ctx->file_path = picoquic_string_duplicate(path_name)) == NULL) { + ret = -1; + } + else { + /* ensure that no data is present */ + FILE* F = picoquic_file_open(stream_ctx->file_path, "w"); + if (F == NULL) { + ret = -1; + } + else { + (void)picoquic_file_close(F); + } + } + return ret; +} + +uint8_t* h3zero_client_data_get_response(uint8_t * bytes, uint8_t * bytes_max) +{ + uint8_t* length_byte = NULL; + uint8_t* data_byte = NULL; + if ((bytes = picoquic_frames_varint_encode(bytes, bytes_max, h3zero_frame_header)) != NULL) { + if (bytes + 2 < bytes_max) { + length_byte = bytes; + bytes += 2; + data_byte = bytes; + } + else { + bytes = NULL; + } + } + if (bytes != NULL) { + bytes = h3zero_create_response_header_frame_ex(bytes, bytes_max, + h3zero_content_type_text_html, "test client data"); + } + if (bytes != NULL) { + size_t sz = bytes - data_byte; + length_byte[0] = 0x40 + (uint8_t)(sz >> 8); + length_byte[1] = (uint8_t)(sz & 0xff); + } + return bytes; +} + +uint8_t* h3zero_client_data_frame(uint8_t* bytes, uint8_t* bytes_max, size_t data_length) +{ + if ((bytes = picoquic_frames_varint_encode(bytes, bytes_max, h3zero_frame_data)) != NULL && + (bytes = picoquic_frames_varint_encode(bytes, bytes_max, data_length)) != NULL) { + if (bytes + data_length > bytes_max) { + bytes = NULL; + } + else { + memset(bytes, 0xda, data_length); + bytes += data_length; + } + } + return bytes; +} + +uint8_t* h3zero_client_data_frames(uint8_t* bytes, uint8_t* bytes_max, size_t data_length, unsigned int split_data) +{ + size_t l1 = (split_data) ? data_length / 2 : 0; + + if (l1 > 0 && (bytes = h3zero_client_data_frame(bytes, bytes_max, l1)) == NULL){ + bytes = NULL; + } + else { + bytes = h3zero_client_data_frame(bytes, bytes_max, data_length - l1); + } + return bytes; +} + +int h3zero_client_data_submit(picoquic_cnx_t * cnx, uint64_t stream_id, uint8_t* bytes, size_t length, + h3zero_callback_ctx_t* h3_ctx, h3zero_stream_ctx_t* stream_ctx, uint64_t * finstream_id, + client_data_test_spec_t* spec) +{ + int ret = 0; + size_t chunk = (spec->split_submit) ? 7 : length; + size_t submitted = 0; + picoquic_call_back_event_t fin_or_event = picoquic_callback_stream_data; + + if (spec->short_length) { + length--; + } + + while (ret == 0 && submitted < length) { + size_t next_chunk = chunk; + if (submitted + next_chunk >= length) { + next_chunk = length - submitted; + if (!spec->split_fin) { + fin_or_event = picoquic_callback_stream_fin; + } + } + ret = h3zero_process_h3_client_data(cnx, stream_id, bytes + submitted, next_chunk, fin_or_event, h3_ctx, + stream_ctx, finstream_id); + submitted += next_chunk; + } + if (ret == 0 && spec->split_fin) { + ret = h3zero_process_h3_client_data(cnx, stream_id, NULL, 0, picoquic_callback_stream_fin, h3_ctx, + stream_ctx, finstream_id); + } + if (cnx->cnx_state != picoquic_state_ready) { + ret = -1; + } + return ret; +} + +int h3zero_client_data_test_one(client_data_test_spec_t * spec) +{ + picoquic_quic_t* quic = NULL; + picoquic_cnx_t* cnx = NULL; + h3zero_callback_ctx_t* h3_ctx = NULL; + int ret = h3zero_set_test_context(&quic, &cnx, &h3_ctx); + uint8_t buffer[1024]; + uint8_t* bytes = NULL; + uint8_t* bytes_max = buffer + sizeof(buffer); + uint64_t stream_id = 4; + uint64_t fin_stream_id = UINT64_MAX; + size_t data_length = 128; + h3zero_stream_ctx_t* stream_ctx = NULL; + char const* path_name = "h3zero_test_client_data.html"; + + if (ret == 0 && (stream_ctx = h3zero_find_or_create_stream(cnx, 4, h3_ctx, 1, 1)) == NULL) { + ret = -1; + } + else { + cnx->cnx_state = picoquic_state_ready; + ret = h3zero_client_data_set_file_name(stream_ctx, path_name); + if (ret == 0) { + stream_ctx->is_open = 1; + } + } + +#if 1 + bytes = buffer; +#else + /* Encode type and offset */ + bytes = picoquic_frames_varint_encode(buffer, bytes_max, spec->stream_type); + *bytes++ = 0; +#endif + + /* Encode a stream header */ + if (ret == 0 && !spec->skip_header && + (bytes = h3zero_client_data_get_response(bytes, bytes_max)) == NULL){ + ret = -1; + } + /* encode a stray trailer */ + if (ret == 0 && spec->trailer_after_header && + (bytes = h3zero_client_data_get_response(bytes, bytes_max)) == NULL) { + ret = -1; + } + /* Encode a data frame (or 2?)*/ + if (ret == 0 && + (bytes = h3zero_client_data_frames(bytes, bytes_max, data_length, spec->split_data)) == NULL) { + ret = -1; + } + /* Encode a stream trailer */ + if (ret == 0 && spec->add_trailer && + (bytes = h3zero_client_data_get_response(bytes, bytes_max)) == NULL) { + ret = -1; + } + + /* Encode a data frame after the trailer, causing an error */ + if (ret == 0 && spec->data_after_trailer && + (bytes = h3zero_client_data_frames(bytes, bytes_max, 15, 0)) == NULL) { + ret = -1; + } + + /* submit as incoming data */ + if (ret == 0) { + int data_ret = h3zero_client_data_submit(cnx, stream_id, buffer, bytes - buffer, h3_ctx, stream_ctx, &fin_stream_id, spec); + /* verify that the result is as expected */ + if (spec->expect_error) { + if (data_ret == 0) { + ret = -1; + } + } + else { + if (data_ret != 0) { + ret = -1; + } + else { + /* verify that the stream is properly removed */ + FILE* Fbis = picoquic_file_open(path_name, "r"); + if (Fbis == NULL) { + /* error -- the file remained open! */ + ret = -1; + } + else { + long sz; + fseek(Fbis, 0, SEEK_END); + sz = ftell(Fbis); + (void)picoquic_file_close(Fbis); + if (sz != data_length) { + ret = -1; + } + } + } + } + } + + /* clean up everything */ + picoquic_set_callback(cnx, NULL, NULL); + h3zero_callback_delete_context(cnx, h3_ctx); + picoquic_test_delete_minimal_cnx(&quic, &cnx); + + return ret; +} + +int h3zero_client_data_test() +{ + client_data_test_spec_t spec = { 0 }; + int ret = h3zero_client_data_test_one(&spec); + + if (ret == 0) { + memset(&spec, 0, sizeof(spec)); + spec.split_data = 1; + ret = h3zero_client_data_test_one(&spec); + } + + if (ret == 0) { + memset(&spec, 0, sizeof(spec)); + spec.split_fin = 1; + ret = h3zero_client_data_test_one(&spec); + } + + if (ret == 0) { + memset(&spec, 0, sizeof(spec)); + spec.split_submit = 1; + ret = h3zero_client_data_test_one(&spec); + } + + if (ret == 0) { + memset(&spec, 0, sizeof(spec)); + spec.add_trailer = 1; + ret = h3zero_client_data_test_one(&spec); + } + + if (ret == 0) { + memset(&spec, 0, sizeof(spec)); + spec.expect_error = 1; + spec.short_length = 1; + ret = h3zero_client_data_test_one(&spec); + } + + if (ret == 0) { + memset(&spec, 0, sizeof(spec)); + spec.expect_error = 1; + spec.skip_header = 1; + ret = h3zero_client_data_test_one(&spec); + } + + if (ret == 0) { + memset(&spec, 0, sizeof(spec)); + spec.expect_error = 1; + spec.trailer_after_header = 1; + ret = h3zero_client_data_test_one(&spec); + } + + if (ret == 0) { + memset(&spec, 0, sizeof(spec)); + spec.expect_error = 1; + spec.add_trailer = 1; + spec.data_after_trailer = 1; + ret = h3zero_client_data_test_one(&spec); + } + return ret; } \ No newline at end of file diff --git a/picoquictest/picoquictest.h b/picoquictest/picoquictest.h index b9fabc77e..50b3db6da 100644 --- a/picoquictest/picoquictest.h +++ b/picoquictest/picoquictest.h @@ -300,6 +300,9 @@ int initial_server_close_test(); int h3zero_integer_test(); int h3zero_varint_stream_test(); int h3zero_incoming_unidir_test(); +int h3zero_unidir_error_test(); +int h3zero_setting_error_test(); +int h3zero_client_data_test(); int qpack_huffman_test(); int qpack_huffman_base_test(); int h3zero_parse_qpack_test();