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