Author: rhuijben
Date: Sat Oct 17 15:28:37 2015
New Revision: 1709185
URL: http://svn.apache.org/viewvc?rev=1709185&view=rev
Log:
Following up on r1709184, really add a new bucket type that handles reading
an http2 frame.
* serf-dev/dev/buckets/http2_frame_buckets.c
New file.
* serf-dev/dev/test/test_buckets.c
(test_http2_unframe_buckets): New function.
(test_buckets): Add function to suite.
Added:
serf/trunk/buckets/http2_frame_buckets.c (with props)
Modified:
serf/trunk/test/test_buckets.c
Added: serf/trunk/buckets/http2_frame_buckets.c
URL:
http://svn.apache.org/viewvc/serf/trunk/buckets/http2_frame_buckets.c?rev=1709185&view=auto
==============================================================================
--- serf/trunk/buckets/http2_frame_buckets.c (added)
+++ serf/trunk/buckets/http2_frame_buckets.c Sat Oct 17 15:28:37 2015
@@ -0,0 +1,263 @@
+/* ====================================================================
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+* ====================================================================
+*/
+
+#include <apr_pools.h>
+
+#include "serf.h"
+#include "serf_bucket_util.h"
+#include "serf_private.h"
+
+/* https://tools.ietf.org/html/rfc7540#section-4.1 */
+#define FRAME_PREFIX_SIZE 9
+
+typedef struct http2_unframe_context_t
+{
+ serf_bucket_t *stream;
+ apr_size_t max_payload_size;
+
+ apr_size_t prefix_remaining;
+ unsigned char prefix_buffer[FRAME_PREFIX_SIZE];
+
+ /* These fields are only set after prefix_remaining is 0 */
+ apr_size_t payload_length; /* 0 <= payload_length < 2^24 */
+ apr_int32_t stream_id; /* 0 <= stream_id < 2^31 */
+ unsigned char frame_type;
+ unsigned char flags;
+
+ apr_size_t payload_remaining;
+} http2_unframe_context_t;
+
+serf_bucket_t *
+serf_bucket_http2_unframe_create(serf_bucket_t *stream,
+ apr_size_t max_payload_size,
+ serf_bucket_alloc_t *allocator)
+{
+ http2_unframe_context_t *ctx;
+
+ ctx = serf_bucket_mem_alloc(allocator, sizeof(*ctx));
+ ctx->stream = stream;
+ ctx->max_payload_size = max_payload_size;
+ ctx->prefix_remaining = sizeof(ctx->prefix_buffer);
+
+
+ return serf_bucket_create(&serf_bucket_type_http2_unframe, allocator, ctx);
+}
+
+apr_status_t
+serf_http2_unframe_bucket_read_info(serf_bucket_t *bucket,
+ apr_size_t *payload_length,
+ apr_int32_t *stream_id,
+ unsigned char *frame_type,
+ unsigned char *flags)
+{
+ http2_unframe_context_t *ctx = bucket->data;
+ const char *data;
+ apr_size_t len;
+ apr_status_t status;
+
+ if (ctx->prefix_remaining == 0)
+ {
+ if (payload_length)
+ *payload_length = ctx->payload_length;
+ if (stream_id)
+ *stream_id = ctx->stream_id;
+ if (frame_type)
+ *frame_type = ctx->frame_type;
+ if (flags)
+ *flags = ctx->flags;
+
+ return APR_SUCCESS;
+ }
+
+ status = serf_bucket_read(ctx->stream, ctx->prefix_remaining, &data, &len);
+ if (! SERF_BUCKET_READ_ERROR(status))
+ {
+ memcpy(ctx->prefix_buffer + FRAME_PREFIX_SIZE - ctx->prefix_remaining,
+ data, len);
+
+ ctx->prefix_remaining -= len;
+
+ if (ctx->prefix_remaining == 0)
+ {
+ ctx->payload_length = (ctx->prefix_buffer[0] << 16)
+ | (ctx->prefix_buffer[1] << 8)
+ | (ctx->prefix_buffer[2]);
+ ctx->frame_type = ctx->prefix_buffer[3];
+ ctx->flags = ctx->prefix_buffer[4];
+ /* Highest bit of stream_id MUST be ignored */
+ ctx->stream_id = ((ctx->prefix_buffer[5] & 0x7F) << 24)
+ | (ctx->prefix_buffer[6] << 16)
+ | (ctx->prefix_buffer[7] << 8)
+ | (ctx->prefix_buffer[8]);
+
+ ctx->payload_remaining = ctx->payload_length;
+
+ /* Use recursion to fill output arguments if necessary */
+ serf_http2_unframe_bucket_read_info(bucket, payload_length,
+ stream_id, frame_type, flags);
+
+ /* https://tools.ietf.org/html/rfc7540#section-4.2
+ An endpoint MUST send an error code of FRAME_SIZE_ERROR if a frame
+ exceeds the size defined in SETTINGS_MAX_FRAME_SIZE, exceeds any
+ limit defined for the frame type, or is too small to contain
+ mandatory frame data.
+ */
+ if (ctx->max_payload_size < ctx->payload_remaining)
+ return SERF_ERROR_HTTP2_FRAME_SIZE_ERROR;
+ }
+ }
+ return status;
+}
+
+static apr_status_t
+serf_http2_unframe_read(serf_bucket_t *bucket,
+ apr_size_t requested,
+ const char **data,
+ apr_size_t *len)
+{
+ http2_unframe_context_t *ctx = bucket->data;
+ apr_status_t status;
+
+ status = serf_http2_unframe_bucket_read_info(bucket, NULL, NULL,
+ NULL, NULL);
+
+ if (status)
+ return status;
+
+ if (ctx->payload_remaining == 0)
+ {
+ *len = 0;
+ return APR_EOF;
+ }
+
+ 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;
+
+ if (ctx->payload_remaining == 0)
+ status = APR_EOF;
+ }
+
+ return status;
+}
+
+static apr_status_t
+serf_http2_unframe_read_iovec(serf_bucket_t *bucket,
+ apr_size_t requested,
+ int vecs_size,
+ struct iovec *vecs,
+ int *vecs_used)
+{
+ http2_unframe_context_t *ctx = bucket->data;
+ apr_status_t status;
+
+ status = serf_http2_unframe_bucket_read_info(bucket, NULL, NULL,
+ NULL, NULL);
+
+ if (status)
+ return status;
+
+ if (ctx->payload_remaining == 0)
+ {
+ *vecs_used = 0;
+ return APR_EOF;
+ }
+
+ 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;
+
+ if (ctx->payload_remaining == 0)
+ status = APR_EOF;
+ }
+
+ return status;
+}
+
+static apr_status_t
+serf_http2_unframe_peek(serf_bucket_t *bucket,
+ const char **data,
+ apr_size_t *len)
+{
+ http2_unframe_context_t *ctx = bucket->data;
+ apr_status_t status;
+
+ status = serf_http2_unframe_bucket_read_info(bucket, NULL, NULL,
+ NULL, NULL);
+
+ if (status)
+ 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 apr_uint64_t
+serf_http2_unframe_get_remaining(serf_bucket_t *bucket)
+{
+ http2_unframe_context_t *ctx = bucket->data;
+ apr_status_t status;
+
+ status = serf_http2_unframe_bucket_read_info(bucket, NULL, NULL,
+ NULL, NULL);
+
+ 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_unframe = {
+ "H2-UNFRAME",
+ serf_http2_unframe_read,
+ serf_h2_dechunk_readline /* ### TODO */,
+ serf_http2_unframe_read_iovec,
+ serf_default_read_for_sendfile,
+ serf_buckets_are_v2,
+ serf_http2_unframe_peek,
+ serf_default_destroy_and_data,
+ serf_default_read_bucket,
+ serf_http2_unframe_get_remaining,
+ serf_default_ignore_config
+};
Propchange: serf/trunk/buckets/http2_frame_buckets.c
------------------------------------------------------------------------------
svn:eol-style = native
Modified: serf/trunk/test/test_buckets.c
URL:
http://svn.apache.org/viewvc/serf/trunk/test/test_buckets.c?rev=1709185&r1=1709184&r2=1709185&view=diff
==============================================================================
--- serf/trunk/test/test_buckets.c (original)
+++ serf/trunk/test/test_buckets.c Sat Oct 17 15:28:37 2015
@@ -1746,6 +1746,109 @@ static void test_linebuf_fetch_crlf(CuTe
}
+/* Basic test for unframe buckets. */
+static void test_http2_unframe_buckets(CuTest *tc)
+{
+ test_baton_t *tb = tc->testBaton;
+ const char raw_frame1[] = "\x00\x00\x0c" /* 12 bytes payload */
+ "\x04\x00" /* Settings frame, no flags*/
+ "\x00\x00\x00\x00" /* Stream 0 */
+
+ "\x00\x01" /* SETTINGS_HEADER_TABLE_SIZE */
+ "\x00\x00\x00\x00" /* Value: 0 */
+
+ "\x00\x02" /* 0x2: SETTINGS_ENABLE_PUSH */
+ "\x00\x00\x00\x00" /* Value: 0 */
+ "";
+ const char raw_frame2[] = "\x00\x00\x06" /* 6 bytes payload */
+ "\x01\x02" /* Frame type 0x01, Flags 0x02 */
+ "\x83\x04\x05\x06" /* Stream 0x03040506. Highest (undefined) bit set */
+
+ "\x00\x01" /* SETTINGS_HEADER_TABLE_SIZE */
+ "\x00\x00\x00\x00" /* Value: 0 */
+ "";
+ serf_bucket_alloc_t *alloc;
+ serf_bucket_t *raw;
+ serf_bucket_t *unframe;
+ char result1[12];
+ char result2[6];
+ apr_status_t status;
+ apr_size_t read_len;
+
+ alloc = serf_bucket_allocator_create(tb->pool, NULL, NULL);
+
+ raw = serf_bucket_simple_create(raw_frame1, sizeof(raw_frame1),
+ NULL, NULL, alloc);
+
+ unframe = serf_bucket_http2_unframe_create(raw, SERF_READ_ALL_AVAIL,
+ alloc);
+
+ status = read_all(unframe, result1, sizeof(result1), &read_len);
+ CuAssertIntEquals(tc, APR_EOF, status);
+ CuAssertIntEquals(tc, read_len, sizeof(result1));
+
+ CuAssertIntEquals(tc, 0, memcmp(result1, "\x00\x01\x00\x00\x00\x00"
+ "\x00\x02\x00\x00\x00\x00", read_len));
+
+ {
+ apr_size_t payload_len;
+ apr_int32_t stream_id;
+ unsigned char frame_type, flags;
+
+ CuAssertIntEquals(tc, 0,
+ serf_http2_unframe_bucket_read_info(unframe,
+ &payload_len,
+ &stream_id,
+ &frame_type,
+ &flags));
+ CuAssertIntEquals(tc, 12, payload_len);
+ CuAssertIntEquals(tc, 0, stream_id, 0);
+ CuAssertIntEquals(tc, 4, frame_type, 4);
+ CuAssertIntEquals(tc, 0, flags);
+ }
+
+ raw = serf_bucket_simple_create(raw_frame2, sizeof(raw_frame2),
+ NULL, NULL, alloc);
+
+ unframe = serf_bucket_http2_unframe_create(raw, 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, 0, memcmp(result2, "\x00\x01\x00\x00\x00\x00",
+ read_len));
+
+ {
+ apr_size_t payload_len;
+ apr_int32_t stream_id;
+ unsigned char frame_type, flags;
+
+ CuAssertIntEquals(tc, 0,
+ serf_http2_unframe_bucket_read_info(unframe,
+ &payload_len,
+ &stream_id,
+ &frame_type,
+ &flags));
+ CuAssertIntEquals(tc, 6, payload_len);
+ CuAssertIntEquals(tc, 0x03040506, stream_id);
+ CuAssertIntEquals(tc, 0x01, frame_type, );
+ CuAssertIntEquals(tc, 0x02, flags);
+ }
+
+ /* And now check the frame oversized error */
+ raw = serf_bucket_simple_create(raw_frame2, sizeof(raw_frame2),
+ NULL, NULL, alloc);
+
+ unframe = serf_bucket_http2_unframe_create(raw, 5,
+ alloc);
+
+ status = read_all(unframe, result2, sizeof(result2), &read_len);
+ CuAssertIntEquals(tc, SERF_ERROR_HTTP2_FRAME_SIZE_ERROR, status);
+}
+
+
CuSuite *test_buckets(void)
{
CuSuite *suite = CuSuiteNew();
@@ -1775,6 +1878,7 @@ CuSuite *test_buckets(void)
SUITE_ADD_TEST(suite, test_random_eagain_in_response);
SUITE_ADD_TEST(suite, test_dechunk_buckets);
SUITE_ADD_TEST(suite, test_deflate_buckets);
+ SUITE_ADD_TEST(suite, test_http2_unframe_buckets);
#if 0
/* This test for issue #152 takes a lot of time generating 4GB+ of random
data so it's disabled by default. */