From f785e56dba8ff1f9146dfef79856ac5f65c47ebf Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Tue, 1 Apr 2014 21:47:51 +0900 Subject: [PATCH] Implement ALTSVC frame --- lib/includes/nghttp2/nghttp2.h | 88 +++++++++++++++++- lib/nghttp2_frame.c | 126 +++++++++++++++++++++++++ lib/nghttp2_frame.h | 54 +++++++++++ lib/nghttp2_outbound_item.c | 3 + lib/nghttp2_session.c | 118 +++++++++++++++++++++++- lib/nghttp2_session.h | 47 +++++++++- lib/nghttp2_submit.c | 73 +++++++++++++++ tests/main.c | 5 + tests/nghttp2_frame_test.c | 127 +++++++++++++++++++++++++ tests/nghttp2_frame_test.h | 1 + tests/nghttp2_session_test.c | 163 +++++++++++++++++++++++++++++++++ tests/nghttp2_session_test.h | 2 + tests/nghttp2_test_helper.c | 14 +++ 13 files changed, 814 insertions(+), 7 deletions(-) diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h index e25e8162..3b2df907 100644 --- a/lib/includes/nghttp2/nghttp2.h +++ b/lib/includes/nghttp2/nghttp2.h @@ -409,7 +409,11 @@ typedef enum { /** * The CONTINUATION frame. */ - NGHTTP2_CONTINUATION = 9 + NGHTTP2_CONTINUATION = 9, + /** + * The ALTSVC frame. + */ + NGHTTP2_ALTSVC = 10 } nghttp2_frame_type; /** @@ -950,6 +954,50 @@ typedef struct { int32_t window_size_increment; } nghttp2_window_update; +/** + * @struct + * + * The ALTSVC frame. It has following members: + */ +typedef struct { + /** + * The frame header. + */ + nghttp2_frame_hd hd; + /** + * Protocol ID + */ + uint8_t *protocol_id; + /** + * Host + */ + uint8_t *host; + /** + * Origin + */ + uint8_t *origin; + /** + * The length of |protocol_id| + */ + size_t protocol_id_len; + /** + * The length of |host| + */ + size_t host_len; + /** + * The length of |origin| + */ + size_t origin_len; + /** + * Max-Age + */ + uint32_t max_age; + /** + * Port + */ + uint16_t port; +} nghttp2_altsvc; + /** * @union * @@ -998,6 +1046,10 @@ typedef union { * The WINDOW_UPDATE frame. */ nghttp2_window_update window_update; + /** + * The ALTSVC frame. + */ + nghttp2_altsvc altsvc; } nghttp2_frame; /** @@ -2442,6 +2494,40 @@ int nghttp2_submit_window_update(nghttp2_session *session, uint8_t flags, int32_t stream_id, int32_t window_size_increment); +/** + * @function + * + * Submits ALTSVC frame with given parameters. + * + * The |flags| is currently ignored and should be + * :enum:`NGHTTP2_FLAG_NONE`. + * + * Only the server can send the ALTSVC frame. If |session| is + * initialized as client, this function fails and returns + * NGHTTP2_ERR_INVALID_STATE. + * + * If the |protocol_id_len| is 0, the |protocol_id| could be ``NULL``. + * + * If the |host_len| is 0, the |host| could be ``NULL``. + * + * If the |origin_len| is 0, the |origin| could be ``NULL``. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`NGHTTP2_ERR_INVALID_STATE` + * The function is invoked with |session| which was initialized as + * client. + */ +int nghttp2_submit_altsvc(nghttp2_session *session, uint8_t flags, + int32_t stream_id, + uint32_t max_age, uint16_t port, + const uint8_t *protocol_id, size_t protocol_id_len, + const uint8_t *host, size_t host_len, + const uint8_t *origin, size_t origin_len); + /** * @function * diff --git a/lib/nghttp2_frame.c b/lib/nghttp2_frame.c index 2fa6b4ed..1ef92de0 100644 --- a/lib/nghttp2_frame.c +++ b/lib/nghttp2_frame.c @@ -194,6 +194,37 @@ void nghttp2_frame_window_update_init(nghttp2_window_update *frame, void nghttp2_frame_window_update_free(nghttp2_window_update *frame) {} +void nghttp2_frame_altsvc_init(nghttp2_altsvc *frame, int32_t stream_id, + uint32_t max_age, + uint16_t port, + uint8_t *protocol_id, + size_t protocol_id_len, + uint8_t *host, size_t host_len, + uint8_t *origin, size_t origin_len) +{ + size_t payloadlen; + + /* 8 for Max-Age, Port, Reserved and PID_LEN. 1 for HOST_LEN. */ + payloadlen = 8 + protocol_id_len + 1 + host_len + origin_len; + + nghttp2_frame_set_hd(&frame->hd, payloadlen, NGHTTP2_ALTSVC, + NGHTTP2_FLAG_NONE, stream_id); + + frame->max_age = max_age; + frame->port = port; + frame->protocol_id = protocol_id; + frame->protocol_id_len = protocol_id_len; + frame->host = host; + frame->host_len = host_len; + frame->origin = origin; + frame->origin_len = origin_len; +} + +void nghttp2_frame_altsvc_free(nghttp2_altsvc *frame) +{ + free(frame->protocol_id); +} + void nghttp2_frame_data_init(nghttp2_data *frame, nghttp2_private_data *pdata) { frame->hd = pdata->hd; @@ -700,6 +731,101 @@ void nghttp2_frame_unpack_window_update_payload(nghttp2_window_update *frame, NGHTTP2_WINDOW_SIZE_INCREMENT_MASK; } +int nghttp2_frame_pack_altsvc(nghttp2_bufs *bufs, nghttp2_altsvc *frame) +{ + int rv; + nghttp2_buf *buf; + + assert(bufs->head == bufs->cur); + + buf = &bufs->head->buf; + + buf->pos -= NGHTTP2_FRAME_HDLEN; + + nghttp2_frame_pack_frame_hd(buf->pos, &frame->hd); + + nghttp2_put_uint32be(buf->last, frame->max_age); + buf->last += 4; + + nghttp2_put_uint16be(buf->last, frame->port); + buf->last += 2; + + /* Reserved */ + buf->last[0] = 0; + ++buf->last; + + buf->last[0] = frame->protocol_id_len; + ++buf->last; + + rv = nghttp2_bufs_add(bufs, frame->protocol_id, frame->protocol_id_len); + if(rv != 0) { + return rv; + } + + rv = nghttp2_bufs_addb(bufs, frame->host_len); + if(rv != 0) { + return rv; + } + + rv = nghttp2_bufs_add(bufs, frame->host, frame->host_len); + if(rv != 0) { + return rv; + } + + rv = nghttp2_bufs_add(bufs, frame->origin, frame->origin_len); + if(rv != 0) { + return rv; + } + + return 0; +} + +int nghttp2_frame_unpack_altsvc_payload(nghttp2_altsvc *frame, + const uint8_t *payload, + size_t payloadlen, + uint8_t *var_gift_payload, + size_t var_gift_payloadlen) +{ + nghttp2_buf buf; + + frame->max_age = nghttp2_get_uint32(payload); + payload += 4; + + frame->port = nghttp2_get_uint16(payload); + payload += 2; + + /* Skip Reserved */ + ++payload; + + frame->protocol_id_len = *payload; + + nghttp2_buf_wrap_init(&buf, var_gift_payload, var_gift_payloadlen); + buf.last += var_gift_payloadlen; + + /* 1 for HOST_LEN */ + if(nghttp2_buf_len(&buf) < 1 + (ssize_t)frame->protocol_id_len) { + return NGHTTP2_ERR_FRAME_SIZE_ERROR; + } + + frame->protocol_id = buf.pos; + buf.pos += frame->protocol_id_len; + + frame->host_len = *buf.pos; + ++buf.pos; + + if(nghttp2_buf_len(&buf) < (ssize_t)frame->host_len) { + return NGHTTP2_ERR_FRAME_SIZE_ERROR; + } + + frame->host = buf.pos; + buf.pos += frame->host_len; + + frame->origin = buf.pos; + frame->origin_len = nghttp2_buf_len(&buf); + + return 0; +} + nghttp2_settings_entry* nghttp2_frame_iv_copy(const nghttp2_settings_entry *iv, size_t niv) { diff --git a/lib/nghttp2_frame.h b/lib/nghttp2_frame.h index ed92a440..31b972b2 100644 --- a/lib/nghttp2_frame.h +++ b/lib/nghttp2_frame.h @@ -407,6 +407,44 @@ void nghttp2_frame_unpack_window_update_payload(nghttp2_window_update *frame, const uint8_t *payload, size_t payloadlen); +/* + * Packs ALTSVC frame |frame| in wire format and store it in |bufs|. + * This function expands |bufs| as necessary to store frame. + * + * The caller must make sure that nghttp2_bufs_reset(bufs) is called + * before calling this function. + * + * This function returns 0 if it succeeds or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + * NGHTTP2_ERR_BUFFER_ERROR + * Out of buffer space. + */ +int nghttp2_frame_pack_altsvc(nghttp2_bufs *bufs, nghttp2_altsvc *frame); + + +/* + * Unpacks ALTSVC frame byte sequence into |frame|. + * The |payload| of length |payloadlen| contains first 8 bytes of + * payload. The |var_gift_payload| of length |var_gift_payloadlen| + * contains remaining payload and its buffer is gifted to the function + * and then |frame|. The |var_gift_payloadlen| must be freed by + * nghttp2_frame_altsvc_free(). + * + * This function returns 0 if it succeeds or one of the following + * negative error codes: + * + * NGHTTP2_ERR_FRAME_SIZE_ERROR + * The |var_gift_payload| does not contain required data. + */ +int nghttp2_frame_unpack_altsvc_payload(nghttp2_altsvc *frame, + const uint8_t *payload, + size_t payloadlen, + uint8_t *var_gift_payload, + size_t var_gift_payloadlen); + /* * Initializes HEADERS frame |frame| with given values. |frame| takes * ownership of |nva|, so caller must not free it. If |stream_id| is @@ -481,6 +519,22 @@ void nghttp2_frame_window_update_init(nghttp2_window_update *frame, void nghttp2_frame_window_update_free(nghttp2_window_update *frame); +/* protocol_id, host and origin must be allocated to the one chunk of + memory region and protocol_id must point to it. We only free + protocol_id. This means that |protocol_id| is not NULL even if + |protocol_id_len| == 0 and |host_len| + |origin_len| > 0. If + |protocol_id_len|, |host_len| and |origin_len| are all zero, + |protocol_id| can be NULL. */ +void nghttp2_frame_altsvc_init(nghttp2_altsvc *frame, int32_t stream_id, + uint32_t max_age, + uint16_t port, + uint8_t *protocol_id, + size_t protocol_id_len, + uint8_t *host, size_t host_len, + uint8_t *origin, size_t origin_len); + +void nghttp2_frame_altsvc_free(nghttp2_altsvc *frame); + void nghttp2_frame_data_init(nghttp2_data *frame, nghttp2_private_data *pdata); /* diff --git a/lib/nghttp2_outbound_item.c b/lib/nghttp2_outbound_item.c index da137c18..db308f14 100644 --- a/lib/nghttp2_outbound_item.c +++ b/lib/nghttp2_outbound_item.c @@ -62,6 +62,9 @@ void nghttp2_outbound_item_free(nghttp2_outbound_item *item) case NGHTTP2_WINDOW_UPDATE: nghttp2_frame_window_update_free(&frame->window_update); break; + case NGHTTP2_ALTSVC: + nghttp2_frame_altsvc_free(&frame->altsvc); + break; } } else if(item->frame_cat == NGHTTP2_CAT_DATA) { nghttp2_private_data *data_frame; diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c index 0875c426..951d925e 100644 --- a/lib/nghttp2_session.c +++ b/lib/nghttp2_session.c @@ -199,6 +199,9 @@ static void nghttp2_inbound_frame_reset(nghttp2_session *session) case NGHTTP2_WINDOW_UPDATE: nghttp2_frame_window_update_free(&iframe->frame.window_update); break; + case NGHTTP2_ALTSVC: + nghttp2_frame_altsvc_free(&iframe->frame.altsvc); + break; } memset(&iframe->frame, 0, sizeof(nghttp2_frame)); @@ -662,6 +665,8 @@ int nghttp2_session_add_frame(nghttp2_session *session, break; case NGHTTP2_WINDOW_UPDATE: break; + case NGHTTP2_ALTSVC: + break; } if(frame->hd.type == NGHTTP2_HEADERS && @@ -1665,6 +1670,15 @@ static int nghttp2_session_prep_frame(nghttp2_session *session, if(framebuflen < 0) { return framebuflen; } + break; + case NGHTTP2_ALTSVC: + framebuflen = nghttp2_frame_pack_altsvc(&session->aob.framebufs, + &frame->altsvc); + + if(framebuflen < 0) { + return framebuflen; + } + break; default: return NGHTTP2_ERR_INVALID_ARGUMENT; @@ -2027,6 +2041,9 @@ static int nghttp2_session_after_frame_sent(nghttp2_session *session) case NGHTTP2_WINDOW_UPDATE: /* nothing to do */ break; + case NGHTTP2_ALTSVC: + /* nothing to do */ + break; } nghttp2_active_outbound_item_reset(&session->aob); return 0; @@ -3518,6 +3535,42 @@ static int session_process_goaway_frame(nghttp2_session *session) return nghttp2_session_on_goaway_received(session, frame); } +int nghttp2_session_on_altsvc_received(nghttp2_session *session, + nghttp2_frame *frame) +{ + /* ALTSVC is exptected to be received by client only. We have + already rejected ALTSVC if it is received by server. */ + if(frame->hd.stream_id != 0 && + !nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) { + return nghttp2_session_handle_invalid_connection(session, frame, + NGHTTP2_PROTOCOL_ERROR); + } + + return nghttp2_session_call_on_frame_received(session, frame); +} + +static int session_process_altsvc_frame(nghttp2_session *session) +{ + nghttp2_inbound_frame *iframe = &session->iframe; + nghttp2_frame *frame = &iframe->frame; + int rv; + + rv = nghttp2_frame_unpack_altsvc_payload(&frame->altsvc, + iframe->sbuf.pos, + nghttp2_buf_len(&iframe->sbuf), + iframe->lbuf.pos, + nghttp2_buf_len(&iframe->lbuf)); + + if(rv != 0) { + return nghttp2_session_handle_invalid_connection(session, frame, + NGHTTP2_FRAME_SIZE_ERROR); + } + + nghttp2_buf_wrap_init(&iframe->lbuf, NULL, 0); + + return nghttp2_session_on_altsvc_received(session, frame); +} + static int nghttp2_push_back_deferred_data_func(nghttp2_map_entry *entry, void *ptr) { @@ -4320,6 +4373,37 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, iframe->state = NGHTTP2_IB_READ_NBYTE; inbound_frame_set_mark(iframe, 8); + break; + case NGHTTP2_ALTSVC: + DEBUGF(fprintf(stderr, "recv: ALTSVC\n")); + + iframe->frame.hd.flags = NGHTTP2_FLAG_NONE; + + if(session->server) { + rv = nghttp2_session_terminate_session(session, + NGHTTP2_PROTOCOL_ERROR); + if(nghttp2_is_fatal(rv)) { + return rv; + } + + busy = 1; + + iframe->state = NGHTTP2_IB_IGN_PAYLOAD; + + break; + } + + if(iframe->payloadleft < 9) { + busy = 1; + + iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR; + + break; + } + + iframe->state = NGHTTP2_IB_READ_NBYTE; + inbound_frame_set_mark(iframe, 8); + break; default: DEBUGF(fprintf(stderr, "recv: unknown frame\n")); @@ -4503,6 +4587,25 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, nghttp2_inbound_frame_reset(session); break; + case NGHTTP2_ALTSVC: { + size_t varlen; + + varlen = iframe->frame.hd.length - 8; + + iframe->raw_lbuf = malloc(varlen); + + if(iframe->raw_lbuf == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + nghttp2_buf_wrap_init(&iframe->lbuf, iframe->raw_lbuf, varlen); + + busy = 1; + + iframe->state = NGHTTP2_IB_READ_ALTSVC; + + break; + } default: /* This is unknown frame */ nghttp2_inbound_frame_reset(session); @@ -4710,7 +4813,14 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, break; case NGHTTP2_IB_READ_GOAWAY_DEBUG: - DEBUGF(fprintf(stderr, "recv: [IB_READ_GOAWAY_DEBUG]\n")); + case NGHTTP2_IB_READ_ALTSVC: +#ifdef DEBUGBUILD + if(iframe->state == NGHTTP2_IB_READ_GOAWAY_DEBUG) { + fprintf(stderr, "recv: [IB_READ_GOAWAY_DEBUG]\n"); + } else { + fprintf(stderr, "recv: [IB_READ_ALTSVC]\n"); + } +#endif /* DEBUGBUILD */ readlen = inbound_frame_payload_readlen(iframe, in, last); @@ -4728,7 +4838,11 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, break; } - rv = session_process_goaway_frame(session); + if(iframe->state == NGHTTP2_IB_READ_GOAWAY_DEBUG) { + rv = session_process_goaway_frame(session); + } else { + rv = session_process_altsvc_frame(session); + } if(nghttp2_is_fatal(rv)) { return rv; diff --git a/lib/nghttp2_session.h b/lib/nghttp2_session.h index 0702adf0..e1143df7 100644 --- a/lib/nghttp2_session.h +++ b/lib/nghttp2_session.h @@ -73,6 +73,7 @@ typedef enum { NGHTTP2_IB_FRAME_SIZE_ERROR, NGHTTP2_IB_READ_SETTINGS, NGHTTP2_IB_READ_GOAWAY_DEBUG, + NGHTTP2_IB_READ_ALTSVC, NGHTTP2_IB_EXPECT_CONTINUATION, NGHTTP2_IB_IGN_CONTINUATION, NGHTTP2_IB_READ_PAD_CONTINUATION, @@ -433,6 +434,11 @@ int nghttp2_session_on_push_response_headers_received(nghttp2_session *session, * * NGHTTP2_ERR_NOMEM * Out of memory. + * NGHTTP2_ERR_IGN_HEADER_BLOCK + * Frame was rejected and header block must be decoded but + * result must be ignored. + * NGHTTP2_ERR_CALLBACK_FAILURE + * The read_callback failed */ int nghttp2_session_on_headers_received(nghttp2_session *session, nghttp2_frame *frame, @@ -448,6 +454,8 @@ int nghttp2_session_on_headers_received(nghttp2_session *session, * * NGHTTP2_ERR_NOMEM * Out of memory. + * NGHTTP2_ERR_CALLBACK_FAILURE + * The read_callback failed */ int nghttp2_session_on_priority_received(nghttp2_session *session, nghttp2_frame *frame); @@ -459,7 +467,10 @@ int nghttp2_session_on_priority_received(nghttp2_session *session, * This function returns 0 if it succeeds, or one the following * negative error codes: * - * TBD + * NGHTTP2_ERR_NOMEM + * Out of memory + * NGHTTP2_ERR_CALLBACK_FAILURE + * The read_callback failed */ int nghttp2_session_on_rst_stream_received(nghttp2_session *session, nghttp2_frame *frame); @@ -475,8 +486,6 @@ int nghttp2_session_on_rst_stream_received(nghttp2_session *session, * * NGHTTP2_ERR_NOMEM * Out of memory - * NGHTTP2_ERR_PAUSE - * Callback function returns NGHTTP2_ERR_PAUSE * NGHTTP2_ERR_CALLBACK_FAILURE * The read_callback failed */ @@ -493,6 +502,11 @@ int nghttp2_session_on_settings_received(nghttp2_session *session, * * NGHTTP2_ERR_NOMEM * Out of memory. + * NGHTTP2_ERR_IGN_HEADER_BLOCK + * Frame was rejected and header block must be decoded but + * result must be ignored. + * NGHTTP2_ERR_CALLBACK_FAILURE + * The read_callback failed */ int nghttp2_session_on_push_promise_received(nghttp2_session *session, nghttp2_frame *frame); @@ -506,6 +520,8 @@ int nghttp2_session_on_push_promise_received(nghttp2_session *session, * * NGHTTP2_ERR_NOMEM * Out of memory. + * NGHTTP2_ERR_CALLBACK_FAILURE + * The callback function failed. */ int nghttp2_session_on_ping_received(nghttp2_session *session, nghttp2_frame *frame); @@ -514,7 +530,13 @@ int nghttp2_session_on_ping_received(nghttp2_session *session, * Called when GOAWAY is received, assuming |frame| is properly * initialized. * - * This function returns 0 and never fail. + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + * NGHTTP2_ERR_CALLBACK_FAILURE + * The callback function failed. */ int nghttp2_session_on_goaway_received(nghttp2_session *session, nghttp2_frame *frame); @@ -528,10 +550,27 @@ int nghttp2_session_on_goaway_received(nghttp2_session *session, * * NGHTTP2_ERR_NOMEM * Out of memory. + * NGHTTP2_ERR_CALLBACK_FAILURE + * The callback function failed. */ int nghttp2_session_on_window_update_received(nghttp2_session *session, nghttp2_frame *frame); +/* + * Called when ALTSVC is received, assuming |frame| is properly + * initialized. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + * NGHTTP2_ERR_CALLBACK_FAILURE + * The callback function failed. + */ +int nghttp2_session_on_altsvc_received(nghttp2_session *session, + nghttp2_frame *frame); + /* * Called when DATA is received, assuming |frame| is properly * initialized. diff --git a/lib/nghttp2_submit.c b/lib/nghttp2_submit.c index cf033575..66975696 100644 --- a/lib/nghttp2_submit.c +++ b/lib/nghttp2_submit.c @@ -316,6 +316,79 @@ int nghttp2_submit_window_update(nghttp2_session *session, uint8_t flags, return 0; } +int nghttp2_submit_altsvc(nghttp2_session *session, uint8_t flags, + int32_t stream_id, + uint32_t max_age, uint16_t port, + const uint8_t *protocol_id, size_t protocol_id_len, + const uint8_t *host, size_t host_len, + const uint8_t *origin, size_t origin_len) +{ + int rv; + size_t varlen; + uint8_t *var, *varp; + nghttp2_frame *frame; + uint8_t *copy_protocol_id, *copy_host, *copy_origin; + + if(!session->server) { + return NGHTTP2_ERR_INVALID_STATE; + } + + varlen = protocol_id_len + host_len + origin_len; + + if(varlen == 0) { + var = NULL; + copy_protocol_id = NULL; + copy_host = NULL; + copy_origin = NULL; + } else { + var = malloc(varlen); + + if(var == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + varp = var; + + memcpy(varp, protocol_id, protocol_id_len); + + copy_protocol_id = varp; + varp += protocol_id_len; + + memcpy(varp, host, host_len); + + copy_host = varp; + varp += host_len; + + memcpy(varp, origin, origin_len); + + copy_origin = varp; + varp += origin_len; + } + + frame = malloc(sizeof(nghttp2_frame)); + + if(frame == NULL) { + free(var); + + return NGHTTP2_ERR_NOMEM; + } + + nghttp2_frame_altsvc_init(&frame->altsvc, stream_id, max_age, port, + copy_protocol_id, protocol_id_len, + copy_host, host_len, copy_origin, origin_len); + + rv = nghttp2_session_add_frame(session, NGHTTP2_CAT_CTRL, frame, NULL); + + if(rv != 0) { + nghttp2_frame_altsvc_free(&frame->altsvc); + free(frame); + + return rv; + } + + return 0; +} + static uint8_t set_request_flags(const nghttp2_priority_spec *pri_spec, const nghttp2_data_provider *data_prd) { diff --git a/tests/main.c b/tests/main.c index c53b4ede..eac452f8 100644 --- a/tests/main.c +++ b/tests/main.c @@ -88,6 +88,8 @@ int main(int argc, char* argv[]) test_nghttp2_session_recv_headers_with_priority) || !CU_add_test(pSuite, "session_recv_premature_headers", test_nghttp2_session_recv_premature_headers) || + !CU_add_test(pSuite, "session_recv_altsvc", + test_nghttp2_session_recv_altsvc) || !CU_add_test(pSuite, "session_continue", test_nghttp2_session_continue) || !CU_add_test(pSuite, "session_add_frame", test_nghttp2_session_add_frame) || @@ -161,6 +163,7 @@ int main(int argc, char* argv[]) test_nghttp2_submit_window_update) || !CU_add_test(pSuite, "submit_window_update_local_window_size", test_nghttp2_submit_window_update_local_window_size) || + !CU_add_test(pSuite, "submit_altsvc", test_nghttp2_submit_altsvc) || !CU_add_test(pSuite, "submit_invalid_nv", test_nghttp2_submit_invalid_nv) || !CU_add_test(pSuite, "session_open_stream", @@ -242,6 +245,8 @@ int main(int argc, char* argv[]) test_nghttp2_frame_pack_goaway) || !CU_add_test(pSuite, "frame_pack_window_update", test_nghttp2_frame_pack_window_update) || + !CU_add_test(pSuite, "frame_pack_altsvc", + test_nghttp2_frame_pack_altsvc) || !CU_add_test(pSuite, "nv_array_copy", test_nghttp2_nv_array_copy) || !CU_add_test(pSuite, "iv_check", test_nghttp2_iv_check) || !CU_add_test(pSuite, "hd_deflate", test_nghttp2_hd_deflate) || diff --git a/tests/nghttp2_frame_test.c b/tests/nghttp2_frame_test.c index 2dbc2a4b..60a2a209 100644 --- a/tests/nghttp2_frame_test.c +++ b/tests/nghttp2_frame_test.c @@ -490,6 +490,133 @@ void test_nghttp2_frame_pack_window_update(void) nghttp2_frame_window_update_free(&frame); } +void test_nghttp2_frame_pack_altsvc(void) +{ + nghttp2_altsvc frame, oframe; + nghttp2_bufs bufs; + nghttp2_buf *buf; + size_t protocol_id_len, host_len, origin_len; + uint8_t *protocol_id, *host, *origin; + uint8_t *data; + size_t datalen; + int rv; + size_t payloadlen; + + protocol_id_len = strlen("h2"); + host_len = strlen("h2.example.org"); + origin_len = strlen("www.example.org"); + + datalen = protocol_id_len + host_len + origin_len; + data = malloc(datalen); + + memcpy(data, "h2", protocol_id_len); + protocol_id = data; + + memcpy(data + protocol_id_len, "h2.example.org", host_len); + host = data + protocol_id_len; + + memcpy(data + protocol_id_len + host_len, + "http://www.example.org", origin_len); + origin = data + protocol_id_len + host_len; + + frame_pack_bufs_init(&bufs); + + nghttp2_frame_altsvc_init(&frame, 1000000007, 1u << 31, 4000, + protocol_id, protocol_id_len, + host, host_len, origin, origin_len); + + rv = nghttp2_frame_pack_altsvc(&bufs, &frame); + + CU_ASSERT(0 == rv); + + /* 1 for HOST_LEN */ + CU_ASSERT((ssize_t)(NGHTTP2_FRAME_HDLEN + 8 + 1 + datalen) == + nghttp2_bufs_len(&bufs)); + + CU_ASSERT(0 == unpack_framebuf((nghttp2_frame*)&oframe, &bufs)); + + check_frame_header(8 + 1 + datalen, NGHTTP2_ALTSVC, NGHTTP2_FLAG_NONE, + 1000000007, &oframe.hd); + CU_ASSERT(1u << 31 == oframe.max_age); + CU_ASSERT(4000 == oframe.port); + + CU_ASSERT(protocol_id_len == oframe.protocol_id_len); + CU_ASSERT(memcmp(protocol_id, oframe.protocol_id, protocol_id_len) == 0); + + CU_ASSERT(host_len == oframe.host_len); + CU_ASSERT(memcmp(host, oframe.host, host_len) == 0); + + CU_ASSERT(origin_len == oframe.origin_len); + CU_ASSERT(memcmp(origin, oframe.origin, origin_len) == 0); + + nghttp2_frame_altsvc_free(&oframe); + nghttp2_frame_altsvc_free(&frame); + + memset(&oframe, 0, sizeof(oframe)); + + buf = &bufs.head->buf; + + CU_ASSERT(buf->pos - buf->begin == 2); + + /* Check no origin case */ + + payloadlen = 8 + protocol_id_len + 1 + host_len; + nghttp2_put_uint16be(buf->pos, payloadlen); + + CU_ASSERT(0 == + nghttp2_frame_unpack_altsvc_payload + (&oframe, + buf->pos + NGHTTP2_FRAME_HDLEN, + 8, + buf->pos + NGHTTP2_FRAME_HDLEN + 8, + payloadlen - 8)); + + CU_ASSERT(host_len == oframe.host_len); + CU_ASSERT(0 == oframe.origin_len); + + /* Check insufficient payload length for host */ + payloadlen = 8 + protocol_id_len + 1 + host_len - 1; + nghttp2_put_uint16be(buf->pos, payloadlen); + + CU_ASSERT(NGHTTP2_ERR_FRAME_SIZE_ERROR == + nghttp2_frame_unpack_altsvc_payload + (&oframe, + buf->pos + NGHTTP2_FRAME_HDLEN, + 8, + buf->pos + NGHTTP2_FRAME_HDLEN + 8, + payloadlen - 8)); + + /* Check no host case */ + payloadlen = 8 + protocol_id_len + 1; + nghttp2_put_uint16be(buf->pos, payloadlen); + buf->pos[NGHTTP2_FRAME_HDLEN + 8 + protocol_id_len] = 0; + + CU_ASSERT(0 == + nghttp2_frame_unpack_altsvc_payload + (&oframe, + buf->pos + NGHTTP2_FRAME_HDLEN, + 8, + buf->pos + NGHTTP2_FRAME_HDLEN + 8, + payloadlen - 8)); + + CU_ASSERT(0 == oframe.host_len); + CU_ASSERT(0 == oframe.origin_len); + + /* Check missing HOST_LEN */ + payloadlen = 8 + protocol_id_len; + nghttp2_put_uint16be(buf->pos, payloadlen); + + CU_ASSERT(NGHTTP2_ERR_FRAME_SIZE_ERROR == + nghttp2_frame_unpack_altsvc_payload + (&oframe, + buf->pos + NGHTTP2_FRAME_HDLEN, + 8, + buf->pos + NGHTTP2_FRAME_HDLEN + 8, + payloadlen - 8)); + + nghttp2_bufs_free(&bufs); +} + void test_nghttp2_nv_array_copy(void) { nghttp2_nv *nva; diff --git a/tests/nghttp2_frame_test.h b/tests/nghttp2_frame_test.h index a0ce37b8..652b6bd7 100644 --- a/tests/nghttp2_frame_test.h +++ b/tests/nghttp2_frame_test.h @@ -34,6 +34,7 @@ void test_nghttp2_frame_pack_push_promise(void); void test_nghttp2_frame_pack_ping(void); void test_nghttp2_frame_pack_goaway(void); void test_nghttp2_frame_pack_window_update(void); +void test_nghttp2_frame_pack_altsvc(void); void test_nghttp2_nv_array_copy(void); void test_nghttp2_iv_check(void); diff --git a/tests/nghttp2_session_test.c b/tests/nghttp2_session_test.c index b1ba18de..83a6c5db 100644 --- a/tests/nghttp2_session_test.c +++ b/tests/nghttp2_session_test.c @@ -57,6 +57,7 @@ typedef struct { accumulator *acc; scripted_data_feed *df; int frame_recv_cb_called, invalid_frame_recv_cb_called; + nghttp2_frame_type recv_frame_type; int frame_send_cb_called; nghttp2_frame_type sent_frame_type; int frame_not_send_cb_called; @@ -161,6 +162,7 @@ static int on_frame_recv_callback(nghttp2_session *session, { my_user_data *ud = (my_user_data*)user_data; ++ud->frame_recv_cb_called; + ud->recv_frame_type = frame->hd.type; return 0; } @@ -1084,6 +1086,100 @@ void test_nghttp2_session_recv_premature_headers(void) nghttp2_session_del(session); } +void test_nghttp2_session_recv_altsvc(void) +{ + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_frame frame; + size_t protocol_id_len, host_len, origin_len; + uint8_t *protocol_id, *host, *origin; + uint8_t *data; + size_t datalen; + nghttp2_bufs bufs; + nghttp2_buf *buf; + ssize_t rv; + my_user_data ud; + nghttp2_outbound_item *item; + + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_frame_recv_callback = on_frame_recv_callback; + + protocol_id_len = strlen("h2"); + host_len = strlen("h2.example.org"); + origin_len = strlen("www.example.org"); + + datalen = protocol_id_len + host_len + origin_len; + data = malloc(datalen); + + memcpy(data, "h2", protocol_id_len); + protocol_id = data; + + memcpy(data + protocol_id_len, "h2.example.org", host_len); + host = data + protocol_id_len; + + memcpy(data + protocol_id_len + host_len, + "http://www.example.org", origin_len); + origin = data + protocol_id_len + host_len; + + nghttp2_frame_altsvc_init(&frame.altsvc, 1000000007, 1u << 31, 4000, + protocol_id, protocol_id_len, + host, host_len, origin, origin_len); + + rv = nghttp2_frame_pack_altsvc(&bufs, &frame.altsvc); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + + nghttp2_frame_altsvc_free(&frame.altsvc); + + buf = &bufs.head->buf; + assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf)); + + /* error if server receives ALTSVC */ + nghttp2_session_server_new(&session, &callbacks, &ud); + + ud.frame_recv_cb_called = 0; + + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT(rv == nghttp2_buf_len(buf)); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_GOAWAY == OB_CTRL_TYPE(item)); + + nghttp2_session_del(session); + + nghttp2_session_client_new(&session, &callbacks, &ud); + + ud.frame_recv_cb_called = 0; + + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT(rv == nghttp2_buf_len(buf)); + CU_ASSERT(1 == ud.frame_recv_cb_called); + CU_ASSERT(NGHTTP2_ALTSVC == ud.recv_frame_type); + + /* premature payload */ + nghttp2_put_uint16be(buf->pos, 8); + + ud.frame_recv_cb_called = 0; + + rv = nghttp2_session_mem_recv(session, buf->pos, NGHTTP2_FRAME_HDLEN + 8); + + CU_ASSERT(rv == NGHTTP2_FRAME_HDLEN + 8); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NGHTTP2_GOAWAY == OB_CTRL_TYPE(item)); + + nghttp2_bufs_free(&bufs); + nghttp2_session_del(session); +} + void test_nghttp2_session_continue(void) { nghttp2_session *session; @@ -3381,6 +3477,73 @@ void test_nghttp2_submit_window_update_local_window_size(void) nghttp2_session_del(session); } +void test_nghttp2_submit_altsvc(void) +{ + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + const char protocol_id[] = "h2"; + const char host[] = "localhost"; + const char origin[] = "http://localhost/"; + nghttp2_frame *frame; + nghttp2_outbound_item *item; + my_user_data ud; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_frame_send_callback = on_frame_send_callback; + + nghttp2_session_client_new(&session, &callbacks, NULL); + + CU_ASSERT(NGHTTP2_ERR_INVALID_STATE == + nghttp2_submit_altsvc(session, NGHTTP2_FLAG_NONE, + 0, 0, 3000, + (const uint8_t*)protocol_id, + strlen(protocol_id), + (const uint8_t*)host, strlen(host), + (const uint8_t*)origin, strlen(origin))); + + nghttp2_session_del(session); + + nghttp2_session_server_new(&session, &callbacks, &ud); + + CU_ASSERT(0 == + nghttp2_submit_altsvc(session, NGHTTP2_FLAG_NONE, + 9, 12345, 3000, + (const uint8_t*)protocol_id, + strlen(protocol_id), + (const uint8_t*)host, strlen(host), + (const uint8_t*)origin, strlen(origin))); + + item = nghttp2_session_get_next_ob_item(session); + + frame = OB_CTRL(item); + + CU_ASSERT(NGHTTP2_ALTSVC == frame->hd.type); + CU_ASSERT(9 == frame->hd.stream_id); + + CU_ASSERT(12345 == frame->altsvc.max_age); + CU_ASSERT(3000 == frame->altsvc.port); + + CU_ASSERT(strlen(protocol_id) == frame->altsvc.protocol_id_len); + CU_ASSERT(strlen(host) == frame->altsvc.host_len); + CU_ASSERT(strlen(origin) == frame->altsvc.origin_len); + + CU_ASSERT(0 == memcmp(protocol_id, frame->altsvc.protocol_id, + frame->altsvc.protocol_id_len)); + CU_ASSERT(0 == memcmp(host, frame->altsvc.host, frame->altsvc.host_len)); + CU_ASSERT(0 == memcmp(origin, frame->altsvc.origin, + frame->altsvc.origin_len)); + + ud.frame_send_cb_called = 0; + + CU_ASSERT(0 == nghttp2_session_send(session)); + + CU_ASSERT(1 == ud.frame_send_cb_called); + CU_ASSERT(NGHTTP2_ALTSVC == ud.sent_frame_type); + + nghttp2_session_del(session); +} + void test_nghttp2_submit_invalid_nv(void) { nghttp2_session *session; diff --git a/tests/nghttp2_session_test.h b/tests/nghttp2_session_test.h index 796af957..0ed4a1f9 100644 --- a/tests/nghttp2_session_test.h +++ b/tests/nghttp2_session_test.h @@ -33,6 +33,7 @@ void test_nghttp2_session_recv_data(void); void test_nghttp2_session_recv_continuation(void); void test_nghttp2_session_recv_headers_with_priority(void); void test_nghttp2_session_recv_premature_headers(void); +void test_nghttp2_session_recv_altsvc(void); void test_nghttp2_session_continue(void); void test_nghttp2_session_add_frame(void); void test_nghttp2_session_on_request_headers_received(void); @@ -72,6 +73,7 @@ void test_nghttp2_submit_settings_update_local_window_size(void); void test_nghttp2_submit_push_promise(void); void test_nghttp2_submit_window_update(void); void test_nghttp2_submit_window_update_local_window_size(void); +void test_nghttp2_submit_altsvc(void); void test_nghttp2_submit_invalid_nv(void); void test_nghttp2_session_open_stream(void); void test_nghttp2_session_get_next_ob_item(void); diff --git a/tests/nghttp2_test_helper.c b/tests/nghttp2_test_helper.c index 653b876d..65253eb9 100644 --- a/tests/nghttp2_test_helper.c +++ b/tests/nghttp2_test_helper.c @@ -47,6 +47,8 @@ int unpack_frame(nghttp2_frame *frame, const uint8_t *in, size_t len) const uint8_t *payload = in + NGHTTP2_FRAME_HDLEN; size_t payloadlen = len - NGHTTP2_FRAME_HDLEN; size_t payloadoff; + uint8_t *gift_payload; + size_t gift_payloadlen; nghttp2_frame_unpack_frame_hd(&frame->hd, in); switch(frame->hd.type) { @@ -85,6 +87,18 @@ int unpack_frame(nghttp2_frame *frame, const uint8_t *in, size_t len) nghttp2_frame_unpack_window_update_payload (&frame->window_update, payload, payloadlen); break; + case NGHTTP2_ALTSVC: + gift_payloadlen = payloadlen - 8; + gift_payload = malloc(gift_payloadlen); + + memcpy(gift_payload, payload + 8, gift_payloadlen); + + payloadlen -= 8; + + rv = nghttp2_frame_unpack_altsvc_payload(&frame->altsvc, + payload, payloadlen, + gift_payload, gift_payloadlen); + break; default: /* Must not be reachable */ assert(0);