Author: rhuijben Date: Tue Oct 20 20:12:18 2015 New Revision: 1709674 URL: http://svn.apache.org/viewvc?rev=1709674&view=rev Log: Add an initial implementation of an http/2 framing bucket.
This adds a lot of plumbing for - On demand streamid allocation - Dynamic windowing But currently it can just create valid single frames. * buckets/http2_frame_buckets.c (header): Fix indentation. (serf_http2_frame_context_t): New typedef. (serf_bucket_http2_frame_create, serf_bucket_http2_frame_within_frame, http2_prepare_frame, serf_http2_frame_read, serf_http2_frame_read_iovec, serf_http2_frame_peek, serf_http2_frame_destroy): New functions. (serf_http2_frame_readline): New define. (serf_bucket_type_http2_frame): New bucket definition. * serf-dev/dev/serf_bucket_types.h (serf_bucket_type_http2_frame): New bucket. (SERF_BUCKET_IS_HTTP2_FRAME): New define. (serf_bucket_http2_frame_create): New function. (serf_bucket_http2_frame_within_frame): New function. * serf-dev/dev/test/test_buckets.c (test_http2_frame_bucket_basic): New function. (test_buckets): Add test_http2_frame_bucket_basic. 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=1709674&r1=1709673&r2=1709674&view=diff ============================================================================== --- serf/trunk/buckets/http2_frame_buckets.c (original) +++ serf/trunk/buckets/http2_frame_buckets.c Tue Oct 20 20:12:18 2015 @@ -1,22 +1,22 @@ /* ==================================================================== -* 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. -* ==================================================================== -*/ + * 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> @@ -514,3 +514,237 @@ const serf_bucket_type_t serf_bucket_typ serf_default_ignore_config }; +/* ==================================================================== */ + +typedef struct serf_http2_frame_context_t { + serf_bucket_t *stream; + serf_bucket_alloc_t *alloc; + serf_bucket_t *chunk; + apr_status_t stream_status; + apr_size_t max_payload_size; + apr_int32_t stream_id; + + unsigned char frametype; + unsigned char flags; + char end_of_stream; + char end_of_headers; + char created_frame; + + apr_int32_t *p_stream_id; + void *stream_id_baton; + void (*stream_id_alloc)(void *baton, apr_int32_t *stream_id); + + apr_size_t current_window; + void *alloc_window_baton; + apr_int32_t (*alloc_window)(void *baton, + unsigned char frametype, + apr_int32_t stream_id, + apr_size_t requested, + int peek); + +} serf_http2_frame_context_t; + +serf_bucket_t * +serf_bucket_http2_frame_create(serf_bucket_t *stream, + unsigned char frame_type, + unsigned char flags, + apr_int32_t *stream_id, + void (*stream_id_alloc)( + void *baton, + apr_int32_t *stream_id), + void *stream_id_baton, + apr_size_t max_payload_size, + apr_int32_t (*alloc_window)( + void *baton, + unsigned char frametype, + apr_int32_t stream_id, + apr_size_t requested, + int peek), + void *alloc_window_baton, + serf_bucket_alloc_t *alloc) +{ + serf_http2_frame_context_t *ctx = serf_bucket_mem_alloc(alloc, sizeof(*ctx)); + + ctx->alloc = alloc; + ctx->stream = stream; + ctx->chunk = serf_bucket_aggregate_create(alloc); + ctx->stream_status = APR_SUCCESS; + ctx->max_payload_size = max_payload_size; + ctx->frametype = frame_type; + ctx->flags = flags; + + if (max_payload_size > 0xFFFFFF) + max_payload_size = 0xFFFFFF; + + ctx->stream_id = (stream_id && *stream_id >= 0) ? *stream_id : -1; + ctx->p_stream_id = stream_id ? stream_id : &ctx->stream_id; + ctx->stream_id_alloc = stream_id_alloc; + ctx->stream_id_baton = stream_id_baton; + ctx->current_window = 0; + ctx->alloc_window = alloc_window; + ctx->alloc_window_baton = alloc_window_baton; + + ctx->end_of_stream = ctx->end_of_headers = ctx->created_frame = FALSE; + + return serf_bucket_create(&serf_bucket_type_http2_frame, alloc, ctx); +} + + +int +serf_bucket_http2_frame_within_frame(serf_bucket_t *bucket) +{ + const char *data; + apr_size_t len; + apr_status_t status = serf_bucket_peek(bucket, &data, &len); + + return APR_STATUS_IS_EOF(status); +} + +static apr_status_t +http2_prepare_frame(serf_bucket_t *bucket) +{ + serf_http2_frame_context_t *ctx = bucket->data; + struct iovec vecs[512]; + int vecs_used; + apr_size_t len; + unsigned char frame[FRAME_PREFIX_SIZE]; + int i; + + if (ctx->created_frame) + return APR_SUCCESS; + + ctx->created_frame = TRUE; + + ctx->stream_status = serf_bucket_read_iovec(ctx->stream, + ctx->max_payload_size, + 512, vecs, &vecs_used); + + if (SERF_BUCKET_READ_ERROR(ctx->stream_status)) + return ctx->stream_status; + + /* For this first version assume that everything fits in a single frame */ + if (! APR_STATUS_IS_EOF(ctx->stream_status)) + abort(); /* Not implemented yet */ + + if (ctx->stream_id < 0 && ctx->stream_id_alloc) + { + ctx->stream_id_alloc(ctx->stream_id_baton, ctx->p_stream_id); + ctx->stream_id = *ctx->p_stream_id; + } + + len = 0; + for (i = 0; i < vecs_used; i++) + len += vecs[i].iov_len; + + frame[0] = (len >> 16) & 0xFF; + frame[1] = (len >> 8) & 0xFF; + frame[2] = len & 0xFF; + frame[3] = ctx->frametype; + frame[4] = ctx->flags; + frame[5] = ((apr_uint32_t)ctx->stream_id >> 24) & 0x7F; + frame[6] = ((apr_uint32_t)ctx->stream_id >> 16) & 0xFF; + frame[7] = ((apr_uint32_t)ctx->stream_id >> 8) & 0xFF; + frame[8] = ctx->stream_id & 0xFF; + + serf_bucket_aggregate_append(ctx->chunk, + serf_bucket_simple_copy_create((const char *)&frame, + FRAME_PREFIX_SIZE, + ctx->alloc)); + if (vecs_used > 0) + serf_bucket_aggregate_append_iovec(ctx->chunk, vecs, vecs_used); + + return APR_SUCCESS; +} + +static apr_status_t +serf_http2_frame_read(serf_bucket_t *bucket, + apr_size_t requested, + const char **data, + apr_size_t *len) +{ + serf_http2_frame_context_t *ctx = bucket->data; + apr_status_t status; + + status = http2_prepare_frame(bucket); + if (status) + return status; + + status = serf_bucket_read(ctx->chunk, requested, data, len); + + if (APR_STATUS_IS_EOF(status)) + return ctx->stream_status; + + return status; +} + +static apr_status_t +serf_http2_frame_read_iovec(serf_bucket_t *bucket, + apr_size_t requested, + int vecs_size, + struct iovec *vecs, + int *vecs_used) +{ + serf_http2_frame_context_t *ctx = bucket->data; + apr_status_t status; + + status = http2_prepare_frame(bucket); + if (status) + return status; + + status = serf_bucket_read_iovec(ctx->chunk, requested, vecs_size, vecs, + vecs_used); + + if (APR_STATUS_IS_EOF(status)) + return ctx->stream_status; + + return status; +} + +static apr_status_t +serf_http2_frame_peek(serf_bucket_t *bucket, + const char **data, + apr_size_t *len) +{ + serf_http2_frame_context_t *ctx = bucket->data; + apr_status_t status; + + status = http2_prepare_frame(bucket); + if (status) + return status; + + status = serf_bucket_peek(ctx->chunk, data, len); + + if (APR_STATUS_IS_EOF(status)) + return ctx->stream_status; + + return status; +} + +static void +serf_http2_frame_destroy(serf_bucket_t *bucket) +{ + serf_http2_frame_context_t *ctx = bucket->data; + + serf_bucket_destroy(ctx->stream); + serf_bucket_destroy(ctx->chunk); + + serf_default_destroy_and_data(bucket); +} + +/* ### need to implement */ +#define serf_http2_frame_readline NULL + +const serf_bucket_type_t serf_bucket_type_http2_frame = { + "H2-FRAME", + serf_http2_frame_read, + serf_http2_frame_readline, + serf_http2_frame_read_iovec, + serf_default_read_for_sendfile, + serf_buckets_are_v2, + serf_http2_frame_peek, + serf_http2_frame_destroy, + serf_default_read_bucket, + serf_default_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=1709674&r1=1709673&r2=1709674&view=diff ============================================================================== --- serf/trunk/serf_bucket_types.h (original) +++ serf/trunk/serf_bucket_types.h Tue Oct 20 20:12:18 2015 @@ -861,6 +861,33 @@ void serf_bucket_hpack_do(serf_bucket_t void *baton); /* ==================================================================== */ +extern const serf_bucket_type_t serf_bucket_type_http2_frame; + +#define SERF_BUCKET_IS_HTTP2_FRAME(b) SERF_BUCKET_CHECK((b), http2_frame) + +serf_bucket_t * +serf_bucket_http2_frame_create(serf_bucket_t *stream, + unsigned char frame_type, + unsigned char flags, + apr_int32_t *stream_id, + void(*stream_id_alloc)( + void *baton, + apr_int32_t *stream_id), + void *stream_id_baton, + apr_size_t max_payload_size, + apr_int32_t(*alloc_window)( + void *baton, + unsigned char frametype, + apr_int32_t stream_id, + apr_size_t requested, + int peek), + void *alloc_window_baton, + serf_bucket_alloc_t *alloc); + +int +serf_bucket_http2_frame_within_frame(serf_bucket_t *bucket); + +/* ==================================================================== */ /* ### 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=1709674&r1=1709673&r2=1709674&view=diff ============================================================================== --- serf/trunk/test/test_buckets.c (original) +++ serf/trunk/test/test_buckets.c Tue Oct 20 20:12:18 2015 @@ -2152,6 +2152,48 @@ static void test_hpack_header_encode(CuT CuAssertTrue(tc, sz <= 59); /* The all literal approach takes 59 bytes */ } +static void test_http2_frame_bucket_basic(CuTest *tc) +{ + test_baton_t *tb = tc->testBaton; + serf_bucket_alloc_t *alloc; + serf_bucket_t *hpack; + apr_size_t sz; + serf_bucket_t *body_in; + serf_bucket_t *frame_in; + serf_bucket_t *frame_out; + apr_int32_t exp_streamid = 0x01020304; + + alloc = serf_bucket_allocator_create(tb->pool, NULL, NULL); + + body_in = SERF_BUCKET_SIMPLE_STRING("This is no config!", alloc); + frame_in = serf_bucket_http2_frame_create(body_in, 99, 7, &exp_streamid, + NULL, NULL, + 16384, NULL, NULL, alloc); + frame_out = serf_bucket_http2_unframe_create(frame_in, FALSE, 16384, alloc); + + read_and_check_bucket(tc, frame_out, "This is no config!"); + + { + apr_int32_t streamid; + unsigned char frametype; + unsigned char flags; + const char *buffer; + apr_size_t sz; + + CuAssertIntEquals(tc, 0, + serf_bucket_http2_unframe_read_info(frame_out, &streamid, + &frametype, &flags)); + CuAssertIntEquals(tc, 0x01020304, streamid); + CuAssertIntEquals(tc, 99, frametype); + CuAssertIntEquals(tc, 7, flags); + + CuAssertIntEquals(tc, APR_EOF, + serf_bucket_read(frame_in, SERF_READ_ALL_AVAIL, + &buffer, &sz)); + CuAssertIntEquals(tc, 0, sz); + } +} + CuSuite *test_buckets(void) { CuSuite *suite = CuSuiteNew(); @@ -2186,6 +2228,7 @@ CuSuite *test_buckets(void) SUITE_ADD_TEST(suite, test_hpack_huffman_decode); SUITE_ADD_TEST(suite, test_hpack_huffman_encode); SUITE_ADD_TEST(suite, test_hpack_header_encode); + SUITE_ADD_TEST(suite, test_http2_frame_bucket_basic); #if 0 /* This test for issue #152 takes a lot of time generating 4GB+ of random data so it's disabled by default. */