diff --git a/lib/nghttp2_frame.c b/lib/nghttp2_frame.c index 18e45d3a..326233fe 100644 --- a/lib/nghttp2_frame.c +++ b/lib/nghttp2_frame.c @@ -196,26 +196,6 @@ void nghttp2_frame_extension_init(nghttp2_extension *frame, uint8_t type, void nghttp2_frame_extension_free(nghttp2_extension *frame) { (void)frame; } -void nghttp2_frame_origin_init(nghttp2_extension *frame, uint8_t *origin, - size_t origin_len) { - nghttp2_ext_origin *origin_frame; - - /* Always send ORIGIN frame on stream 0 */ - nghttp2_frame_hd_init(&frame->hd, 2 + origin_len, NGHTTP2_ORIGIN, - NGHTTP2_FLAG_NONE, 0); - - origin_frame = frame->payload; - origin_frame->origin = origin; - origin_frame->origin_len = origin_len; -} - -void nghttp2_frame_origin_free(nghttp2_extension *frame, nghttp2_mem *mem) { - nghttp2_ext_origin *origin_frame; - - origin_frame = frame->payload; - nghttp2_mem_free(mem, origin_frame->origin); -} - void nghttp2_frame_altsvc_init(nghttp2_extension *frame, int32_t stream_id, uint8_t *origin, size_t origin_len, uint8_t *field_value, size_t field_value_len) { @@ -248,6 +228,26 @@ size_t nghttp2_frame_priority_len(uint8_t flags) { return 0; } +void nghttp2_frame_origin_init(nghttp2_extension *frame, uint8_t *origin, + size_t origin_len) { + nghttp2_ext_origin *origin_frame; + + /* Always send ORIGIN frame on stream 0 */ + nghttp2_frame_hd_init(&frame->hd, 2 + origin_len, NGHTTP2_ORIGIN, + NGHTTP2_FLAG_NONE, 0); + + origin_frame = frame->payload; + origin_frame->origin = origin; + origin_frame->origin_len = origin_len; +} + +void nghttp2_frame_origin_free(nghttp2_extension *frame, nghttp2_mem *mem) { + nghttp2_ext_origin *origin_frame; + + origin_frame = frame->payload; + nghttp2_mem_free(mem, origin_frame->origin); +} + size_t nghttp2_frame_headers_payload_nv_offset(nghttp2_headers *frame) { return nghttp2_frame_priority_len(frame->hd.flags); } 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 77bd17b5..090c85a2 100644 --- a/lib/nghttp2_session.c +++ b/lib/nghttp2_session.c @@ -4898,7 +4898,7 @@ static int session_process_origin_frame(nghttp2_session *session) { &frame->ext, nghttp2_get_uint16(iframe->sbuf.pos), iframe->lbuf.pos, nghttp2_buf_len(&iframe->lbuf)); - /* nghttp2_frame_unpack_altsvc_payload steals buffer from + /* nghttp2_frame_unpack_origin_payload steals buffer from iframe->lbuf */ nghttp2_buf_wrap_init(&iframe->lbuf, NULL, 0); diff --git a/lib/nghttp2_submit.c b/lib/nghttp2_submit.c index 3510b2e0..b8699d34 100644 --- a/lib/nghttp2_submit.c +++ b/lib/nghttp2_submit.c @@ -486,76 +486,6 @@ int nghttp2_session_set_local_window_size(nghttp2_session *session, return 0; } -int nghttp2_submit_origin(nghttp2_session *session, uint8_t flags, - const uint8_t *origin, size_t origin_len) { - nghttp2_mem *mem; - uint8_t *buf, *p; - uint8_t *origin_copy; - nghttp2_outbound_item *item; - nghttp2_frame *frame; - nghttp2_ext_origin *origin_frame; - int rv; - (void)flags; - - mem = &session->mem; - - if (!session->server) { - return NGHTTP2_ERR_INVALID_STATE; - } - - if (2 + origin_len > NGHTTP2_MAX_PAYLOADLEN) { - return NGHTTP2_ERR_INVALID_ARGUMENT; - } - - buf = nghttp2_mem_malloc(mem, origin_len + 2); - if (buf == NULL) { - return NGHTTP2_ERR_NOMEM; - } - - p = buf; - - origin_copy = p; - if (origin_len) { - p = nghttp2_cpymem(p, origin, origin_len); - } - *p++ = '\0'; - - 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_frame = &item->ext_frame_payload.origin; - - frame = &item->frame; - frame->ext.payload = origin_frame; - // - // nghttp2_frame_altsvc_init(&frame->ext, stream_id, origin_copy, origin_len, - // field_value_copy, field_value_len); - - nghttp2_frame_origin_init(&frame->ext, origin_copy, origin_len); - - rv = nghttp2_session_add_item(session, item); - if (rv != 0) { - nghttp2_frame_altsvc_free(&frame->ext, mem); - nghttp2_mem_free(mem, item); - - return rv; - } - - return 0; - -fail_item_malloc: - free(buf); - - return rv; -} - int nghttp2_submit_altsvc(nghttp2_session *session, uint8_t flags, int32_t stream_id, const uint8_t *origin, size_t origin_len, const uint8_t *field_value, @@ -641,6 +571,73 @@ fail_item_malloc: return rv; } +int nghttp2_submit_origin(nghttp2_session *session, uint8_t flags, + const uint8_t *origin, size_t origin_len) { + nghttp2_mem *mem; + uint8_t *buf, *p; + uint8_t *origin_copy; + nghttp2_outbound_item *item; + nghttp2_frame *frame; + nghttp2_ext_origin *origin_frame; + int rv; + (void)flags; + + mem = &session->mem; + + if (!session->server) { + return NGHTTP2_ERR_INVALID_STATE; + } + + if (2 + origin_len > NGHTTP2_MAX_PAYLOADLEN) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + buf = nghttp2_mem_malloc(mem, origin_len + 2); + if (buf == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + p = buf; + + origin_copy = p; + if (origin_len) { + p = nghttp2_cpymem(p, origin, origin_len); + } + *p++ = '\0'; + + 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_frame = &item->ext_frame_payload.origin; + + frame = &item->frame; + frame->ext.payload = origin_frame; + + nghttp2_frame_origin_init(&frame->ext, origin_copy, origin_len); + + rv = nghttp2_session_add_item(session, item); + if (rv != 0) { + nghttp2_frame_altsvc_free(&frame->ext, mem); + nghttp2_mem_free(mem, item); + + return rv; + } + + return 0; + +fail_item_malloc: + free(buf); + + 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/src/app_helper.cc b/src/app_helper.cc index a736d308..c5880a8e 100644 --- a/src/app_helper.cc +++ b/src/app_helper.cc @@ -106,6 +106,8 @@ std::string strframetype(uint8_t type) { return "WINDOW_UPDATE"; case NGHTTP2_ALTSVC: return "ALTSVC"; + case NGHTTP2_ORIGIN: + return "ORIGIN"; } std::string s = "extension(0x"; @@ -350,10 +352,16 @@ void print_frame(print_type ptype, const nghttp2_frame *frame) { static_cast(altsvc->field_value_len), altsvc->field_value); break; } + case NGHTTP2_ORIGIN: { + auto origin_frame = static_cast(frame->ext.payload); + print_frame_attr_indent(); + fprintf(outfile, "(origin=[%.*s])\n", + static_cast(origin_frame->origin_len), origin_frame->origin); + break; + } default: break; } -} } // namespace int verbose_on_header_callback(nghttp2_session *session, diff --git a/tests/main.c b/tests/main.c index a922da6e..7ae4ebfa 100644 --- a/tests/main.c +++ b/tests/main.c @@ -105,6 +105,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) || @@ -136,6 +138,8 @@ int main() { test_nghttp2_session_on_data_received_fail_fast) || !CU_add_test(pSuite, "session_on_altsvc_received", test_nghttp2_session_on_altsvc_received) || + !CU_add_test(pSuite, "session_on_origin_received", + test_nghttp2_session_on_origin_received) || !CU_add_test(pSuite, "session_send_headers_start_stream", test_nghttp2_session_send_headers_start_stream) || !CU_add_test(pSuite, "session_send_headers_reply", @@ -204,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", @@ -353,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..931c91be 100644 --- a/tests/nghttp2_frame_test.c +++ b/tests/nghttp2_frame_test.c @@ -508,6 +508,53 @@ 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_frame, oorigin_frame; + nghttp2_bufs bufs; + int rv; + size_t payloadlen; + static const uint8_t origin[] = "www.nghttp2.org"; + nghttp2_buf buf; + uint8_t *rawbuf; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + frame_pack_bufs_init(&bufs); + + frame.payload = &origin_frame; + oframe.payload = &oorigin_frame; + + rawbuf = nghttp2_mem_malloc(mem, 32); + nghttp2_buf_wrap_init(&buf, rawbuf, 32); + + buf.last = nghttp2_cpymem(buf.last, origin, sizeof(origin) - 1); + + nghttp2_frame_origin_init(&frame, buf.pos, sizeof(origin) - 1); + + payloadlen = 2 + sizeof(origin) - 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(sizeof(origin) - 1 == oorigin_frame.origin_len); + CU_ASSERT(0 == memcmp(origin, oorigin_frame.origin, sizeof(origin) - 1)); + + nghttp2_frame_origin_free(&oframe, mem); + nghttp2_frame_origin_free(&frame, mem); + nghttp2_bufs_free(&bufs); +} + void test_nghttp2_nv_array_copy(void) { nghttp2_nv *nva; ssize_t rv; diff --git a/tests/nghttp2_session_test.c b/tests/nghttp2_session_test.c index fc57e982..5ed6bacd 100644 --- a/tests/nghttp2_session_test.c +++ b/tests/nghttp2_session_test.c @@ -2321,6 +2321,121 @@ 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_buf buf; + nghttp2_frame_hd hd; + nghttp2_mem *mem; + ssize_t rv; + nghttp2_option *option; + static const uint8_t origin[] = "www.nghttp2.org"; + + mem = nghttp2_mem_default(); + + nghttp2_buf_init2(&buf, NGHTTP2_FRAME_HDLEN + NGHTTP2_MAX_FRAME_SIZE_MIN, + mem); + + 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_hd_init(&hd, 2 + sizeof(origin) - 1, NGHTTP2_ORIGIN, + NGHTTP2_FLAG_NONE, 0); + nghttp2_frame_pack_frame_hd(buf.last, &hd); + buf.last += NGHTTP2_FRAME_HDLEN; + nghttp2_put_uint16be(buf.last, sizeof(origin) - 1); + buf.last += 2; + buf.last = nghttp2_cpymem(buf.last, origin, sizeof(origin) - 1); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, buf.pos, nghttp2_buf_len(&buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&buf) == 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); + + /* size of origin is larger than frame length */ + nghttp2_buf_reset(&buf); + + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + nghttp2_frame_hd_init(&hd, 2 + sizeof(origin) - 1 - 1, NGHTTP2_ORIGIN, + NGHTTP2_FLAG_NONE, 0); + nghttp2_frame_pack_frame_hd(buf.last, &hd); + buf.last += NGHTTP2_FRAME_HDLEN; + nghttp2_put_uint16be(buf.last, sizeof(origin) - 1); + buf.last += 2; + buf.last = nghttp2_cpymem(buf.last, origin, sizeof(origin) - 1 - 1); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, buf.pos, nghttp2_buf_len(&buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&buf) == rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + nghttp2_session_del(session); + + /* send large frame (16KiB) */ + nghttp2_buf_reset(&buf); + + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + nghttp2_frame_hd_init(&hd, NGHTTP2_MAX_FRAME_SIZE_MIN, NGHTTP2_ORIGIN, + NGHTTP2_FLAG_NONE, 0); + nghttp2_frame_pack_frame_hd(buf.last, &hd); + buf.last += NGHTTP2_FRAME_HDLEN; + nghttp2_put_uint16be(buf.last, sizeof(origin) - 1); + buf.last += 2; + buf.last = nghttp2_cpymem(buf.last, origin, sizeof(origin) - 1); + memset(buf.last, 0, nghttp2_buf_avail(&buf)); + buf.last += nghttp2_buf_avail(&buf); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, buf.pos, nghttp2_buf_len(&buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&buf) == rv); + CU_ASSERT(1 == ud.frame_recv_cb_called); + CU_ASSERT(NGHTTP2_ORIGIN == ud.recv_frame_hd.type); + CU_ASSERT(NGHTTP2_MAX_FRAME_SIZE_MIN == ud.recv_frame_hd.length); + + nghttp2_session_del(session); + + /* received by server */ + nghttp2_buf_reset(&buf); + + nghttp2_session_server_new2(&session, &callbacks, &ud, option); + + nghttp2_frame_hd_init(&hd, 2 + sizeof(origin) - 1, NGHTTP2_ORIGIN, + NGHTTP2_FLAG_NONE, 0); + nghttp2_frame_pack_frame_hd(buf.last, &hd); + buf.last += NGHTTP2_FRAME_HDLEN; + nghttp2_put_uint16be(buf.last, sizeof(origin) - 1); + buf.last += 2; + buf.last = nghttp2_cpymem(buf.last, origin, sizeof(origin) - 1); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, buf.pos, nghttp2_buf_len(&buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&buf) == rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + nghttp2_session_del(session); + + nghttp2_buf_free(&buf, mem); + nghttp2_option_del(option); +} + void test_nghttp2_session_continue(void) { nghttp2_session *session; nghttp2_session_callbacks callbacks; @@ -3834,6 +3949,55 @@ void test_nghttp2_session_on_altsvc_received(void) { nghttp2_option_del(option); } +void test_nghttp2_session_on_origin_received(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + nghttp2_frame frame; + nghttp2_option *option; + uint8_t origin[] = "www.nghttp2.org"; + int rv; + + 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); + + frame.ext.payload = &session->iframe.ext_frame_payload; + + /* We just pass the strings without making a copy. This is OK, + since we never call nghttp2_frame_origin_free(). */ + nghttp2_frame_origin_init(&frame.ext, origin, sizeof(origin) - 1); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_on_origin_received(session, &frame); + + CU_ASSERT(0 == rv); + CU_ASSERT(1 == ud.frame_recv_cb_called); + + nghttp2_session_del(session); + + /* Receiving empty origin; this is OK */ + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + frame.ext.payload = &session->iframe.ext_frame_payload; + + nghttp2_frame_origin_init(&frame.ext, origin, 0); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_on_origin_received(session, &frame); + + CU_ASSERT(0 == rv); + CU_ASSERT(1 == ud.frame_recv_cb_called); + + nghttp2_session_del(session); + + nghttp2_option_del(option); +} + void test_nghttp2_session_send_headers_start_stream(void) { nghttp2_session *session; nghttp2_session_callbacks callbacks; @@ -5922,6 +6086,55 @@ 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; + nghttp2_frame_hd hd; + size_t origin_len; + const uint8_t origin[] = "www.nghttp2.org"; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + + nghttp2_session_server_new(&session, &callbacks, &ud); + + rv = nghttp2_submit_origin(session, NGHTTP2_FLAG_NONE, origin, + sizeof(origin) - 1); + + CU_ASSERT(0 == rv); + + ud.frame_send_cb_called = 0; + + len = nghttp2_session_mem_send(session, &data); + + CU_ASSERT(len == NGHTTP2_FRAME_HDLEN + 2 + sizeof(origin) - 1); + + nghttp2_frame_unpack_frame_hd(&hd, data); + + CU_ASSERT(2 + sizeof(origin) - 1 == hd.length); + CU_ASSERT(NGHTTP2_ORIGIN == hd.type); + CU_ASSERT(NGHTTP2_FLAG_NONE == hd.flags); + + origin_len = nghttp2_get_uint16(data + NGHTTP2_FRAME_HDLEN); + + CU_ASSERT(sizeof(origin) - 1 == origin_len); + CU_ASSERT(0 == + memcmp(origin, data + NGHTTP2_FRAME_HDLEN + 2, sizeof(origin) - 1)); + + /* submitting from client side session is error */ + nghttp2_session_client_new(&session, &callbacks, NULL); + + rv = nghttp2_submit_origin(session, NGHTTP2_FLAG_NONE, origin, + sizeof(origin) - 1); + + CU_ASSERT(NGHTTP2_ERR_INVALID_STATE == rv); + + nghttp2_session_del(session); +} + void test_nghttp2_session_open_stream(void) { nghttp2_session *session; nghttp2_session_callbacks callbacks; diff --git a/tests/nghttp2_test_helper.c b/tests/nghttp2_test_helper.c index b8869f5c..2c22aaab 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: + assert(payloadlen > 2); + nghttp2_frame_unpack_origin_payload2(&frame->ext, payload, payloadlen, mem); + break; default: /* Must not be reachable */ assert(0);