diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h index 1c74b35c..0b4e1553 100644 --- a/lib/includes/nghttp2/nghttp2.h +++ b/lib/includes/nghttp2/nghttp2.h @@ -597,7 +597,12 @@ typedef enum { * The ALTSVC frame, which is defined in `RFC 7383 * `_. */ - NGHTTP2_ALTSVC = 0x0a + NGHTTP2_ALTSVC = 0x0a, + /** + * The ORIGIN frame, whic is defined in `RFC XXXX + * https://tools.ietf.org/html/draft-ietf-httpbis-origin-frame-03#section-2`_. + */ + NGHTTP2_ORIGIN = 0x0c } nghttp2_frame_type; /** @@ -4477,6 +4482,66 @@ NGHTTP2_EXTERN int nghttp2_submit_altsvc(nghttp2_session *session, const uint8_t *field_value, size_t field_value_len); +/** + * @struct + * + * The payload of ORIGIN frame. ORIGIN frame is a non-critical + * extension to HTTP/2. 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 pointer to an origin which the sender believes this connection is or + * could be + * authoritative for. This is not necessarily NULL-terminated. + */ + uint8_t *origin; + /** + * The length of the |origin|. + */ + size_t origin_len; +} nghttp2_ext_origin; + +/** + * @function + * + * Submits ORIGIN frame. + * + * ORIGIN frame is a non-critical extension to HTTP/2, and defined in + * `RFC XXX + * `_. + * + * The |flags| is currently ignored and should be + * :enum:`NGHTTP2_FLAG_NONE`. + * + * The |origin| points to the origin which the sender believes this connection + * is or could be + * authoritative for + * + * The ORIGIN frame is only usable from server side. If this function + * is invoked with client side session, this function returns + * :enum:`NGHTTP2_ERR_INVALID_STATE`. + * + * 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 called from client side session + * :enum:`NGHTTP2_ERR_INVALID_ARGUMENT` + * The |origin_len| is larger than + * 16382. + */ +NGHTTP2_EXTERN int nghttp2_submit_origin(nghttp2_session *session, + uint8_t flags, const uint8_t *origin, + size_t origin_len); + /** * @function * diff --git a/lib/nghttp2_frame.c b/lib/nghttp2_frame.c index 210df058..2e213287 100644 --- a/lib/nghttp2_frame.c +++ b/lib/nghttp2_frame.c @@ -196,6 +196,26 @@ 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) { diff --git a/lib/nghttp2_frame.h b/lib/nghttp2_frame.h index 891289f6..d15d3890 100644 --- a/lib/nghttp2_frame.h +++ b/lib/nghttp2_frame.h @@ -70,7 +70,10 @@ #define NGHTTP2_MAX_PADLEN 256 /* Union of extension frame payload */ -typedef union { nghttp2_ext_altsvc altsvc; } nghttp2_ext_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); @@ -487,6 +490,22 @@ 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. + * On success, this function takes ownership of + * |origin|, so caller must not free it. + */ +void nghttp2_frame_origin_init(nghttp2_extension *frame, uint8_t *origin, + size_t origin_len); + +/* + * Frees up resources under |frame|. This function does not free + * nghttp2_ext_origin object pointed by frame->payload. This function + * only frees origin pointed by nghttp2_ext_origin.origin. + */ +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_session.c b/lib/nghttp2_session.c index 4bc94cbb..cc12c31a 100644 --- a/lib/nghttp2_session.c +++ b/lib/nghttp2_session.c @@ -337,6 +337,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; } } @@ -5746,7 +5752,37 @@ 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); + case NGHTTP2_ORIGIN: + if ((session->builtin_recv_ext_types & NGHTTP2_TYPEMASK_ORIGIN) == + 0) { + busy = 1; + iframe->state = NGHTTP2_IB_IGN_PAYLOAD; + break; + } + DEBUGF("recv: ORIGIN\n"); + + iframe->frame.hd.flags = NGHTTP2_FLAG_NONE; + iframe->frame.ext.payload = &iframe->ext_frame_payload.origin; + + if (session->server) { + busy = 1; + iframe->state = NGHTTP2_IB_IGN_PAYLOAD; + break; + } + + if (iframe->payloadleft < 2) { + busy = 1; + iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR; + break; + } + + busy = 1; + + iframe->state = NGHTTP2_IB_READ_NBYTE; + inbound_frame_set_mark(iframe, 2); + + break; break; default: busy = 1; diff --git a/lib/nghttp2_session.h b/lib/nghttp2_session.h index 3e4c1440..99f3de4c 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 = 2 << 0 } nghttp2_typemask; typedef enum { diff --git a/lib/nghttp2_submit.c b/lib/nghttp2_submit.c index 6c15c824..3510b2e0 100644 --- a/lib/nghttp2_submit.c +++ b/lib/nghttp2_submit.c @@ -486,6 +486,76 @@ 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,