From 80342215259d23a534c3c2a4382bdc9a3f4c78a2 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Thu, 10 May 2018 10:57:02 +0900 Subject: [PATCH] Implement ORIGIN frame --- doc/Makefile.am | 1 + lib/includes/nghttp2/nghttp2.h | 81 ++++++++++- lib/nghttp2_frame.c | 130 ++++++++++++++++++ lib/nghttp2_frame.h | 49 +++++++ lib/nghttp2_option.c | 4 + lib/nghttp2_outbound_item.c | 3 + lib/nghttp2_session.c | 119 ++++++++++++++++- lib/nghttp2_session.h | 17 ++- lib/nghttp2_submit.c | 83 ++++++++++++ tests/failmalloc_test.c | 10 ++ tests/main.c | 5 + tests/nghttp2_frame_test.c | 92 +++++++++++++ tests/nghttp2_frame_test.h | 1 + tests/nghttp2_session_test.c | 236 +++++++++++++++++++++++++++++++++ tests/nghttp2_session_test.h | 2 + tests/nghttp2_test_helper.c | 4 + 16 files changed, 834 insertions(+), 3 deletions(-) diff --git a/doc/Makefile.am b/doc/Makefile.am index 07cd34e8..199c8952 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -164,6 +164,7 @@ APIDOCS= \ nghttp2_submit_extension.rst \ nghttp2_submit_goaway.rst \ nghttp2_submit_headers.rst \ + nghttp2_submit_origin.rst \ nghttp2_submit_ping.rst \ nghttp2_submit_priority.rst \ nghttp2_submit_push_promise.rst \ diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h index 14f8950b..29a24a00 100644 --- a/lib/includes/nghttp2/nghttp2.h +++ b/lib/includes/nghttp2/nghttp2.h @@ -611,7 +611,12 @@ typedef enum { * The ALTSVC frame, which is defined in `RFC 7383 * `_. */ - NGHTTP2_ALTSVC = 0x0a + NGHTTP2_ALTSVC = 0x0a, + /** + * The ORIGIN frame, which is defined by `RFC 8336 + * `_. + */ + NGHTTP2_ORIGIN = 0x0c } nghttp2_frame_type; /** @@ -4551,6 +4556,80 @@ NGHTTP2_EXTERN int nghttp2_submit_altsvc(nghttp2_session *session, const uint8_t *field_value, size_t field_value_len); +/** + * @struct + * + * The single entry of an origin. + */ +typedef struct { + /** + * The pointer to origin. No validation is made against this field + * by the library. This is not necessarily NULL-terminated. + */ + uint8_t *origin; + /** + * The length of the |origin|. + */ + size_t origin_len; +} nghttp2_origin_entry; + +/** + * @struct + * + * The payload of ORIGIN frame. ORIGIN frame is a non-critical + * extension to HTTP/2 and defined by `RFC 8336 + * `_. + * + * If this frame is received, and + * `nghttp2_option_set_user_recv_extension_type()` is not set, and + * `nghttp2_option_set_builtin_recv_extension_type()` is set for + * :enum:`NGHTTP2_ORIGIN`, ``nghttp2_extension.payload`` will point to + * this struct. + * + * It has the following members: + */ +typedef struct { + /** + * The number of origins contained in |ov|. + */ + size_t nov; + /** + * The pointer to the array of origins contained in ORIGIN frame. + */ + nghttp2_origin_entry *ov; +} nghttp2_ext_origin; + +/** + * @function + * + * Submits ORIGIN frame. + * + * ORIGIN frame is a non-critical extension to HTTP/2 and defined by + * `RFC 8336 `_. + * + * The |flags| is currently ignored and should be + * :enum:`NGHTTP2_FLAG_NONE`. + * + * The |ov| points to the array of origins. The |nov| specifies the + * number of origins included in |ov|. + * + * The ORIGIN frame is only usable by a server. If this function is + * invoked with client side session, this function returns + * :enum:`NGHTTP2_ERR_INVALID_STATE`. + * + * :enum:`NGHTTP2_ERR_NOMEM` + * Out of memory + * :enum:`NGHTTP2_ERR_INVALID_STATE` + * The function is called from client side session. + * :enum:`NGHTTP2_ERR_INVALID_ARGUMENT` + * There are too many origins, or an origin is too large to fit + * into a default frame payload. + */ +NGHTTP2_EXTERN int nghttp2_submit_origin(nghttp2_session *session, + uint8_t flags, + const nghttp2_origin_entry *ov, + size_t nov); + /** * @function * diff --git a/lib/nghttp2_frame.c b/lib/nghttp2_frame.c index fa7cb695..6e33f3c2 100644 --- a/lib/nghttp2_frame.c +++ b/lib/nghttp2_frame.c @@ -223,6 +223,36 @@ void nghttp2_frame_altsvc_free(nghttp2_extension *frame, nghttp2_mem *mem) { nghttp2_mem_free(mem, altsvc->origin); } +void nghttp2_frame_origin_init(nghttp2_extension *frame, + nghttp2_origin_entry *ov, size_t nov) { + nghttp2_ext_origin *origin; + size_t payloadlen = 0; + size_t i; + + for (i = 0; i < nov; ++i) { + payloadlen += 2 + ov[i].origin_len; + } + + nghttp2_frame_hd_init(&frame->hd, payloadlen, NGHTTP2_ORIGIN, + NGHTTP2_FLAG_NONE, 0); + + origin = frame->payload; + origin->ov = ov; + origin->nov = nov; +} + +void nghttp2_frame_origin_free(nghttp2_extension *frame, nghttp2_mem *mem) { + nghttp2_ext_origin *origin; + + origin = frame->payload; + if (origin == NULL) { + return; + } + /* We use the same buffer for all resources pointed by the field of + origin directly or indirectly. */ + nghttp2_mem_free(mem, origin->ov); +} + size_t nghttp2_frame_priority_len(uint8_t flags) { if (flags & NGHTTP2_FLAG_PRIORITY) { return NGHTTP2_PRIORITY_SPECLEN; @@ -746,6 +776,106 @@ int nghttp2_frame_unpack_altsvc_payload2(nghttp2_extension *frame, return 0; } +int nghttp2_frame_pack_origin(nghttp2_bufs *bufs, nghttp2_extension *frame) { + nghttp2_buf *buf; + nghttp2_ext_origin *origin; + nghttp2_origin_entry *orig; + size_t i; + + origin = frame->payload; + + buf = &bufs->head->buf; + + if (nghttp2_buf_avail(buf) < frame->hd.length) { + return NGHTTP2_ERR_FRAME_SIZE_ERROR; + } + + buf->pos -= NGHTTP2_FRAME_HDLEN; + + nghttp2_frame_pack_frame_hd(buf->pos, &frame->hd); + + for (i = 0; i < origin->nov; ++i) { + orig = &origin->ov[i]; + nghttp2_put_uint16be(buf->last, (uint16_t)orig->origin_len); + buf->last += 2; + buf->last = nghttp2_cpymem(buf->last, orig->origin, orig->origin_len); + } + + assert(nghttp2_buf_len(buf) == NGHTTP2_FRAME_HDLEN + frame->hd.length); + + return 0; +} + +int nghttp2_frame_unpack_origin_payload(nghttp2_extension *frame, + const uint8_t *payload, + size_t payloadlen, nghttp2_mem *mem) { + nghttp2_ext_origin *origin; + const uint8_t *p, *end; + uint8_t *dst; + size_t originlen; + nghttp2_origin_entry *ov; + size_t nov = 0; + size_t len = 0; + + origin = frame->payload; + p = payload; + end = p + payloadlen; + + for (; p != end;) { + if (end - p < 2) { + return NGHTTP2_ERR_FRAME_SIZE_ERROR; + } + originlen = nghttp2_get_uint16(p); + p += 2; + if (originlen == 0) { + continue; + } + if (originlen > (size_t)(end - p)) { + return NGHTTP2_ERR_FRAME_SIZE_ERROR; + } + p += originlen; + /* 1 for terminal NULL */ + len += originlen + 1; + ++nov; + } + + if (nov == 0) { + origin->ov = NULL; + origin->nov = 0; + + return 0; + } + + len += nov * sizeof(nghttp2_origin_entry); + + ov = nghttp2_mem_malloc(mem, len); + if (ov == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + origin->ov = ov; + origin->nov = nov; + + dst = (uint8_t *)ov + nov * sizeof(nghttp2_origin_entry); + p = payload; + + for (; p != end;) { + originlen = nghttp2_get_uint16(p); + p += 2; + if (originlen == 0) { + continue; + } + ov->origin = dst; + ov->origin_len = originlen; + dst = nghttp2_cpymem(dst, p, originlen); + *dst++ = '\0'; + p += originlen; + ++ov; + } + + return 0; +} + nghttp2_settings_entry *nghttp2_frame_iv_copy(const nghttp2_settings_entry *iv, size_t niv, nghttp2_mem *mem) { nghttp2_settings_entry *iv_copy; diff --git a/lib/nghttp2_frame.h b/lib/nghttp2_frame.h index 35ca214a..cf04eec4 100644 --- a/lib/nghttp2_frame.h +++ b/lib/nghttp2_frame.h @@ -72,6 +72,7 @@ /* Union of extension frame payload */ typedef union { nghttp2_ext_altsvc altsvc; + nghttp2_ext_origin origin; } nghttp2_ext_frame_payload; void nghttp2_frame_pack_frame_hd(uint8_t *buf, const nghttp2_frame_hd *hd); @@ -392,6 +393,36 @@ int nghttp2_frame_unpack_altsvc_payload2(nghttp2_extension *frame, const uint8_t *payload, size_t payloadlen, nghttp2_mem *mem); +/* + * Packs ORIGIN frame |frame| in wire frame format and store it in + * |bufs|. + * + * 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_FRAME_SIZE_ERROR + * The length of the frame is too large. + */ +int nghttp2_frame_pack_origin(nghttp2_bufs *bufs, nghttp2_extension *ext); + +/* + * Unpacks ORIGIN wire format into |frame|. The |payload| of length + * |payloadlen| contains the frame payload. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + * NGHTTP2_ERR_FRAME_SIZE_ERROR + * The payload is too small. + */ +int nghttp2_frame_unpack_origin_payload(nghttp2_extension *frame, + const uint8_t *payload, + size_t payloadlen, nghttp2_mem *mem); /* * Initializes HEADERS frame |frame| with given values. |frame| takes * ownership of |nva|, so caller must not free it. If |stream_id| is @@ -489,6 +520,24 @@ void nghttp2_frame_altsvc_init(nghttp2_extension *frame, int32_t stream_id, */ void nghttp2_frame_altsvc_free(nghttp2_extension *frame, nghttp2_mem *mem); +/* + * Initializes ORIGIN frame |frame| with given values. This function + * assumes that frame->payload points to nghttp2_ext_origin object. + * Also |ov| and the memory pointed by the field of its elements are + * allocated in single buffer, starting with |ov|. On success, this + * function takes ownership of |ov|, so caller must not free it. + */ +void nghttp2_frame_origin_init(nghttp2_extension *frame, + nghttp2_origin_entry *ov, size_t nov); + +/* + * Frees up resources under |frame|. This function does not free + * nghttp2_ext_origin object pointed by frame->payload. This function + * only frees nghttp2_ext_origin.ov. Therefore, other fields must be + * allocated in the same buffer with ov. + */ +void nghttp2_frame_origin_free(nghttp2_extension *frame, nghttp2_mem *mem); + /* * Returns the number of padding bytes after payload. The total * padding length is given in the |padlen|. The returned value does diff --git a/lib/nghttp2_option.c b/lib/nghttp2_option.c index aec5dcfa..8946d7dd 100644 --- a/lib/nghttp2_option.c +++ b/lib/nghttp2_option.c @@ -86,6 +86,10 @@ void nghttp2_option_set_builtin_recv_extension_type(nghttp2_option *option, option->opt_set_mask |= NGHTTP2_OPT_BUILTIN_RECV_EXT_TYPES; option->builtin_recv_ext_types |= NGHTTP2_TYPEMASK_ALTSVC; return; + case NGHTTP2_ORIGIN: + option->opt_set_mask |= NGHTTP2_OPT_BUILTIN_RECV_EXT_TYPES; + option->builtin_recv_ext_types |= NGHTTP2_TYPEMASK_ORIGIN; + return; default: return; } diff --git a/lib/nghttp2_outbound_item.c b/lib/nghttp2_outbound_item.c index 1633cc36..f651c802 100644 --- a/lib/nghttp2_outbound_item.c +++ b/lib/nghttp2_outbound_item.c @@ -86,6 +86,9 @@ void nghttp2_outbound_item_free(nghttp2_outbound_item *item, nghttp2_mem *mem) { case NGHTTP2_ALTSVC: nghttp2_frame_altsvc_free(&frame->ext, mem); break; + case NGHTTP2_ORIGIN: + nghttp2_frame_origin_free(&frame->ext, mem); + break; default: assert(0); break; diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c index a9e7a623..540886a9 100644 --- a/lib/nghttp2_session.c +++ b/lib/nghttp2_session.c @@ -348,6 +348,12 @@ static void session_inbound_frame_reset(nghttp2_session *session) { } nghttp2_frame_altsvc_free(&iframe->frame.ext, mem); break; + case NGHTTP2_ORIGIN: + if ((session->builtin_recv_ext_types & NGHTTP2_TYPEMASK_ORIGIN) == 0) { + break; + } + nghttp2_frame_origin_free(&iframe->frame.ext, mem); + break; } } @@ -1749,6 +1755,13 @@ static int session_predicate_altsvc_send(nghttp2_session *session, return 0; } +static int session_predicate_origin_send(nghttp2_session *session) { + if (session_is_closing(session)) { + return NGHTTP2_ERR_SESSION_CLOSING; + } + return 0; +} + /* Take into account settings max frame size and both connection-level flow control here */ static ssize_t @@ -2280,6 +2293,18 @@ static int session_prep_frame(nghttp2_session *session, nghttp2_frame_pack_altsvc(&session->aob.framebufs, &frame->ext); + return 0; + case NGHTTP2_ORIGIN: + rv = session_predicate_origin_send(session); + if (rv != 0) { + return rv; + } + + rv = nghttp2_frame_pack_origin(&session->aob.framebufs, &frame->ext); + if (rv != 0) { + return rv; + } + return 0; default: /* Unreachable here */ @@ -4821,6 +4846,11 @@ int nghttp2_session_on_altsvc_received(nghttp2_session *session, return session_call_on_frame_received(session, frame); } +int nghttp2_session_on_origin_received(nghttp2_session *session, + nghttp2_frame *frame) { + return 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; @@ -4836,6 +4866,25 @@ static int session_process_altsvc_frame(nghttp2_session *session) { return nghttp2_session_on_altsvc_received(session, frame); } +static int session_process_origin_frame(nghttp2_session *session) { + nghttp2_inbound_frame *iframe = &session->iframe; + nghttp2_frame *frame = &iframe->frame; + nghttp2_mem *mem = &session->mem; + int rv; + + rv = nghttp2_frame_unpack_origin_payload(&frame->ext, iframe->lbuf.pos, + nghttp2_buf_len(&iframe->lbuf), mem); + if (rv != 0) { + if (nghttp2_is_fatal(rv)) { + return rv; + } + /* Ignore ORIGIN frame which cannot be parsed. */ + return 0; + } + + return nghttp2_session_on_origin_received(session, frame); +} + static int session_process_extension_frame(nghttp2_session *session) { int rv; nghttp2_inbound_frame *iframe = &session->iframe; @@ -5746,6 +5795,42 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in, iframe->state = NGHTTP2_IB_READ_NBYTE; inbound_frame_set_mark(iframe, 2); + break; + case NGHTTP2_ORIGIN: + if (!(session->builtin_recv_ext_types & NGHTTP2_TYPEMASK_ORIGIN)) { + busy = 1; + iframe->state = NGHTTP2_IB_IGN_PAYLOAD; + break; + } + + DEBUGF("recv: ORIGIN\n"); + + iframe->frame.ext.payload = &iframe->ext_frame_payload.origin; + + if (session->server || iframe->frame.hd.stream_id || + (iframe->frame.hd.flags & 0xf0)) { + busy = 1; + iframe->state = NGHTTP2_IB_IGN_PAYLOAD; + break; + } + + iframe->frame.hd.flags = NGHTTP2_FLAG_NONE; + + if (iframe->payloadleft) { + iframe->raw_lbuf = nghttp2_mem_malloc(mem, iframe->payloadleft); + + if (iframe->raw_lbuf == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + nghttp2_buf_wrap_init(&iframe->lbuf, iframe->raw_lbuf, + iframe->payloadleft); + } else { + busy = 1; + } + + iframe->state = NGHTTP2_IB_READ_ORIGIN_PAYLOAD; + break; default: busy = 1; @@ -6583,7 +6668,6 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in, DEBUGF("recv: [IB_READ_ALTSVC_PAYLOAD]\n"); readlen = inbound_frame_payload_readlen(iframe, in, last); - if (readlen > 0) { iframe->lbuf.last = nghttp2_cpymem(iframe->lbuf.last, in, readlen); @@ -6601,11 +6685,44 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in, } rv = session_process_altsvc_frame(session); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + session_inbound_frame_reset(session); + + break; + case NGHTTP2_IB_READ_ORIGIN_PAYLOAD: + DEBUGF("recv: [IB_READ_ORIGIN_PAYLOAD]\n"); + + readlen = inbound_frame_payload_readlen(iframe, in, last); + + if (readlen > 0) { + iframe->lbuf.last = nghttp2_cpymem(iframe->lbuf.last, in, readlen); + + iframe->payloadleft -= readlen; + in += readlen; + } + + DEBUGF("recv: readlen=%zu, payloadleft=%zu\n", readlen, + iframe->payloadleft); + + if (iframe->payloadleft) { + assert(nghttp2_buf_avail(&iframe->lbuf) > 0); + + break; + } + + rv = session_process_origin_frame(session); if (nghttp2_is_fatal(rv)) { return rv; } + if (iframe->state == NGHTTP2_IB_IGN_ALL) { + return (ssize_t)inlen; + } + session_inbound_frame_reset(session); break; diff --git a/lib/nghttp2_session.h b/lib/nghttp2_session.h index c7cb27d7..19b42f63 100644 --- a/lib/nghttp2_session.h +++ b/lib/nghttp2_session.h @@ -61,7 +61,8 @@ typedef enum { */ typedef enum { NGHTTP2_TYPEMASK_NONE = 0, - NGHTTP2_TYPEMASK_ALTSVC = 1 << 0 + NGHTTP2_TYPEMASK_ALTSVC = 1 << 0, + NGHTTP2_TYPEMASK_ORIGIN = 1 << 1 } nghttp2_typemask; typedef enum { @@ -121,6 +122,7 @@ typedef enum { NGHTTP2_IB_IGN_DATA, NGHTTP2_IB_IGN_ALL, NGHTTP2_IB_READ_ALTSVC_PAYLOAD, + NGHTTP2_IB_READ_ORIGIN_PAYLOAD, NGHTTP2_IB_READ_EXTENSION_PAYLOAD } nghttp2_inbound_state; @@ -749,6 +751,19 @@ int nghttp2_session_on_window_update_received(nghttp2_session *session, int nghttp2_session_on_altsvc_received(nghttp2_session *session, nghttp2_frame *frame); +/* + * Called when ORIGIN is received, assuming |frame| is properly + * initialized. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_CALLBACK_FAILURE + * The callback function failed. + */ +int nghttp2_session_on_origin_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 6c15c824..f604eff5 100644 --- a/lib/nghttp2_submit.c +++ b/lib/nghttp2_submit.c @@ -571,6 +571,89 @@ fail_item_malloc: return rv; } +int nghttp2_submit_origin(nghttp2_session *session, uint8_t flags, + const nghttp2_origin_entry *ov, size_t nov) { + nghttp2_mem *mem; + uint8_t *p; + nghttp2_outbound_item *item; + nghttp2_frame *frame; + nghttp2_ext_origin *origin; + nghttp2_origin_entry *ov_copy; + size_t len = 0; + size_t i; + int rv; + (void)flags; + + mem = &session->mem; + + if (!session->server) { + return NGHTTP2_ERR_INVALID_STATE; + } + + if (nov) { + for (i = 0; i < nov; ++i) { + len += ov[i].origin_len; + } + + if (2 * nov + len > NGHTTP2_MAX_PAYLOADLEN) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + /* The last nov is added for terminal NULL character. */ + ov_copy = + nghttp2_mem_malloc(mem, nov * sizeof(nghttp2_origin_entry) + len + nov); + if (ov_copy == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + p = (uint8_t *)ov_copy + nov * sizeof(nghttp2_origin_entry); + + for (i = 0; i < nov; ++i) { + ov_copy[i].origin = p; + ov_copy[i].origin_len = ov[i].origin_len; + p = nghttp2_cpymem(p, ov[i].origin, ov[i].origin_len); + *p++ = '\0'; + } + + assert((size_t)(p - (uint8_t *)ov_copy) == + nov * sizeof(nghttp2_origin_entry) + len + nov); + } else { + ov_copy = NULL; + } + + item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item)); + if (item == NULL) { + rv = NGHTTP2_ERR_NOMEM; + goto fail_item_malloc; + } + + nghttp2_outbound_item_init(item); + + item->aux_data.ext.builtin = 1; + + origin = &item->ext_frame_payload.origin; + + frame = &item->frame; + frame->ext.payload = origin; + + nghttp2_frame_origin_init(&frame->ext, ov_copy, nov); + + rv = nghttp2_session_add_item(session, item); + if (rv != 0) { + nghttp2_frame_origin_free(&frame->ext, mem); + nghttp2_mem_free(mem, item); + + return rv; + } + + return 0; + +fail_item_malloc: + free(ov_copy); + + return rv; +} + static uint8_t set_request_flags(const nghttp2_priority_spec *pri_spec, const nghttp2_data_provider *data_prd) { uint8_t flags = NGHTTP2_FLAG_NONE; diff --git a/tests/failmalloc_test.c b/tests/failmalloc_test.c index a7ac2f06..d8a229db 100644 --- a/tests/failmalloc_test.c +++ b/tests/failmalloc_test.c @@ -224,6 +224,11 @@ static void run_nghttp2_session_send_server(void) { ssize_t txdatalen; const uint8_t origin[] = "nghttp2.org"; const uint8_t altsvc_field_value[] = "h2=\":443\""; + static const uint8_t nghttp2[] = "https://nghttp2.org"; + static const nghttp2_origin_entry ov = { + (uint8_t *)nghttp2, + sizeof(nghttp2) - 1, + }; rv = nghttp2_session_callbacks_new(&callbacks); if (rv != 0) { @@ -246,6 +251,11 @@ static void run_nghttp2_session_send_server(void) { goto fail; } + rv = nghttp2_submit_origin(session, NGHTTP2_FLAG_NONE, &ov, 1); + if (rv != 0) { + goto fail; + } + txdatalen = nghttp2_session_mem_send(session, &txdata); if (txdatalen < 0) { diff --git a/tests/main.c b/tests/main.c index 9fe0bfdb..4af7d419 100644 --- a/tests/main.c +++ b/tests/main.c @@ -107,6 +107,8 @@ int main() { test_nghttp2_session_recv_extension) || !CU_add_test(pSuite, "session_recv_altsvc", test_nghttp2_session_recv_altsvc) || + !CU_add_test(pSuite, "session_recv_origin", + test_nghttp2_session_recv_origin) || !CU_add_test(pSuite, "session_continue", test_nghttp2_session_continue) || !CU_add_test(pSuite, "session_add_frame", test_nghttp2_session_add_frame) || @@ -206,6 +208,7 @@ int main() { test_nghttp2_submit_invalid_nv) || !CU_add_test(pSuite, "submit_extension", test_nghttp2_submit_extension) || !CU_add_test(pSuite, "submit_altsvc", test_nghttp2_submit_altsvc) || + !CU_add_test(pSuite, "submit_origin", test_nghttp2_submit_origin) || !CU_add_test(pSuite, "session_open_stream", test_nghttp2_session_open_stream) || !CU_add_test(pSuite, "session_open_stream_with_idle_stream_dep", @@ -355,6 +358,8 @@ int main() { test_nghttp2_frame_pack_window_update) || !CU_add_test(pSuite, "frame_pack_altsvc", test_nghttp2_frame_pack_altsvc) || + !CU_add_test(pSuite, "frame_pack_origin", + test_nghttp2_frame_pack_origin) || !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 e1064e03..de409318 100644 --- a/tests/nghttp2_frame_test.c +++ b/tests/nghttp2_frame_test.c @@ -508,6 +508,98 @@ void test_nghttp2_frame_pack_altsvc(void) { nghttp2_bufs_free(&bufs); } +void test_nghttp2_frame_pack_origin(void) { + nghttp2_extension frame, oframe; + nghttp2_ext_origin origin, oorigin; + nghttp2_bufs bufs; + nghttp2_buf *buf; + int rv; + size_t payloadlen; + static const uint8_t example[] = "https://example.com"; + static const uint8_t nghttp2[] = "https://nghttp2.org"; + nghttp2_origin_entry ov[] = { + { + (uint8_t *)example, + sizeof(example) - 1, + }, + { + NULL, + 0, + }, + { + (uint8_t *)nghttp2, + sizeof(nghttp2) - 1, + }, + }; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + frame_pack_bufs_init(&bufs); + + frame.payload = &origin; + oframe.payload = &oorigin; + + nghttp2_frame_origin_init(&frame, ov, 3); + + payloadlen = 2 + sizeof(example) - 1 + 2 + 2 + sizeof(nghttp2) - 1; + + rv = nghttp2_frame_pack_origin(&bufs, &frame); + + CU_ASSERT(0 == rv); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + payloadlen == nghttp2_bufs_len(&bufs)); + + rv = unpack_framebuf((nghttp2_frame *)&oframe, &bufs); + + CU_ASSERT(0 == rv); + + check_frame_header(payloadlen, NGHTTP2_ORIGIN, NGHTTP2_FLAG_NONE, 0, + &oframe.hd); + + CU_ASSERT(2 == oorigin.nov); + CU_ASSERT(sizeof(example) - 1 == oorigin.ov[0].origin_len); + CU_ASSERT(0 == memcmp(example, oorigin.ov[0].origin, sizeof(example) - 1)); + CU_ASSERT(sizeof(nghttp2) - 1 == oorigin.ov[1].origin_len); + CU_ASSERT(0 == memcmp(nghttp2, oorigin.ov[1].origin, sizeof(nghttp2) - 1)); + + nghttp2_frame_origin_free(&oframe, mem); + + /* Check the case where origin length is too large */ + buf = &bufs.head->buf; + nghttp2_put_uint16be(buf->pos + NGHTTP2_FRAME_HDLEN, + (uint16_t)(payloadlen - 1)); + + rv = unpack_framebuf((nghttp2_frame *)&oframe, &bufs); + + CU_ASSERT(NGHTTP2_ERR_FRAME_SIZE_ERROR == rv); + + nghttp2_bufs_reset(&bufs); + memset(&oframe, 0, sizeof(oframe)); + memset(&oorigin, 0, sizeof(oorigin)); + oframe.payload = &oorigin; + + /* Empty ORIGIN frame */ + nghttp2_frame_origin_init(&frame, NULL, 0); + + rv = nghttp2_frame_pack_origin(&bufs, &frame); + + CU_ASSERT(0 == rv); + CU_ASSERT(NGHTTP2_FRAME_HDLEN == nghttp2_bufs_len(&bufs)); + + rv = unpack_framebuf((nghttp2_frame *)&oframe, &bufs); + + CU_ASSERT(0 == rv); + + check_frame_header(0, NGHTTP2_ORIGIN, NGHTTP2_FLAG_NONE, 0, &oframe.hd); + + CU_ASSERT(0 == oorigin.nov); + CU_ASSERT(NULL == oorigin.ov); + + nghttp2_frame_origin_free(&oframe, mem); + + nghttp2_bufs_free(&bufs); +} + void test_nghttp2_nv_array_copy(void) { nghttp2_nv *nva; ssize_t rv; diff --git a/tests/nghttp2_frame_test.h b/tests/nghttp2_frame_test.h index 059b20ba..8c0d6d5b 100644 --- a/tests/nghttp2_frame_test.h +++ b/tests/nghttp2_frame_test.h @@ -39,6 +39,7 @@ 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_frame_pack_origin(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 e54e5afd..a44fe2a6 100644 --- a/tests/nghttp2_session_test.c +++ b/tests/nghttp2_session_test.c @@ -2436,6 +2436,153 @@ void test_nghttp2_session_recv_altsvc(void) { nghttp2_option_del(option); } +void test_nghttp2_session_recv_origin(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + nghttp2_bufs bufs; + ssize_t rv; + nghttp2_option *option; + nghttp2_extension frame; + nghttp2_ext_origin origin; + nghttp2_origin_entry ov; + static const uint8_t nghttp2[] = "https://nghttp2.org"; + + frame_pack_bufs_init(&bufs); + + frame.payload = &origin; + + ov.origin = (uint8_t *)nghttp2; + ov.origin_len = sizeof(nghttp2) - 1; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + + callbacks.on_frame_recv_callback = on_frame_recv_callback; + + nghttp2_option_new(&option); + nghttp2_option_set_builtin_recv_extension_type(option, NGHTTP2_ORIGIN); + + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + nghttp2_frame_origin_init(&frame, &ov, 1); + + rv = nghttp2_frame_pack_origin(&bufs, &frame); + + CU_ASSERT(0 == rv); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv); + CU_ASSERT(1 == ud.frame_recv_cb_called); + CU_ASSERT(NGHTTP2_ORIGIN == ud.recv_frame_hd.type); + CU_ASSERT(NGHTTP2_FLAG_NONE == ud.recv_frame_hd.flags); + CU_ASSERT(0 == ud.recv_frame_hd.stream_id); + + nghttp2_session_del(session); + nghttp2_bufs_reset(&bufs); + + /* The length of origin is larger than payload length. */ + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + nghttp2_frame_origin_init(&frame, &ov, 1); + rv = nghttp2_frame_pack_origin(&bufs, &frame); + + CU_ASSERT(0 == rv); + + nghttp2_put_uint16be(bufs.head->buf.pos + NGHTTP2_FRAME_HDLEN, + (uint16_t)sizeof(nghttp2)); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + nghttp2_session_del(session); + nghttp2_bufs_reset(&bufs); + + /* A frame should be ignored if it is sent to a stream other than + stream 0. */ + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + nghttp2_frame_origin_init(&frame, &ov, 1); + frame.hd.stream_id = 1; + rv = nghttp2_frame_pack_origin(&bufs, &frame); + + CU_ASSERT(0 == rv); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + nghttp2_session_del(session); + nghttp2_bufs_reset(&bufs); + + /* A frame should be ignored if the reserved flag is set */ + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + nghttp2_frame_origin_init(&frame, &ov, 1); + frame.hd.flags = 0xf0; + rv = nghttp2_frame_pack_origin(&bufs, &frame); + + CU_ASSERT(0 == rv); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + nghttp2_session_del(session); + nghttp2_bufs_reset(&bufs); + + /* A frame should be ignored if it is received by a server. */ + nghttp2_session_server_new2(&session, &callbacks, &ud, option); + + nghttp2_frame_origin_init(&frame, &ov, 1); + rv = nghttp2_frame_pack_origin(&bufs, &frame); + + CU_ASSERT(0 == rv); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + nghttp2_session_del(session); + nghttp2_bufs_reset(&bufs); + + /* Receiving empty ORIGIN frame */ + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + nghttp2_frame_origin_init(&frame, NULL, 0); + rv = nghttp2_frame_pack_origin(&bufs, &frame); + + CU_ASSERT(0 == rv); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv); + CU_ASSERT(1 == ud.frame_recv_cb_called); + CU_ASSERT(NGHTTP2_ORIGIN == ud.recv_frame_hd.type); + + nghttp2_session_del(session); + + nghttp2_option_del(option); + nghttp2_bufs_free(&bufs); +} + void test_nghttp2_session_continue(void) { nghttp2_session *session; nghttp2_session_callbacks callbacks; @@ -6036,6 +6183,95 @@ void test_nghttp2_submit_altsvc(void) { nghttp2_session_del(session); } +void test_nghttp2_submit_origin(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + int rv; + ssize_t len; + const uint8_t *data; + static const uint8_t nghttp2[] = "https://nghttp2.org"; + static const uint8_t examples[] = "https://examples.com"; + static const nghttp2_origin_entry ov[] = { + { + (uint8_t *)nghttp2, + sizeof(nghttp2) - 1, + }, + { + (uint8_t *)examples, + sizeof(examples) - 1, + }, + }; + nghttp2_frame frame; + nghttp2_ext_origin origin; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_frame_send_callback = on_frame_send_callback; + + frame.ext.payload = &origin; + + nghttp2_session_server_new(&session, &callbacks, &ud); + + rv = nghttp2_submit_origin(session, NGHTTP2_FLAG_NONE, ov, 2); + + CU_ASSERT(0 == rv); + + ud.frame_send_cb_called = 0; + len = nghttp2_session_mem_send(session, &data); + + CU_ASSERT(len > 0); + CU_ASSERT(1 == ud.frame_send_cb_called); + + nghttp2_frame_unpack_frame_hd(&frame.hd, data); + rv = nghttp2_frame_unpack_origin_payload( + &frame.ext, data + NGHTTP2_FRAME_HDLEN, (size_t)len - NGHTTP2_FRAME_HDLEN, + mem); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == frame.hd.stream_id); + CU_ASSERT(NGHTTP2_ORIGIN == frame.hd.type); + CU_ASSERT(2 == origin.nov); + CU_ASSERT(0 == memcmp(nghttp2, origin.ov[0].origin, sizeof(nghttp2) - 1)); + CU_ASSERT(sizeof(nghttp2) - 1 == origin.ov[0].origin_len); + CU_ASSERT(0 == memcmp(examples, origin.ov[1].origin, sizeof(examples) - 1)); + CU_ASSERT(sizeof(examples) - 1 == origin.ov[1].origin_len); + + nghttp2_frame_origin_free(&frame.ext, mem); + + nghttp2_session_del(session); + + /* Submitting ORIGIN frame from client session is error */ + nghttp2_session_client_new(&session, &callbacks, NULL); + + rv = nghttp2_submit_origin(session, NGHTTP2_FLAG_NONE, ov, 1); + + CU_ASSERT(NGHTTP2_ERR_INVALID_STATE == rv); + + nghttp2_session_del(session); + + /* Submitting empty ORIGIN frame */ + nghttp2_session_server_new(&session, &callbacks, &ud); + + rv = nghttp2_submit_origin(session, NGHTTP2_FLAG_NONE, NULL, 0); + + CU_ASSERT(0 == rv); + + ud.frame_send_cb_called = 0; + len = nghttp2_session_mem_send(session, &data); + + CU_ASSERT(len == NGHTTP2_FRAME_HDLEN); + CU_ASSERT(1 == ud.frame_send_cb_called); + + nghttp2_frame_unpack_frame_hd(&frame.hd, data); + + CU_ASSERT(NGHTTP2_ORIGIN == frame.hd.type); + + nghttp2_session_del(session); +} + void test_nghttp2_session_open_stream(void) { nghttp2_session *session; nghttp2_session_callbacks callbacks; diff --git a/tests/nghttp2_session_test.h b/tests/nghttp2_session_test.h index 9af6ea99..52d990c0 100644 --- a/tests/nghttp2_session_test.h +++ b/tests/nghttp2_session_test.h @@ -47,6 +47,7 @@ void test_nghttp2_session_recv_settings_header_table_size(void); void test_nghttp2_session_recv_too_large_frame_length(void); void test_nghttp2_session_recv_extension(void); void test_nghttp2_session_recv_altsvc(void); +void test_nghttp2_session_recv_origin(void); void test_nghttp2_session_continue(void); void test_nghttp2_session_add_frame(void); void test_nghttp2_session_on_request_headers_received(void); @@ -100,6 +101,7 @@ void test_nghttp2_submit_shutdown_notice(void); void test_nghttp2_submit_invalid_nv(void); void test_nghttp2_submit_extension(void); void test_nghttp2_submit_altsvc(void); +void test_nghttp2_submit_origin(void); void test_nghttp2_session_open_stream(void); void test_nghttp2_session_open_stream_with_idle_stream_dep(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 b8869f5c..d741d5ef 100644 --- a/tests/nghttp2_test_helper.c +++ b/tests/nghttp2_test_helper.c @@ -84,6 +84,10 @@ int unpack_frame(nghttp2_frame *frame, const uint8_t *in, size_t len) { assert(payloadlen > 2); nghttp2_frame_unpack_altsvc_payload2(&frame->ext, payload, payloadlen, mem); break; + case NGHTTP2_ORIGIN: + rv = nghttp2_frame_unpack_origin_payload(&frame->ext, payload, payloadlen, + mem); + break; default: /* Must not be reachable */ assert(0);