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