buckets/http2_frame_buckets.c (719 lines of code) (raw):

/* ==================================================================== * 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 <stdlib.h> #include <apr_pools.h> #include "serf.h" #include "serf_bucket_util.h" #include "serf_private.h" #include "protocols/http2_buckets.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; serf_bucket_end_of_frame_t end_of_frame; void *end_of_frame_baton; /* These fields are only set after prefix_remaining is 0 */ apr_size_t payload_remaining; /* 0 <= payload_length < 2^24 */ apr_int32_t stream_id; /* 0 <= stream_id < 2^31 */ unsigned char frame_type; unsigned char flags; unsigned char buffer[FRAME_PREFIX_SIZE]; } 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->buffer); ctx->end_of_frame = NULL; return serf_bucket_create(&serf_bucket_type__http2_unframe, allocator, ctx); } void serf__bucket_http2_unframe_set_eof(serf_bucket_t *bucket, serf_bucket_end_of_frame_t end_of_frame, void *end_of_frame_baton) { http2_unframe_context_t *ctx = bucket->data; ctx->end_of_frame = end_of_frame; ctx->end_of_frame_baton = end_of_frame_baton; } apr_status_t serf__bucket_http2_unframe_read_info(serf_bucket_t *bucket, 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; const unsigned char *header; if (ctx->prefix_remaining == 0) { if (stream_id) *stream_id = ctx->stream_id; if (frame_type) *frame_type = ctx->frame_type; if (flags) *flags = ctx->flags; return APR_SUCCESS; } do { status = serf_bucket_read(ctx->stream, ctx->prefix_remaining, &data, &len); if (SERF_BUCKET_READ_ERROR(status)) return status; else if (!status && !len) return SERF_ERROR_EMPTY_READ; if (len < FRAME_PREFIX_SIZE) { memcpy(ctx->buffer + FRAME_PREFIX_SIZE - ctx->prefix_remaining, data, len); ctx->prefix_remaining -= len; header = ctx->buffer; } else { header = (const void *)data; ctx->prefix_remaining = 0; } } while (!status && ctx->prefix_remaining > 0); if (ctx->prefix_remaining == 0) { apr_size_t payload_length = (header[0] << 16) | (header[1] << 8) | (header[2]); ctx->frame_type = header[3]; ctx->flags = header[4]; /* Highest bit of stream_id MUST be ignored */ ctx->stream_id = ((header[5] & 0x7F) << 24) | (header[6] << 16) | (header[7] << 8) | (header[8]); ctx->payload_remaining = payload_length; /* Fill output arguments if necessary */ if (stream_id) *stream_id = ctx->stream_id; if (frame_type) *frame_type = ctx->frame_type; if (flags) *flags = ctx->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 < payload_length) { if (payload_length == 0x485454 && ctx->frame_type == 0x50 && ctx->flags == 0x2F) { /* We found "HTTP/" instead of an actual frame. This is clearly above the initial max payload size of 16384, which applies before we negotiate a bigger size. We found a HTTP/1.1 server that didn't understand our HTTP2 prefix "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" */ return SERF_ERROR_HTTP2_PROTOCOL_ERROR; } return SERF_ERROR_HTTP2_FRAME_SIZE_ERROR; } if (ctx->payload_remaining == 0) status = APR_EOF; else if (APR_STATUS_IS_EOF(status)) status = SERF_ERROR_TRUNCATED_STREAM; /* If we hava a zero-length frame we have to call the eof callback now, as the read operations will just shortcut to APR_EOF */ if (ctx->payload_remaining == 0 && ctx->end_of_frame) { apr_status_t cb_status; cb_status = (*ctx->end_of_frame)(ctx->end_of_frame_baton, bucket); if (SERF_BUCKET_READ_ERROR(cb_status)) status = cb_status; } } else if (APR_STATUS_IS_EOF(status)) { /* Reading frame failed because we couldn't read the header. Report a read failure instead of semi-success */ if (ctx->prefix_remaining == FRAME_PREFIX_SIZE) status = SERF_ERROR_EMPTY_STREAM; else status = 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__bucket_http2_unframe_read_info(bucket, NULL, NULL, NULL); if (status) { *len = 0; return (status == SERF_ERROR_EMPTY_READ) ? APR_SUCCESS : 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) { if (ctx->end_of_frame) status = (*ctx->end_of_frame)(ctx->end_of_frame_baton, bucket); if (!SERF_BUCKET_READ_ERROR(status)) status = APR_EOF; } else if (APR_STATUS_IS_EOF(status)) return SERF_ERROR_HTTP2_FRAME_SIZE_ERROR; } 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__bucket_http2_unframe_read_info(bucket, NULL, NULL, NULL); if (status) { *vecs_used = 0; return (status == SERF_ERROR_EMPTY_READ) ? APR_SUCCESS : 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) { if (ctx->end_of_frame) status = (*ctx->end_of_frame)(ctx->end_of_frame_baton, bucket); if (!SERF_BUCKET_READ_ERROR(status)) status = APR_EOF; } else if (APR_STATUS_IS_EOF(status)) return SERF_ERROR_HTTP2_FRAME_SIZE_ERROR; } 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__bucket_http2_unframe_read_info(bucket, NULL, NULL, NULL); if (status) { *len = 0; return (status == SERF_ERROR_EMPTY_READ) ? APR_SUCCESS : 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__bucket_http2_unframe_read_info(bucket, NULL, NULL, NULL); if (status) return SERF_LENGTH_UNKNOWN; return ctx->payload_remaining; } const serf_bucket_type_t serf_bucket_type__http2_unframe = { "H2-UNFRAME", serf_http2_unframe_read, serf_default_readline, 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 }; 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; char padsize_read; } http2_unpad_context_t; serf_bucket_t * serf__bucket_http2_unpad_create(serf_bucket_t *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; 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) && len > 0) { 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 */ /* http://tools.ietf.org/html/rfc7540#section-6.1 If the length of the padding is the length of the frame payload or greater, the recipient MUST treat this as a connection error (Section 5.4.1) of type PROTOCOL_ERROR. The frame payload includes the length byte, so when remaining is 0, that isn't a protocol error */ if (remaining < ctx->pad_length) return SERF_ERROR_HTTP2_PROTOCOL_ERROR; ctx->payload_remaining = (apr_size_t)remaining - ctx->pad_length; } else if (APR_STATUS_IS_EOF(status)) status = SERF_ERROR_HTTP2_FRAME_SIZE_ERROR; else if (!status) status = APR_EAGAIN; return status; } 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 && ctx->pad_remaining == 0) { *len = 0; return APR_EOF; } if (requested >= ctx->payload_remaining) requested = ctx->payload_remaining + ctx->pad_remaining; status = serf_bucket_read(ctx->stream, requested, data, len); if (!SERF_BUCKET_READ_ERROR(status)) { if (*len < ctx->payload_remaining) ctx->payload_remaining -= *len; else { ctx->pad_remaining -= (*len - ctx->payload_remaining); *len = ctx->payload_remaining; ctx->payload_remaining = 0; if (ctx->pad_remaining == 0) status = APR_EOF; } if (APR_STATUS_IS_EOF(status) && (ctx->pad_remaining != 0 || ctx->payload_remaining != 0)) { status = SERF_ERROR_HTTP2_FRAME_SIZE_ERROR; } } 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 && ctx->pad_remaining == 0) { *vecs_used = 0; return APR_EOF; } if (requested > ctx->payload_remaining) requested = ctx->payload_remaining + ctx->pad_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 total = 0; for (i = 0; i < *vecs_used; i++) total += vecs[i].iov_len; if (total < ctx->payload_remaining) ctx->payload_remaining -= total; else { apr_size_t padread = (total - ctx->payload_remaining); ctx->pad_remaining -= padread; ctx->payload_remaining = 0; /* Remove padding from returned result? */ while (padread && *vecs_used) { struct iovec *cv = &vecs[*vecs_used - 1]; if (cv->iov_len <= padread) { padread -= cv->iov_len; (*vecs_used)--; } else { cv->iov_len -= padread; break; } } if (ctx->pad_remaining == 0) status = APR_EOF; } if (APR_STATUS_IS_EOF(status) && (ctx->pad_remaining != 0 || ctx->payload_remaining != 0)) { status = SERF_ERROR_HTTP2_FRAME_SIZE_ERROR; } } 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; 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; } const serf_bucket_type_t serf_bucket_type__http2_unpad = { "H2-UNPAD", serf_http2_unpad_read, serf_default_readline, 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 }; /* ==================================================================== */ typedef struct serf_http2_frame_context_t { serf_bucket_t *stream; serf_bucket_alloc_t *alloc; serf_bucket_t *chunk; apr_size_t bytes_remaining; apr_size_t max_payload_size; apr_int32_t stream_id; unsigned char frametype; unsigned char flags; char created_frame; apr_int32_t *p_stream_id; void *stream_id_baton; void(*stream_id_alloc)(void *baton, apr_int32_t *stream_id); serf_config_t *config; } 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_uint32_t max_payload_size, 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->max_payload_size = max_payload_size; ctx->frametype = frame_type; ctx->flags = flags; if (max_payload_size > 0xFFFFFF) max_payload_size = 0xFFFFFF; if (!stream_id_alloc || (stream_id && *stream_id >= 0)) { /* Avoid all alloc handling; we know the final id */ ctx->stream_id = stream_id ? *stream_id : 0; ctx->p_stream_id = &ctx->stream_id; ctx->stream_id_alloc = NULL; ctx->stream_id_baton = NULL; } else { /* Delay creating the id until we really need it. Using a higher stream number before a lower version in communication closes the lower number directly (as 'unused') */ ctx->stream_id = -1; ctx->p_stream_id = stream_id; ctx->stream_id_alloc = stream_id_alloc; ctx->stream_id_baton = stream_id_baton; } ctx->config = NULL; ctx->created_frame = FALSE; return serf_bucket_create(&serf_bucket_type__http2_frame, alloc, ctx); } static apr_status_t http2_prepare_frame(serf_bucket_t *bucket) { serf_http2_frame_context_t *ctx = bucket->data; int vecs_used; apr_uint64_t payload_remaining; if (ctx->created_frame) return APR_SUCCESS; /* How long will this frame be? */ if (!ctx->stream) payload_remaining = 0; else payload_remaining = serf_bucket_get_remaining(ctx->stream); if (payload_remaining != SERF_LENGTH_UNKNOWN && payload_remaining > ctx->max_payload_size) { return SERF_ERROR_HTTP2_FRAME_SIZE_ERROR; } else if (payload_remaining != SERF_LENGTH_UNKNOWN) { if (ctx->stream) serf_bucket_aggregate_append(ctx->chunk, ctx->stream); ctx->stream = NULL; /* Now managed by aggregate */ } else { /* Our payload doesn't know how long it is. Our only option now is to create the actual data */ struct iovec vecs[SERF__STD_IOV_COUNT]; apr_status_t status; status = serf_bucket_read_iovec(ctx->stream, ctx->max_payload_size, COUNT_OF(vecs), vecs, &vecs_used); if (SERF_BUCKET_READ_ERROR(status)) return status; else if (APR_STATUS_IS_EOF(status)) { /* OK, we got everything, let's put the data at the start of the aggregate. */ serf_bucket_aggregate_append_iovec(ctx->chunk, vecs, vecs_used); /* Obtain the size now , to avoid problems when the bucket doesn't know that it has nothing remaining*/ payload_remaining = serf_bucket_get_remaining(ctx->chunk); /* Just add the stream behind the iovecs. This keeps the chunks available exactly until they are no longer necessary */ serf_bucket_aggregate_append(ctx->chunk, ctx->stream); ctx->stream = NULL; /* Managed by aggregate */ if (payload_remaining == SERF_LENGTH_UNKNOWN) { /* Should never happen: Aggregate with only iovecs should know size */ return SERF_ERROR_HTTP2_FRAME_SIZE_ERROR; } } else { /* Auch... worst case scenario, we have to copy the data. Luckily we have an absolute limit after which we may error out */ apr_size_t total = 0; char *data = serf_bucket_mem_alloc(bucket->allocator, ctx->max_payload_size); serf__copy_iovec(data, &total, vecs, vecs_used); while (!APR_STATUS_IS_EOF(status) && total < ctx->max_payload_size) { apr_size_t read; status = serf_bucket_read_iovec(ctx->stream, ctx->max_payload_size - total + 1, COUNT_OF(vecs), vecs, &vecs_used); if (SERF_BUCKET_READ_ERROR(status)) { serf_bucket_mem_free(bucket->allocator, data); return status; } serf__copy_iovec(data, &read, vecs, vecs_used); total += read; if (status && !APR_STATUS_IS_EOF(status)) { /* Checkpoint what we got now... Next time this function is called the buffer is read first and then continued from the original stream */ serf_bucket_t *new_stream; new_stream = serf_bucket_aggregate_create(bucket->allocator); serf_bucket_aggregate_append( new_stream, serf_bucket_simple_own_create(data, total, bucket->allocator)); serf_bucket_aggregate_append(new_stream, ctx->stream); ctx->stream = new_stream; return status; } } if (total > ctx->max_payload_size) { /* The chunk is at least 1 byte bigger then allowed */ serf_bucket_mem_free(bucket->allocator, data); return SERF_ERROR_HTTP2_FRAME_SIZE_ERROR; } else { /* Ok, we have what we need in our buffer */ serf_bucket_aggregate_append( ctx->chunk, serf_bucket_simple_own_create(data, total, bucket->allocator)); payload_remaining = total; /* And we no longer need stream */ serf_bucket_destroy(ctx->stream); ctx->stream = NULL; } } } /* Ok, now we can construct the frame */ ctx->created_frame = TRUE; { unsigned char frame[FRAME_PREFIX_SIZE]; /* Allocate the streamid if there isn't one. Once the streamid hits the wire it automatically closes all unused identifiers < this value. */ 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; } frame[0] = (payload_remaining >> 16) & 0xFF; frame[1] = (payload_remaining >> 8) & 0xFF; frame[2] = payload_remaining & 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; /* Put the frame before the data */ serf_bucket_aggregate_prepend(ctx->chunk, serf_bucket_simple_copy_create((const char *)&frame, FRAME_PREFIX_SIZE, ctx->alloc)); /* And set the amount of data that we verify will be read */ ctx->bytes_remaining = (apr_size_t)payload_remaining + FRAME_PREFIX_SIZE; } 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 (!SERF_BUCKET_READ_ERROR(status)) { if (*len > ctx->bytes_remaining) { /* Frame payload resized after the header was written */ return SERF_ERROR_HTTP2_FRAME_SIZE_ERROR; } ctx->bytes_remaining -= *len; } if (APR_STATUS_IS_EOF(status)) { if (ctx->bytes_remaining > 0) { /* Frame payload resized after the header was written */ return SERF_ERROR_HTTP2_FRAME_SIZE_ERROR; } } 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 (!SERF_BUCKET_READ_ERROR(status)) { apr_size_t len = 0; int i; for (i = 0; i < *vecs_used; i++) len += vecs[i].iov_len; if (len > ctx->bytes_remaining) { /* Frame resized after the header was written */ return SERF_ERROR_HTTP2_FRAME_SIZE_ERROR; } ctx->bytes_remaining -= len; } if (APR_STATUS_IS_EOF(status)) { if (ctx->bytes_remaining > 0) { /* Frame payload resized after the header was written */ return SERF_ERROR_HTTP2_FRAME_SIZE_ERROR; } } 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) { *len = 0; return APR_SUCCESS; } return serf_bucket_peek(ctx->chunk, data, len); } static apr_uint64_t serf_http2_frame_get_remaining(serf_bucket_t *bucket) { serf_http2_frame_context_t *ctx = bucket->data; if (!ctx->created_frame) return SERF_LENGTH_UNKNOWN; else return ctx->bytes_remaining; } static apr_status_t serf_http2_frame_set_config(serf_bucket_t *bucket, serf_config_t *config) { serf_http2_frame_context_t *ctx = bucket->data; ctx->config = config; if (ctx->stream) return serf_bucket_set_config(ctx->stream, config); return APR_SUCCESS; } static void serf_http2_frame_destroy(serf_bucket_t *bucket) { serf_http2_frame_context_t *ctx = bucket->data; if (ctx->stream) serf_bucket_destroy(ctx->stream); serf_bucket_destroy(ctx->chunk); serf_default_destroy_and_data(bucket); } const serf_bucket_type_t serf_bucket_type__http2_frame = { "H2-FRAME", serf_http2_frame_read, serf_default_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_http2_frame_get_remaining, serf_http2_frame_set_config };