Author: rhuijben Date: Sat Oct 17 17:02:26 2015 New Revision: 1709198 URL: http://svn.apache.org/viewvc?rev=1709198&view=rev Log: Following up on r1709185, add a serf bucket that can remove the optional padding from a http2 frame when needed.
Fix some issues in the unframe buckets while working on this. * serf-dev/dev/buckets/http2_frame_buckets.c (http2_unframe_context_t, serf_bucket_http2_unframe_create): Record if we should destroy the inner stream. It is very unlikely that we should, but in general buckets do this. (serf_http2_unframe_read, serf_http2_unframe_read_iovec, serf_http2_unframe_peek): Properly set len when exiting early. (serf_http2_unframe_destroy): New function. (serf_bucket_type_http2_unframe): Add serf_http2_unframe_destroy. (*): Add unpad bucket. * serf-dev/dev/serf_bucket_types.h (serf_bucket_http2_unframe_create): Add destroy_stream flag. (serf_bucket_type_http2_unpad): New bucket type. (SERF_BUCKET_IS_HTTP2_UNPAD): New define. (serf_bucket_http2_unpad_create): New function. * serf-dev/dev/test/test_buckets.c (test_http2_unframe_buckets): Tweak expectations. Add type test. (test_http2_unpad_buckets): New function. (test_buckets): Add test_http2_unpad_buckets. Modified: serf/trunk/buckets/http2_frame_buckets.c serf/trunk/serf_bucket_types.h serf/trunk/test/test_buckets.c Modified: serf/trunk/buckets/http2_frame_buckets.c URL: http://svn.apache.org/viewvc/serf/trunk/buckets/http2_frame_buckets.c?rev=1709198&r1=1709197&r2=1709198&view=diff ============================================================================== --- serf/trunk/buckets/http2_frame_buckets.c (original) +++ serf/trunk/buckets/http2_frame_buckets.c Sat Oct 17 17:02:26 2015 @@ -42,10 +42,12 @@ typedef struct http2_unframe_context_t unsigned char flags; apr_size_t payload_remaining; + int destroy_stream; } http2_unframe_context_t; serf_bucket_t * serf_bucket_http2_unframe_create(serf_bucket_t *stream, + int destroy_stream, apr_size_t max_payload_size, serf_bucket_alloc_t *allocator) { @@ -55,7 +57,7 @@ serf_bucket_http2_unframe_create(serf_bu ctx->stream = stream; ctx->max_payload_size = max_payload_size; ctx->prefix_remaining = sizeof(ctx->prefix_buffer); - + ctx->destroy_stream = destroy_stream; return serf_bucket_create(&serf_bucket_type_http2_unframe, allocator, ctx); } @@ -139,7 +141,10 @@ serf_http2_unframe_read(serf_bucket_t *b NULL, NULL); if (status) - return status; + { + *len = 0; + return status; + } if (ctx->payload_remaining == 0) { @@ -176,7 +181,10 @@ serf_http2_unframe_read_iovec(serf_bucke NULL, NULL); if (status) - return status; + { + *vecs_used = 0; + return status; + } if (ctx->payload_remaining == 0) { @@ -218,7 +226,10 @@ serf_http2_unframe_peek(serf_bucket_t *b NULL, NULL); if (status) - return status; + { + *len = 0; + return status; + } status = serf_bucket_peek(ctx->stream, data, len); if (!SERF_BUCKET_READ_ERROR(status)) @@ -230,6 +241,17 @@ serf_http2_unframe_peek(serf_bucket_t *b return status; } +static void +serf_http2_unframe_destroy(serf_bucket_t *bucket) +{ + http2_unframe_context_t *ctx = bucket->data; + + if (ctx->destroy_stream) + serf_bucket_destroy(ctx->stream); + + serf_default_destroy_and_data(bucket); +} + static apr_uint64_t serf_http2_unframe_get_remaining(serf_bucket_t *bucket) { @@ -256,8 +278,245 @@ const serf_bucket_type_t serf_bucket_typ serf_default_read_for_sendfile, serf_buckets_are_v2, serf_http2_unframe_peek, - serf_default_destroy_and_data, + serf_http2_unframe_destroy, serf_default_read_bucket, serf_http2_unframe_get_remaining, serf_default_ignore_config }; + +typedef struct http2_unpad_context_t +{ + serf_bucket_t *stream; + apr_size_t payload_remaining; + apr_size_t pad_remaining; + apr_size_t pad_length; + int padsize_read; + int destroy_stream; +} http2_unpad_context_t; + +serf_bucket_t * +serf_bucket_http2_unpad_create(serf_bucket_t *stream, + int destroy_stream, + serf_bucket_alloc_t *allocator) +{ + http2_unpad_context_t *ctx; + + ctx = serf_bucket_mem_alloc(allocator, sizeof(*ctx)); + ctx->stream = stream; + ctx->padsize_read = FALSE; + ctx->destroy_stream = destroy_stream; + + return serf_bucket_create(&serf_bucket_type_http2_unpad, allocator, ctx); +} + +static apr_status_t +serf_http2_unpad_read_padsize(serf_bucket_t *bucket) +{ + http2_unpad_context_t *ctx = bucket->data; + apr_status_t status; + const char *data; + apr_size_t len; + + if (ctx->padsize_read) + return APR_SUCCESS; + + status = serf_bucket_read(ctx->stream, 1, &data, &len); + if (! SERF_BUCKET_READ_ERROR(status)) + { + apr_int64_t remaining; + ctx->pad_length = *(unsigned char *)data; + ctx->pad_remaining = ctx->pad_length; + ctx->padsize_read = TRUE; + + /* We call get_remaining() *after* reading from ctx->stream, + to allow the framing above us to be read before we call this */ + remaining = serf_bucket_get_remaining(ctx->stream); + + if (remaining == SERF_LENGTH_UNKNOWN + || remaining > APR_SIZE_MAX) + return APR_EGENERAL; /* Can't calculate padding size */ + + if (remaining < ctx->pad_length) + { + ctx->payload_remaining = 0; + ctx->pad_remaining = (apr_size_t)remaining; + } + else + { + ctx->payload_remaining = (apr_size_t)remaining - ctx->pad_length; + } + } + return status; +} + +static apr_status_t +serf_http2_unpad_read_padding(serf_bucket_t *bucket) +{ + http2_unpad_context_t *ctx = bucket->data; + apr_status_t status; + + /* ### What is the most efficient way to skip data? + Should we use serf_bucket_read_iovec()? */ + + while (ctx->pad_remaining > 0) + { + apr_size_t pad_read; + const char *pad_data; + + status = serf_bucket_read(ctx->stream, ctx->pad_remaining, + &pad_data, &pad_read); + + if (! SERF_BUCKET_READ_ERROR(status)) + ctx->pad_remaining -= pad_read; + + if (status) + return status; + } + + return APR_EOF; +} + +static apr_status_t +serf_http2_unpad_read(serf_bucket_t *bucket, + apr_size_t requested, + const char **data, + apr_size_t *len) +{ + http2_unpad_context_t *ctx = bucket->data; + apr_status_t status; + + status = serf_http2_unpad_read_padsize(bucket); + + if (status) + { + *len = 0; + return status; + } + else if (ctx->payload_remaining == 0) + { + *len = 0; + return serf_http2_unpad_read_padding(bucket); + } + + if (requested > ctx->payload_remaining) + requested = ctx->payload_remaining; + + status = serf_bucket_read(ctx->stream, requested, data, len); + if (! SERF_BUCKET_READ_ERROR(status)) + { + ctx->payload_remaining -= *len; + } + + return status; +} + +static apr_status_t +serf_http2_unpad_read_iovec(serf_bucket_t *bucket, + apr_size_t requested, + int vecs_size, + struct iovec *vecs, + int *vecs_used) +{ + http2_unpad_context_t *ctx = bucket->data; + apr_status_t status; + + status = serf_http2_unpad_read_padsize(bucket); + + if (status) + { + *vecs_used = 0; + return status; + } + else if (ctx->payload_remaining == 0) + { + *vecs_used = 0; + return serf_http2_unpad_read_padding(bucket); + } + + if (requested > ctx->payload_remaining) + requested = ctx->payload_remaining; + + status = serf_bucket_read_iovec(ctx->stream, requested, + vecs_size, vecs, vecs_used); + if (! SERF_BUCKET_READ_ERROR(status)) + { + int i; + apr_size_t len = 0; + + for (i = 0; i < *vecs_used; i++) + len += vecs[i].iov_len; + + ctx->payload_remaining -= len; + } + + return status; +} + +static apr_status_t +serf_http2_unpad_peek(serf_bucket_t *bucket, + const char **data, + apr_size_t *len) +{ + http2_unpad_context_t *ctx = bucket->data; + apr_status_t status; + + status = serf_http2_unpad_read_padsize(bucket); + + if (status) + { + *len = 0; + return status; + } + + status = serf_bucket_peek(ctx->stream, data, len); + if (!SERF_BUCKET_READ_ERROR(status)) + { + if (*len > ctx->payload_remaining) + *len = ctx->payload_remaining; + } + + return status; +} + +static void +serf_http2_unpad_destroy(serf_bucket_t *bucket) +{ + http2_unpad_context_t *ctx = bucket->data; + + if (ctx->destroy_stream) + serf_bucket_destroy(ctx->stream); + + serf_default_destroy_and_data(bucket); +} + +static apr_uint64_t +serf_http2_unpad_get_remaining(serf_bucket_t *bucket) +{ + http2_unframe_context_t *ctx = bucket->data; + apr_status_t status; + + status = serf_http2_unpad_read_padsize(bucket); + + if (status) + return SERF_LENGTH_UNKNOWN; + + return ctx->payload_remaining; +} + +/* ### need to implement */ +#define serf_h2_dechunk_readline NULL + +const serf_bucket_type_t serf_bucket_type_http2_unpad = { + "H2-UNPAD", + serf_http2_unpad_read, + serf_h2_dechunk_readline /* ### TODO */, + serf_http2_unpad_read_iovec, + serf_default_read_for_sendfile, + serf_buckets_are_v2, + serf_http2_unpad_peek, + serf_http2_unpad_destroy, + serf_default_read_bucket, + serf_http2_unpad_get_remaining, + serf_default_ignore_config +}; + Modified: serf/trunk/serf_bucket_types.h URL: http://svn.apache.org/viewvc/serf/trunk/serf_bucket_types.h?rev=1709198&r1=1709197&r2=1709198&view=diff ============================================================================== --- serf/trunk/serf_bucket_types.h (original) +++ serf/trunk/serf_bucket_types.h Sat Oct 17 17:02:26 2015 @@ -761,6 +761,7 @@ extern const serf_bucket_type_t serf_buc serf_bucket_t * serf_bucket_http2_unframe_create(serf_bucket_t *stream, + int destroy_stream, apr_size_t max_payload_size, serf_bucket_alloc_t *allocator); @@ -780,6 +781,16 @@ serf_http2_unframe_bucket_read_info(serf /* ==================================================================== */ +extern const serf_bucket_type_t serf_bucket_type_http2_unpad; +#define SERF_BUCKET_IS_HTTP2_UNPAD(b) SERF_BUCKET_CHECK((b), http2_unpad) + +serf_bucket_t * +serf_bucket_http2_unpad_create(serf_bucket_t *stream, + int destroy_stream, + serf_bucket_alloc_t *allocator); + +/* ==================================================================== */ + /* ### do we need a PIPE bucket type? they are simple apr_file_t objects */ Modified: serf/trunk/test/test_buckets.c URL: http://svn.apache.org/viewvc/serf/trunk/test/test_buckets.c?rev=1709198&r1=1709197&r2=1709198&view=diff ============================================================================== --- serf/trunk/test/test_buckets.c (original) +++ serf/trunk/test/test_buckets.c Sat Oct 17 17:02:26 2015 @@ -1780,12 +1780,14 @@ static void test_http2_unframe_buckets(C raw = serf_bucket_simple_create(raw_frame1, sizeof(raw_frame1), NULL, NULL, alloc); - unframe = serf_bucket_http2_unframe_create(raw, SERF_READ_ALL_AVAIL, + unframe = serf_bucket_http2_unframe_create(raw, TRUE, SERF_READ_ALL_AVAIL, alloc); + CuAssertTrue(tc, SERF_BUCKET_IS_HTTP2_UNFRAME(unframe)); + status = read_all(unframe, result1, sizeof(result1), &read_len); CuAssertIntEquals(tc, APR_EOF, status); - CuAssertIntEquals(tc, read_len, sizeof(result1)); + CuAssertIntEquals(tc, sizeof(result1), read_len); CuAssertIntEquals(tc, 0, memcmp(result1, "\x00\x01\x00\x00\x00\x00" "\x00\x02\x00\x00\x00\x00", read_len)); @@ -1810,12 +1812,12 @@ static void test_http2_unframe_buckets(C raw = serf_bucket_simple_create(raw_frame2, sizeof(raw_frame2), NULL, NULL, alloc); - unframe = serf_bucket_http2_unframe_create(raw, SERF_READ_ALL_AVAIL, + unframe = serf_bucket_http2_unframe_create(raw, TRUE, SERF_READ_ALL_AVAIL, alloc); status = read_all(unframe, result2, sizeof(result2), &read_len); CuAssertIntEquals(tc, APR_EOF, status); - CuAssertIntEquals(tc, read_len, sizeof(result2)); + CuAssertIntEquals(tc, sizeof(result2), read_len); CuAssertIntEquals(tc, 0, memcmp(result2, "\x00\x01\x00\x00\x00\x00", read_len)); @@ -1841,13 +1843,69 @@ static void test_http2_unframe_buckets(C raw = serf_bucket_simple_create(raw_frame2, sizeof(raw_frame2), NULL, NULL, alloc); - unframe = serf_bucket_http2_unframe_create(raw, 5, - alloc); + unframe = serf_bucket_http2_unframe_create(raw, TRUE, 5, alloc); status = read_all(unframe, result2, sizeof(result2), &read_len); CuAssertIntEquals(tc, SERF_ERROR_HTTP2_FRAME_SIZE_ERROR, status); } +/* Basic test for unframe buckets. */ +static void test_http2_unpad_buckets(CuTest *tc) +{ + test_baton_t *tb = tc->testBaton; + const char raw_frame[] = "\x00\x00\x18" /* 24 bytes payload */ + "\x00\x08" /* Data frame, padding flag */ + "\x00\x00\x00\x07" /* Stream 7 */ + + "\x07" /* 7 bytes padding at end */ + + "\x01\x03\x05\x07\x09\x0B\x0D\x0F" /* 16 bytes */ + "\x00\x02\x04\x06\x08\x0A\x0C\x0E" + + "\x00\x00\x00\x00\x00\x00\x00" /* 7 bytes padding*/ + + "Extra" + ""; + serf_bucket_alloc_t *alloc; + serf_bucket_t *raw; + serf_bucket_t *unframe; + serf_bucket_t *unpad; + char result1[16]; + char result2[5]; + apr_status_t status; + apr_size_t read_len; + + alloc = serf_bucket_allocator_create(tb->pool, NULL, NULL); + + raw = serf_bucket_simple_create(raw_frame, sizeof(raw_frame)-1, + NULL, NULL, alloc); + + unframe = serf_bucket_http2_unframe_create(raw, FALSE, SERF_READ_ALL_AVAIL, + alloc); + + { + apr_size_t streamid; + unsigned char frame_type, flags; + + status = serf_http2_unframe_bucket_read_info(unframe, NULL, &streamid, + &frame_type, &flags); + + CuAssertIntEquals(tc, APR_SUCCESS, status); + CuAssertIntEquals(tc, 7, streamid); + CuAssertIntEquals(tc, 0, frame_type); + CuAssertIntEquals(tc, 8, flags); + } + + unpad = serf_bucket_http2_unpad_create(unframe, TRUE, alloc); + + CuAssertTrue(tc, SERF_BUCKET_IS_HTTP2_UNPAD(unpad)); + + status = read_all(unpad, result1, sizeof(result1), &read_len); + CuAssertIntEquals(tc, APR_EOF, status); + CuAssertIntEquals(tc, sizeof(result1), read_len); + + read_and_check_bucket(tc, raw, "Extra"); +} CuSuite *test_buckets(void) { @@ -1879,6 +1937,7 @@ CuSuite *test_buckets(void) SUITE_ADD_TEST(suite, test_dechunk_buckets); SUITE_ADD_TEST(suite, test_deflate_buckets); SUITE_ADD_TEST(suite, test_http2_unframe_buckets); + SUITE_ADD_TEST(suite, test_http2_unpad_buckets); #if 0 /* This test for issue #152 takes a lot of time generating 4GB+ of random data so it's disabled by default. */