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


Reply via email to