Implement ORIGIN frame

This commit is contained in:
Tatsuhiro Tsujikawa 2018-05-10 10:57:02 +09:00
parent 2e6593e5a5
commit 8034221525
16 changed files with 834 additions and 3 deletions

View File

@ -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 \

View File

@ -611,7 +611,12 @@ typedef enum {
* The ALTSVC frame, which is defined in `RFC 7383
* <https://tools.ietf.org/html/rfc7838#section-4>`_.
*/
NGHTTP2_ALTSVC = 0x0a
NGHTTP2_ALTSVC = 0x0a,
/**
* The ORIGIN frame, which is defined by `RFC 8336
* <https://tools.ietf.org/html/rfc8336>`_.
*/
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
* <https://tools.ietf.org/html/rfc8336>`_.
*
* 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 <https://tools.ietf.org/html/rfc8336>`_.
*
* 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
*

View File

@ -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;

View File

@ -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

View File

@ -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;
}

View File

@ -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;

View File

@ -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;

View File

@ -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.

View File

@ -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;

View File

@ -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) {

View File

@ -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) ||

View File

@ -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;

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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);