From c10a55588b5c6e3997a3a5d93f9fd82adb71f841 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 12 Jun 2022 15:10:51 +0900 Subject: [PATCH] Implement RFC 9218 extensible prioritization scheme This commit implements RFC 9218 extensible prioritization scheme. It is enabled when a local endpoint submits SETTINGS_NO_RFC7540_PRIORITIES = 1. This commit only handles priority signal in HTTP request header field. Priority header field in PUSH_PROMISE is not supported. HTTP messaging must be enabled to take advantage of this prioritization scheme because HTTP fields are not parsed if HTTP messaging is disabled. --- genlibtokenlookup.py | 1 + lib/CMakeLists.txt | 1 + lib/Makefile.am | 2 + lib/includes/nghttp2/nghttp2.h | 58 ++- lib/nghttp2_extpri.c | 35 ++ lib/nghttp2_extpri.h | 65 +++ lib/nghttp2_hd.c | 5 + lib/nghttp2_hd.h | 1 + lib/nghttp2_http.c | 725 +++++++++++++++++++++++++++++++++ lib/nghttp2_http.h | 51 +++ lib/nghttp2_session.c | 186 +++++++-- lib/nghttp2_session.h | 9 +- lib/nghttp2_stream.c | 2 + lib/nghttp2_stream.h | 6 + tests/CMakeLists.txt | 2 + tests/Makefile.am | 8 +- tests/main.c | 12 +- tests/nghttp2_extpri_test.c | 50 +++ tests/nghttp2_extpri_test.h | 35 ++ tests/nghttp2_http_test.c | 655 +++++++++++++++++++++++++++++ tests/nghttp2_http_test.h | 37 ++ tests/nghttp2_session_test.c | 65 ++- tests/nghttp2_session_test.h | 1 + 23 files changed, 1974 insertions(+), 38 deletions(-) create mode 100644 lib/nghttp2_extpri.c create mode 100644 lib/nghttp2_extpri.h create mode 100644 tests/nghttp2_extpri_test.c create mode 100644 tests/nghttp2_extpri_test.h create mode 100644 tests/nghttp2_http_test.c create mode 100644 tests/nghttp2_http_test.h diff --git a/genlibtokenlookup.py b/genlibtokenlookup.py index 12a8533e..d15d0b50 100755 --- a/genlibtokenlookup.py +++ b/genlibtokenlookup.py @@ -68,6 +68,7 @@ HEADERS = [ ('proxy-connection', None), ('upgrade', None), (':protocol', None), + ('priority', None), ] def to_enum_hd(k): diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 4dc2fcdb..fb24fa18 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -23,6 +23,7 @@ set(NGHTTP2_SOURCES nghttp2_mem.c nghttp2_http.c nghttp2_rcbuf.c + nghttp2_extpri.c nghttp2_debug.c ) diff --git a/lib/Makefile.am b/lib/Makefile.am index 1e1f2484..9a985bf7 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -50,6 +50,7 @@ OBJECTS = nghttp2_pq.c nghttp2_map.c nghttp2_queue.c \ nghttp2_mem.c \ nghttp2_http.c \ nghttp2_rcbuf.c \ + nghttp2_extpri.c \ nghttp2_debug.c HFILES = nghttp2_pq.h nghttp2_int.h nghttp2_map.h nghttp2_queue.h \ @@ -66,6 +67,7 @@ HFILES = nghttp2_pq.h nghttp2_int.h nghttp2_map.h nghttp2_queue.h \ nghttp2_mem.h \ nghttp2_http.h \ nghttp2_rcbuf.h \ + nghttp2_extpri.h \ nghttp2_debug.h libnghttp2_la_SOURCES = $(HFILES) $(OBJECTS) diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h index 57c0a945..ec625b9c 100644 --- a/lib/includes/nghttp2/nghttp2.h +++ b/lib/includes/nghttp2/nghttp2.h @@ -705,8 +705,7 @@ typedef enum { */ NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL = 0x08, /** - * SETTINGS_NO_RFC7540_PRIORITIES (`RFC 9218 - * `_) + * SETTINGS_NO_RFC7540_PRIORITIES (:rfc:`9218`) */ NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES = 0x09 } nghttp2_settings_id; @@ -4233,6 +4232,61 @@ nghttp2_submit_priority(nghttp2_session *session, uint8_t flags, int32_t stream_id, const nghttp2_priority_spec *pri_spec); +/** + * @macro + * + * :macro:`NGHTTP2_EXTPRI_DEFAULT_URGENCY` is the default urgency + * level for :rfc:`9218` extensible priorities. + */ +#define NGHTTP2_EXTPRI_DEFAULT_URGENCY 3 + +/** + * @macro + * + * :macro:`NGHTTP2_EXTPRI_URGENCY_HIGH` is the highest urgency level + * for :rfc:`9218` extensible priorities. + */ +#define NGHTTP2_EXTPRI_URGENCY_HIGH 0 + +/** + * @macro + * + * :macro:`NGHTTP2_EXTPRI_URGENCY_LOW` is the lowest urgency level for + * :rfc:`9218` extensible priorities. + */ +#define NGHTTP2_EXTPRI_URGENCY_LOW 7 + +/** + * @macro + * + * :macro:`NGHTTP2_EXTPRI_URGENCY_LEVELS` is the number of urgency + * levels for :rfc:`9218` extensible priorities. + */ +#define NGHTTP2_EXTPRI_URGENCY_LEVELS (NGHTTP2_EXTPRI_URGENCY_LOW + 1) + +/** + * @struct + * + * :type:`nghttp2_extpri` is :rfc:`9218` extensible priorities + * specification for a stream. + */ +typedef struct nghttp2_extpri { + /** + * :member:`urgency` is the urgency of a stream, it must be in + * [:macro:`NGHTTP2_EXTPRI_URGENCY_HIGH`, + * :macro:`NGHTTP2_EXTPRI_URGENCY_LOW`], inclusive, and 0 is the + * highest urgency. + */ + uint32_t urgency; + /** + * :member:`inc` indicates that a content can be processed + * incrementally or not. If inc is 0, it cannot be processed + * incrementally. If inc is 1, it can be processed incrementally. + * Other value is not permitted. + */ + int inc; +} nghttp2_extpri; + /** * @function * diff --git a/lib/nghttp2_extpri.c b/lib/nghttp2_extpri.c new file mode 100644 index 00000000..3fd9b781 --- /dev/null +++ b/lib/nghttp2_extpri.c @@ -0,0 +1,35 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2022 nghttp3 contributors + * Copyright (c) 2022 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_extpri.h" + +uint8_t nghttp2_extpri_to_uint8(const nghttp2_extpri *extpri) { + return (uint8_t)((uint32_t)extpri->inc << 7 | extpri->urgency); +} + +void nghttp2_extpri_from_uint8(nghttp2_extpri *extpri, uint8_t u8extpri) { + extpri->urgency = nghttp2_extpri_uint8_urgency(u8extpri); + extpri->inc = nghttp2_extpri_uint8_inc(u8extpri); +} diff --git a/lib/nghttp2_extpri.h b/lib/nghttp2_extpri.h new file mode 100644 index 00000000..23c6ddc0 --- /dev/null +++ b/lib/nghttp2_extpri.h @@ -0,0 +1,65 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2022 nghttp3 contributors + * Copyright (c) 2022 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_EXTPRI_H +#define NGHTTP2_EXTPRI_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include + +/* + * NGHTTP2_EXTPRI_INC_MASK is a bit mask to retrieve incremental bit + * from a value produced by nghttp2_extpri_to_uint8. + */ +#define NGHTTP2_EXTPRI_INC_MASK (1 << 7) + +/* + * nghttp2_extpri_to_uint8 encodes |pri| into uint8_t variable. + */ +uint8_t nghttp2_extpri_to_uint8(const nghttp2_extpri *extpri); + +/* + * nghttp2_extpri_from_uint8 decodes |u8extpri|, which is produced by + * nghttp2_extpri_to_uint8, intto |extpri|. + */ +void nghttp2_extpri_from_uint8(nghttp2_extpri *extpri, uint8_t u8extpri); + +/* + * nghttp2_extpri_uint8_urgency extracts urgency from |PRI| which is + * supposed to be constructed by nghttp2_extpri_to_uint8. + */ +#define nghttp2_extpri_uint8_urgency(PRI) \ + ((uint32_t)((PRI) & ~NGHTTP2_EXTPRI_INC_MASK)) + +/* + * nghttp2_extpri_uint8_inc extracts inc from |PRI| which is supposed to + * be constructed by nghttp2_extpri_to_uint8. + */ +#define nghttp2_extpri_uint8_inc(PRI) (((PRI)&NGHTTP2_EXTPRI_INC_MASK) != 0) + +#endif /* NGHTTP2_EXTPRI_H */ diff --git a/lib/nghttp2_hd.c b/lib/nghttp2_hd.c index 30ee9b88..8a2bda64 100644 --- a/lib/nghttp2_hd.c +++ b/lib/nghttp2_hd.c @@ -269,6 +269,11 @@ static int32_t lookup_token(const uint8_t *name, size_t namelen) { return NGHTTP2_TOKEN_LOCATION; } break; + case 'y': + if (memeq("priorit", name, 7)) { + return NGHTTP2_TOKEN_PRIORITY; + } + break; } break; case 9: diff --git a/lib/nghttp2_hd.h b/lib/nghttp2_hd.h index 26740288..6de0052a 100644 --- a/lib/nghttp2_hd.h +++ b/lib/nghttp2_hd.h @@ -112,6 +112,7 @@ typedef enum { NGHTTP2_TOKEN_PROXY_CONNECTION, NGHTTP2_TOKEN_UPGRADE, NGHTTP2_TOKEN__PROTOCOL, + NGHTTP2_TOKEN_PRIORITY, } nghttp2_token; struct nghttp2_hd_entry; diff --git a/lib/nghttp2_http.c b/lib/nghttp2_http.c index 01062244..81f5b6e9 100644 --- a/lib/nghttp2_http.c +++ b/lib/nghttp2_http.c @@ -30,6 +30,7 @@ #include "nghttp2_hd.h" #include "nghttp2_helper.h" +#include "nghttp2_extpri.h" static uint8_t downcase(uint8_t c) { return 'A' <= c && c <= 'Z' ? (uint8_t)(c - 'A' + 'a') : c; @@ -114,6 +115,8 @@ static int check_path(nghttp2_stream *stream) { static int http_request_on_header(nghttp2_stream *stream, nghttp2_hd_nv *nv, int trailer, int connect_protocol) { + nghttp2_extpri extpri; + if (nv->name->base[0] == ':') { if (trailer || (stream->http_flags & NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED)) { @@ -212,6 +215,16 @@ static int http_request_on_header(nghttp2_stream *stream, nghttp2_hd_nv *nv, return NGHTTP2_ERR_HTTP_HEADER; } break; + case NGHTTP2_TOKEN_PRIORITY: + if (!trailer && + (stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES)) { + nghttp2_extpri_from_uint8(&extpri, stream->http_extpri); + if (nghttp2_http_parse_priority(&extpri, nv->value->base, + nv->value->len) == 0) { + stream->http_extpri = nghttp2_extpri_to_uint8(&extpri); + } + } + break; default: if (nv->name->base[0] == ':') { return NGHTTP2_ERR_HTTP_HEADER; @@ -541,3 +554,715 @@ void nghttp2_http_record_request_method(nghttp2_stream *stream, return; } } + +/* Generated by genchartbl.py */ +static const int SF_KEY_CHARS[] = { + 0 /* NUL */, 0 /* SOH */, 0 /* STX */, 0 /* ETX */, 0 /* EOT */, + 0 /* ENQ */, 0 /* ACK */, 0 /* BEL */, 0 /* BS */, 0 /* HT */, + 0 /* LF */, 0 /* VT */, 0 /* FF */, 0 /* CR */, 0 /* SO */, + 0 /* SI */, 0 /* DLE */, 0 /* DC1 */, 0 /* DC2 */, 0 /* DC3 */, + 0 /* DC4 */, 0 /* NAK */, 0 /* SYN */, 0 /* ETB */, 0 /* CAN */, + 0 /* EM */, 0 /* SUB */, 0 /* ESC */, 0 /* FS */, 0 /* GS */, + 0 /* RS */, 0 /* US */, 0 /* SPC */, 0 /* ! */, 0 /* " */, + 0 /* # */, 0 /* $ */, 0 /* % */, 0 /* & */, 0 /* ' */, + 0 /* ( */, 0 /* ) */, 1 /* * */, 0 /* + */, 0 /* , */, + 1 /* - */, 1 /* . */, 0 /* / */, 1 /* 0 */, 1 /* 1 */, + 1 /* 2 */, 1 /* 3 */, 1 /* 4 */, 1 /* 5 */, 1 /* 6 */, + 1 /* 7 */, 1 /* 8 */, 1 /* 9 */, 0 /* : */, 0 /* ; */, + 0 /* < */, 0 /* = */, 0 /* > */, 0 /* ? */, 0 /* @ */, + 0 /* A */, 0 /* B */, 0 /* C */, 0 /* D */, 0 /* E */, + 0 /* F */, 0 /* G */, 0 /* H */, 0 /* I */, 0 /* J */, + 0 /* K */, 0 /* L */, 0 /* M */, 0 /* N */, 0 /* O */, + 0 /* P */, 0 /* Q */, 0 /* R */, 0 /* S */, 0 /* T */, + 0 /* U */, 0 /* V */, 0 /* W */, 0 /* X */, 0 /* Y */, + 0 /* Z */, 0 /* [ */, 0 /* \ */, 0 /* ] */, 0 /* ^ */, + 1 /* _ */, 0 /* ` */, 1 /* a */, 1 /* b */, 1 /* c */, + 1 /* d */, 1 /* e */, 1 /* f */, 1 /* g */, 1 /* h */, + 1 /* i */, 1 /* j */, 1 /* k */, 1 /* l */, 1 /* m */, + 1 /* n */, 1 /* o */, 1 /* p */, 1 /* q */, 1 /* r */, + 1 /* s */, 1 /* t */, 1 /* u */, 1 /* v */, 1 /* w */, + 1 /* x */, 1 /* y */, 1 /* z */, 0 /* { */, 0 /* | */, + 0 /* } */, 0 /* ~ */, 0 /* DEL */, 0 /* 0x80 */, 0 /* 0x81 */, + 0 /* 0x82 */, 0 /* 0x83 */, 0 /* 0x84 */, 0 /* 0x85 */, 0 /* 0x86 */, + 0 /* 0x87 */, 0 /* 0x88 */, 0 /* 0x89 */, 0 /* 0x8a */, 0 /* 0x8b */, + 0 /* 0x8c */, 0 /* 0x8d */, 0 /* 0x8e */, 0 /* 0x8f */, 0 /* 0x90 */, + 0 /* 0x91 */, 0 /* 0x92 */, 0 /* 0x93 */, 0 /* 0x94 */, 0 /* 0x95 */, + 0 /* 0x96 */, 0 /* 0x97 */, 0 /* 0x98 */, 0 /* 0x99 */, 0 /* 0x9a */, + 0 /* 0x9b */, 0 /* 0x9c */, 0 /* 0x9d */, 0 /* 0x9e */, 0 /* 0x9f */, + 0 /* 0xa0 */, 0 /* 0xa1 */, 0 /* 0xa2 */, 0 /* 0xa3 */, 0 /* 0xa4 */, + 0 /* 0xa5 */, 0 /* 0xa6 */, 0 /* 0xa7 */, 0 /* 0xa8 */, 0 /* 0xa9 */, + 0 /* 0xaa */, 0 /* 0xab */, 0 /* 0xac */, 0 /* 0xad */, 0 /* 0xae */, + 0 /* 0xaf */, 0 /* 0xb0 */, 0 /* 0xb1 */, 0 /* 0xb2 */, 0 /* 0xb3 */, + 0 /* 0xb4 */, 0 /* 0xb5 */, 0 /* 0xb6 */, 0 /* 0xb7 */, 0 /* 0xb8 */, + 0 /* 0xb9 */, 0 /* 0xba */, 0 /* 0xbb */, 0 /* 0xbc */, 0 /* 0xbd */, + 0 /* 0xbe */, 0 /* 0xbf */, 0 /* 0xc0 */, 0 /* 0xc1 */, 0 /* 0xc2 */, + 0 /* 0xc3 */, 0 /* 0xc4 */, 0 /* 0xc5 */, 0 /* 0xc6 */, 0 /* 0xc7 */, + 0 /* 0xc8 */, 0 /* 0xc9 */, 0 /* 0xca */, 0 /* 0xcb */, 0 /* 0xcc */, + 0 /* 0xcd */, 0 /* 0xce */, 0 /* 0xcf */, 0 /* 0xd0 */, 0 /* 0xd1 */, + 0 /* 0xd2 */, 0 /* 0xd3 */, 0 /* 0xd4 */, 0 /* 0xd5 */, 0 /* 0xd6 */, + 0 /* 0xd7 */, 0 /* 0xd8 */, 0 /* 0xd9 */, 0 /* 0xda */, 0 /* 0xdb */, + 0 /* 0xdc */, 0 /* 0xdd */, 0 /* 0xde */, 0 /* 0xdf */, 0 /* 0xe0 */, + 0 /* 0xe1 */, 0 /* 0xe2 */, 0 /* 0xe3 */, 0 /* 0xe4 */, 0 /* 0xe5 */, + 0 /* 0xe6 */, 0 /* 0xe7 */, 0 /* 0xe8 */, 0 /* 0xe9 */, 0 /* 0xea */, + 0 /* 0xeb */, 0 /* 0xec */, 0 /* 0xed */, 0 /* 0xee */, 0 /* 0xef */, + 0 /* 0xf0 */, 0 /* 0xf1 */, 0 /* 0xf2 */, 0 /* 0xf3 */, 0 /* 0xf4 */, + 0 /* 0xf5 */, 0 /* 0xf6 */, 0 /* 0xf7 */, 0 /* 0xf8 */, 0 /* 0xf9 */, + 0 /* 0xfa */, 0 /* 0xfb */, 0 /* 0xfc */, 0 /* 0xfd */, 0 /* 0xfe */, + 0 /* 0xff */, +}; + +static ssize_t sf_parse_key(const uint8_t *begin, const uint8_t *end) { + const uint8_t *p = begin; + + if ((*p < 'a' || 'z' < *p) && *p != '*') { + return -1; + } + + for (; p != end && SF_KEY_CHARS[*p]; ++p) + ; + + return p - begin; +} + +static ssize_t sf_parse_integer_or_decimal(nghttp2_sf_value *dest, + const uint8_t *begin, + const uint8_t *end) { + const uint8_t *p = begin; + int sign = 1; + int64_t value = 0; + int type = NGHTTP2_SF_VALUE_TYPE_INTEGER; + size_t len = 0; + size_t fpos = 0; + size_t i; + + if (*p == '-') { + if (++p == end) { + return -1; + } + + sign = -1; + } + + if (*p < '0' || '9' < *p) { + return -1; + } + + for (; p != end; ++p) { + switch (*p) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + value *= 10; + value += *p - '0'; + + if (++len > 15) { + return -1; + } + + break; + case '.': + if (type != NGHTTP2_SF_VALUE_TYPE_INTEGER) { + goto fin; + } + + if (len > 12) { + return -1; + } + fpos = len; + type = NGHTTP2_SF_VALUE_TYPE_DECIMAL; + + break; + default: + goto fin; + }; + } + +fin: + switch (type) { + case NGHTTP2_SF_VALUE_TYPE_INTEGER: + if (dest) { + dest->type = (uint8_t)type; + dest->i = value * sign; + } + + return p - begin; + case NGHTTP2_SF_VALUE_TYPE_DECIMAL: + if (fpos == len || len - fpos > 3) { + return -1; + } + + if (dest) { + dest->type = (uint8_t)type; + dest->d = (double)value; + for (i = len - fpos; i > 0; --i) { + dest->d /= (double)10; + } + dest->d *= sign; + } + + return p - begin; + default: + assert(0); + abort(); + } +} + +/* Generated by genchartbl.py */ +static const int SF_DQUOTE_CHARS[] = { + 0 /* NUL */, 0 /* SOH */, 0 /* STX */, 0 /* ETX */, 0 /* EOT */, + 0 /* ENQ */, 0 /* ACK */, 0 /* BEL */, 0 /* BS */, 0 /* HT */, + 0 /* LF */, 0 /* VT */, 0 /* FF */, 0 /* CR */, 0 /* SO */, + 0 /* SI */, 0 /* DLE */, 0 /* DC1 */, 0 /* DC2 */, 0 /* DC3 */, + 0 /* DC4 */, 0 /* NAK */, 0 /* SYN */, 0 /* ETB */, 0 /* CAN */, + 0 /* EM */, 0 /* SUB */, 0 /* ESC */, 0 /* FS */, 0 /* GS */, + 0 /* RS */, 0 /* US */, 1 /* SPC */, 1 /* ! */, 0 /* " */, + 1 /* # */, 1 /* $ */, 1 /* % */, 1 /* & */, 1 /* ' */, + 1 /* ( */, 1 /* ) */, 1 /* * */, 1 /* + */, 1 /* , */, + 1 /* - */, 1 /* . */, 1 /* / */, 1 /* 0 */, 1 /* 1 */, + 1 /* 2 */, 1 /* 3 */, 1 /* 4 */, 1 /* 5 */, 1 /* 6 */, + 1 /* 7 */, 1 /* 8 */, 1 /* 9 */, 1 /* : */, 1 /* ; */, + 1 /* < */, 1 /* = */, 1 /* > */, 1 /* ? */, 1 /* @ */, + 1 /* A */, 1 /* B */, 1 /* C */, 1 /* D */, 1 /* E */, + 1 /* F */, 1 /* G */, 1 /* H */, 1 /* I */, 1 /* J */, + 1 /* K */, 1 /* L */, 1 /* M */, 1 /* N */, 1 /* O */, + 1 /* P */, 1 /* Q */, 1 /* R */, 1 /* S */, 1 /* T */, + 1 /* U */, 1 /* V */, 1 /* W */, 1 /* X */, 1 /* Y */, + 1 /* Z */, 1 /* [ */, 0 /* \ */, 1 /* ] */, 1 /* ^ */, + 1 /* _ */, 1 /* ` */, 1 /* a */, 1 /* b */, 1 /* c */, + 1 /* d */, 1 /* e */, 1 /* f */, 1 /* g */, 1 /* h */, + 1 /* i */, 1 /* j */, 1 /* k */, 1 /* l */, 1 /* m */, + 1 /* n */, 1 /* o */, 1 /* p */, 1 /* q */, 1 /* r */, + 1 /* s */, 1 /* t */, 1 /* u */, 1 /* v */, 1 /* w */, + 1 /* x */, 1 /* y */, 1 /* z */, 1 /* { */, 1 /* | */, + 1 /* } */, 1 /* ~ */, 0 /* DEL */, 0 /* 0x80 */, 0 /* 0x81 */, + 0 /* 0x82 */, 0 /* 0x83 */, 0 /* 0x84 */, 0 /* 0x85 */, 0 /* 0x86 */, + 0 /* 0x87 */, 0 /* 0x88 */, 0 /* 0x89 */, 0 /* 0x8a */, 0 /* 0x8b */, + 0 /* 0x8c */, 0 /* 0x8d */, 0 /* 0x8e */, 0 /* 0x8f */, 0 /* 0x90 */, + 0 /* 0x91 */, 0 /* 0x92 */, 0 /* 0x93 */, 0 /* 0x94 */, 0 /* 0x95 */, + 0 /* 0x96 */, 0 /* 0x97 */, 0 /* 0x98 */, 0 /* 0x99 */, 0 /* 0x9a */, + 0 /* 0x9b */, 0 /* 0x9c */, 0 /* 0x9d */, 0 /* 0x9e */, 0 /* 0x9f */, + 0 /* 0xa0 */, 0 /* 0xa1 */, 0 /* 0xa2 */, 0 /* 0xa3 */, 0 /* 0xa4 */, + 0 /* 0xa5 */, 0 /* 0xa6 */, 0 /* 0xa7 */, 0 /* 0xa8 */, 0 /* 0xa9 */, + 0 /* 0xaa */, 0 /* 0xab */, 0 /* 0xac */, 0 /* 0xad */, 0 /* 0xae */, + 0 /* 0xaf */, 0 /* 0xb0 */, 0 /* 0xb1 */, 0 /* 0xb2 */, 0 /* 0xb3 */, + 0 /* 0xb4 */, 0 /* 0xb5 */, 0 /* 0xb6 */, 0 /* 0xb7 */, 0 /* 0xb8 */, + 0 /* 0xb9 */, 0 /* 0xba */, 0 /* 0xbb */, 0 /* 0xbc */, 0 /* 0xbd */, + 0 /* 0xbe */, 0 /* 0xbf */, 0 /* 0xc0 */, 0 /* 0xc1 */, 0 /* 0xc2 */, + 0 /* 0xc3 */, 0 /* 0xc4 */, 0 /* 0xc5 */, 0 /* 0xc6 */, 0 /* 0xc7 */, + 0 /* 0xc8 */, 0 /* 0xc9 */, 0 /* 0xca */, 0 /* 0xcb */, 0 /* 0xcc */, + 0 /* 0xcd */, 0 /* 0xce */, 0 /* 0xcf */, 0 /* 0xd0 */, 0 /* 0xd1 */, + 0 /* 0xd2 */, 0 /* 0xd3 */, 0 /* 0xd4 */, 0 /* 0xd5 */, 0 /* 0xd6 */, + 0 /* 0xd7 */, 0 /* 0xd8 */, 0 /* 0xd9 */, 0 /* 0xda */, 0 /* 0xdb */, + 0 /* 0xdc */, 0 /* 0xdd */, 0 /* 0xde */, 0 /* 0xdf */, 0 /* 0xe0 */, + 0 /* 0xe1 */, 0 /* 0xe2 */, 0 /* 0xe3 */, 0 /* 0xe4 */, 0 /* 0xe5 */, + 0 /* 0xe6 */, 0 /* 0xe7 */, 0 /* 0xe8 */, 0 /* 0xe9 */, 0 /* 0xea */, + 0 /* 0xeb */, 0 /* 0xec */, 0 /* 0xed */, 0 /* 0xee */, 0 /* 0xef */, + 0 /* 0xf0 */, 0 /* 0xf1 */, 0 /* 0xf2 */, 0 /* 0xf3 */, 0 /* 0xf4 */, + 0 /* 0xf5 */, 0 /* 0xf6 */, 0 /* 0xf7 */, 0 /* 0xf8 */, 0 /* 0xf9 */, + 0 /* 0xfa */, 0 /* 0xfb */, 0 /* 0xfc */, 0 /* 0xfd */, 0 /* 0xfe */, + 0 /* 0xff */, +}; + +static ssize_t sf_parse_string(nghttp2_sf_value *dest, const uint8_t *begin, + const uint8_t *end) { + const uint8_t *p = begin; + + if (*p++ != '"') { + return -1; + } + + for (; p != end; ++p) { + switch (*p) { + case '\\': + if (++p == end) { + return -1; + } + + switch (*p) { + case '"': + case '\\': + break; + default: + return -1; + } + + break; + case '"': + if (dest) { + dest->type = NGHTTP2_SF_VALUE_TYPE_STRING; + dest->s.base = begin + 1; + dest->s.len = (size_t)(p - dest->s.base); + } + + ++p; + + return p - begin; + default: + if (!SF_DQUOTE_CHARS[*p]) { + return -1; + } + } + } + + return -1; +} + +/* Generated by genchartbl.py */ +static const int SF_TOKEN_CHARS[] = { + 0 /* NUL */, 0 /* SOH */, 0 /* STX */, 0 /* ETX */, 0 /* EOT */, + 0 /* ENQ */, 0 /* ACK */, 0 /* BEL */, 0 /* BS */, 0 /* HT */, + 0 /* LF */, 0 /* VT */, 0 /* FF */, 0 /* CR */, 0 /* SO */, + 0 /* SI */, 0 /* DLE */, 0 /* DC1 */, 0 /* DC2 */, 0 /* DC3 */, + 0 /* DC4 */, 0 /* NAK */, 0 /* SYN */, 0 /* ETB */, 0 /* CAN */, + 0 /* EM */, 0 /* SUB */, 0 /* ESC */, 0 /* FS */, 0 /* GS */, + 0 /* RS */, 0 /* US */, 0 /* SPC */, 1 /* ! */, 0 /* " */, + 1 /* # */, 1 /* $ */, 1 /* % */, 1 /* & */, 1 /* ' */, + 0 /* ( */, 0 /* ) */, 1 /* * */, 1 /* + */, 0 /* , */, + 1 /* - */, 1 /* . */, 1 /* / */, 1 /* 0 */, 1 /* 1 */, + 1 /* 2 */, 1 /* 3 */, 1 /* 4 */, 1 /* 5 */, 1 /* 6 */, + 1 /* 7 */, 1 /* 8 */, 1 /* 9 */, 1 /* : */, 0 /* ; */, + 0 /* < */, 0 /* = */, 0 /* > */, 0 /* ? */, 0 /* @ */, + 1 /* A */, 1 /* B */, 1 /* C */, 1 /* D */, 1 /* E */, + 1 /* F */, 1 /* G */, 1 /* H */, 1 /* I */, 1 /* J */, + 1 /* K */, 1 /* L */, 1 /* M */, 1 /* N */, 1 /* O */, + 1 /* P */, 1 /* Q */, 1 /* R */, 1 /* S */, 1 /* T */, + 1 /* U */, 1 /* V */, 1 /* W */, 1 /* X */, 1 /* Y */, + 1 /* Z */, 0 /* [ */, 0 /* \ */, 0 /* ] */, 1 /* ^ */, + 1 /* _ */, 1 /* ` */, 1 /* a */, 1 /* b */, 1 /* c */, + 1 /* d */, 1 /* e */, 1 /* f */, 1 /* g */, 1 /* h */, + 1 /* i */, 1 /* j */, 1 /* k */, 1 /* l */, 1 /* m */, + 1 /* n */, 1 /* o */, 1 /* p */, 1 /* q */, 1 /* r */, + 1 /* s */, 1 /* t */, 1 /* u */, 1 /* v */, 1 /* w */, + 1 /* x */, 1 /* y */, 1 /* z */, 0 /* { */, 1 /* | */, + 0 /* } */, 1 /* ~ */, 0 /* DEL */, 0 /* 0x80 */, 0 /* 0x81 */, + 0 /* 0x82 */, 0 /* 0x83 */, 0 /* 0x84 */, 0 /* 0x85 */, 0 /* 0x86 */, + 0 /* 0x87 */, 0 /* 0x88 */, 0 /* 0x89 */, 0 /* 0x8a */, 0 /* 0x8b */, + 0 /* 0x8c */, 0 /* 0x8d */, 0 /* 0x8e */, 0 /* 0x8f */, 0 /* 0x90 */, + 0 /* 0x91 */, 0 /* 0x92 */, 0 /* 0x93 */, 0 /* 0x94 */, 0 /* 0x95 */, + 0 /* 0x96 */, 0 /* 0x97 */, 0 /* 0x98 */, 0 /* 0x99 */, 0 /* 0x9a */, + 0 /* 0x9b */, 0 /* 0x9c */, 0 /* 0x9d */, 0 /* 0x9e */, 0 /* 0x9f */, + 0 /* 0xa0 */, 0 /* 0xa1 */, 0 /* 0xa2 */, 0 /* 0xa3 */, 0 /* 0xa4 */, + 0 /* 0xa5 */, 0 /* 0xa6 */, 0 /* 0xa7 */, 0 /* 0xa8 */, 0 /* 0xa9 */, + 0 /* 0xaa */, 0 /* 0xab */, 0 /* 0xac */, 0 /* 0xad */, 0 /* 0xae */, + 0 /* 0xaf */, 0 /* 0xb0 */, 0 /* 0xb1 */, 0 /* 0xb2 */, 0 /* 0xb3 */, + 0 /* 0xb4 */, 0 /* 0xb5 */, 0 /* 0xb6 */, 0 /* 0xb7 */, 0 /* 0xb8 */, + 0 /* 0xb9 */, 0 /* 0xba */, 0 /* 0xbb */, 0 /* 0xbc */, 0 /* 0xbd */, + 0 /* 0xbe */, 0 /* 0xbf */, 0 /* 0xc0 */, 0 /* 0xc1 */, 0 /* 0xc2 */, + 0 /* 0xc3 */, 0 /* 0xc4 */, 0 /* 0xc5 */, 0 /* 0xc6 */, 0 /* 0xc7 */, + 0 /* 0xc8 */, 0 /* 0xc9 */, 0 /* 0xca */, 0 /* 0xcb */, 0 /* 0xcc */, + 0 /* 0xcd */, 0 /* 0xce */, 0 /* 0xcf */, 0 /* 0xd0 */, 0 /* 0xd1 */, + 0 /* 0xd2 */, 0 /* 0xd3 */, 0 /* 0xd4 */, 0 /* 0xd5 */, 0 /* 0xd6 */, + 0 /* 0xd7 */, 0 /* 0xd8 */, 0 /* 0xd9 */, 0 /* 0xda */, 0 /* 0xdb */, + 0 /* 0xdc */, 0 /* 0xdd */, 0 /* 0xde */, 0 /* 0xdf */, 0 /* 0xe0 */, + 0 /* 0xe1 */, 0 /* 0xe2 */, 0 /* 0xe3 */, 0 /* 0xe4 */, 0 /* 0xe5 */, + 0 /* 0xe6 */, 0 /* 0xe7 */, 0 /* 0xe8 */, 0 /* 0xe9 */, 0 /* 0xea */, + 0 /* 0xeb */, 0 /* 0xec */, 0 /* 0xed */, 0 /* 0xee */, 0 /* 0xef */, + 0 /* 0xf0 */, 0 /* 0xf1 */, 0 /* 0xf2 */, 0 /* 0xf3 */, 0 /* 0xf4 */, + 0 /* 0xf5 */, 0 /* 0xf6 */, 0 /* 0xf7 */, 0 /* 0xf8 */, 0 /* 0xf9 */, + 0 /* 0xfa */, 0 /* 0xfb */, 0 /* 0xfc */, 0 /* 0xfd */, 0 /* 0xfe */, + 0 /* 0xff */, +}; + +static ssize_t sf_parse_token(nghttp2_sf_value *dest, const uint8_t *begin, + const uint8_t *end) { + const uint8_t *p = begin; + + if ((*p < 'A' || 'Z' < *p) && (*p < 'a' || 'z' < *p) && *p != '*') { + return -1; + } + + for (; p != end && SF_TOKEN_CHARS[*p]; ++p) + ; + + if (dest) { + dest->type = NGHTTP2_SF_VALUE_TYPE_TOKEN; + dest->s.base = begin; + dest->s.len = (size_t)(p - begin); + } + + return p - begin; +} + +/* Generated by genchartbl.py */ +static const int SF_BYTESEQ_CHARS[] = { + 0 /* NUL */, 0 /* SOH */, 0 /* STX */, 0 /* ETX */, 0 /* EOT */, + 0 /* ENQ */, 0 /* ACK */, 0 /* BEL */, 0 /* BS */, 0 /* HT */, + 0 /* LF */, 0 /* VT */, 0 /* FF */, 0 /* CR */, 0 /* SO */, + 0 /* SI */, 0 /* DLE */, 0 /* DC1 */, 0 /* DC2 */, 0 /* DC3 */, + 0 /* DC4 */, 0 /* NAK */, 0 /* SYN */, 0 /* ETB */, 0 /* CAN */, + 0 /* EM */, 0 /* SUB */, 0 /* ESC */, 0 /* FS */, 0 /* GS */, + 0 /* RS */, 0 /* US */, 0 /* SPC */, 0 /* ! */, 0 /* " */, + 0 /* # */, 0 /* $ */, 0 /* % */, 0 /* & */, 0 /* ' */, + 0 /* ( */, 0 /* ) */, 0 /* * */, 1 /* + */, 0 /* , */, + 0 /* - */, 0 /* . */, 1 /* / */, 1 /* 0 */, 1 /* 1 */, + 1 /* 2 */, 1 /* 3 */, 1 /* 4 */, 1 /* 5 */, 1 /* 6 */, + 1 /* 7 */, 1 /* 8 */, 1 /* 9 */, 0 /* : */, 0 /* ; */, + 0 /* < */, 1 /* = */, 0 /* > */, 0 /* ? */, 0 /* @ */, + 1 /* A */, 1 /* B */, 1 /* C */, 1 /* D */, 1 /* E */, + 1 /* F */, 1 /* G */, 1 /* H */, 1 /* I */, 1 /* J */, + 1 /* K */, 1 /* L */, 1 /* M */, 1 /* N */, 1 /* O */, + 1 /* P */, 1 /* Q */, 1 /* R */, 1 /* S */, 1 /* T */, + 1 /* U */, 1 /* V */, 1 /* W */, 1 /* X */, 1 /* Y */, + 1 /* Z */, 0 /* [ */, 0 /* \ */, 0 /* ] */, 0 /* ^ */, + 0 /* _ */, 0 /* ` */, 1 /* a */, 1 /* b */, 1 /* c */, + 1 /* d */, 1 /* e */, 1 /* f */, 1 /* g */, 1 /* h */, + 1 /* i */, 1 /* j */, 1 /* k */, 1 /* l */, 1 /* m */, + 1 /* n */, 1 /* o */, 1 /* p */, 1 /* q */, 1 /* r */, + 1 /* s */, 1 /* t */, 1 /* u */, 1 /* v */, 1 /* w */, + 1 /* x */, 1 /* y */, 1 /* z */, 0 /* { */, 0 /* | */, + 0 /* } */, 0 /* ~ */, 0 /* DEL */, 0 /* 0x80 */, 0 /* 0x81 */, + 0 /* 0x82 */, 0 /* 0x83 */, 0 /* 0x84 */, 0 /* 0x85 */, 0 /* 0x86 */, + 0 /* 0x87 */, 0 /* 0x88 */, 0 /* 0x89 */, 0 /* 0x8a */, 0 /* 0x8b */, + 0 /* 0x8c */, 0 /* 0x8d */, 0 /* 0x8e */, 0 /* 0x8f */, 0 /* 0x90 */, + 0 /* 0x91 */, 0 /* 0x92 */, 0 /* 0x93 */, 0 /* 0x94 */, 0 /* 0x95 */, + 0 /* 0x96 */, 0 /* 0x97 */, 0 /* 0x98 */, 0 /* 0x99 */, 0 /* 0x9a */, + 0 /* 0x9b */, 0 /* 0x9c */, 0 /* 0x9d */, 0 /* 0x9e */, 0 /* 0x9f */, + 0 /* 0xa0 */, 0 /* 0xa1 */, 0 /* 0xa2 */, 0 /* 0xa3 */, 0 /* 0xa4 */, + 0 /* 0xa5 */, 0 /* 0xa6 */, 0 /* 0xa7 */, 0 /* 0xa8 */, 0 /* 0xa9 */, + 0 /* 0xaa */, 0 /* 0xab */, 0 /* 0xac */, 0 /* 0xad */, 0 /* 0xae */, + 0 /* 0xaf */, 0 /* 0xb0 */, 0 /* 0xb1 */, 0 /* 0xb2 */, 0 /* 0xb3 */, + 0 /* 0xb4 */, 0 /* 0xb5 */, 0 /* 0xb6 */, 0 /* 0xb7 */, 0 /* 0xb8 */, + 0 /* 0xb9 */, 0 /* 0xba */, 0 /* 0xbb */, 0 /* 0xbc */, 0 /* 0xbd */, + 0 /* 0xbe */, 0 /* 0xbf */, 0 /* 0xc0 */, 0 /* 0xc1 */, 0 /* 0xc2 */, + 0 /* 0xc3 */, 0 /* 0xc4 */, 0 /* 0xc5 */, 0 /* 0xc6 */, 0 /* 0xc7 */, + 0 /* 0xc8 */, 0 /* 0xc9 */, 0 /* 0xca */, 0 /* 0xcb */, 0 /* 0xcc */, + 0 /* 0xcd */, 0 /* 0xce */, 0 /* 0xcf */, 0 /* 0xd0 */, 0 /* 0xd1 */, + 0 /* 0xd2 */, 0 /* 0xd3 */, 0 /* 0xd4 */, 0 /* 0xd5 */, 0 /* 0xd6 */, + 0 /* 0xd7 */, 0 /* 0xd8 */, 0 /* 0xd9 */, 0 /* 0xda */, 0 /* 0xdb */, + 0 /* 0xdc */, 0 /* 0xdd */, 0 /* 0xde */, 0 /* 0xdf */, 0 /* 0xe0 */, + 0 /* 0xe1 */, 0 /* 0xe2 */, 0 /* 0xe3 */, 0 /* 0xe4 */, 0 /* 0xe5 */, + 0 /* 0xe6 */, 0 /* 0xe7 */, 0 /* 0xe8 */, 0 /* 0xe9 */, 0 /* 0xea */, + 0 /* 0xeb */, 0 /* 0xec */, 0 /* 0xed */, 0 /* 0xee */, 0 /* 0xef */, + 0 /* 0xf0 */, 0 /* 0xf1 */, 0 /* 0xf2 */, 0 /* 0xf3 */, 0 /* 0xf4 */, + 0 /* 0xf5 */, 0 /* 0xf6 */, 0 /* 0xf7 */, 0 /* 0xf8 */, 0 /* 0xf9 */, + 0 /* 0xfa */, 0 /* 0xfb */, 0 /* 0xfc */, 0 /* 0xfd */, 0 /* 0xfe */, + 0 /* 0xff */, +}; + +static ssize_t sf_parse_byteseq(nghttp2_sf_value *dest, const uint8_t *begin, + const uint8_t *end) { + const uint8_t *p = begin; + + if (*p++ != ':') { + return -1; + } + + for (; p != end; ++p) { + switch (*p) { + case ':': + if (dest) { + dest->type = NGHTTP2_SF_VALUE_TYPE_BYTESEQ; + dest->s.base = begin + 1; + dest->s.len = (size_t)(p - dest->s.base); + } + + ++p; + + return p - begin; + default: + if (!SF_BYTESEQ_CHARS[*p]) { + return -1; + } + } + } + + return -1; +} + +static ssize_t sf_parse_boolean(nghttp2_sf_value *dest, const uint8_t *begin, + const uint8_t *end) { + const uint8_t *p = begin; + int b; + + if (*p++ != '?') { + return -1; + } + + if (p == end) { + return -1; + } + + switch (*p++) { + case '0': + b = 0; + break; + case '1': + b = 1; + break; + default: + return -1; + } + + if (dest) { + dest->type = NGHTTP2_SF_VALUE_TYPE_BOOLEAN; + dest->b = b; + } + + return p - begin; +} + +static ssize_t sf_parse_bare_item(nghttp2_sf_value *dest, const uint8_t *begin, + const uint8_t *end) { + switch (*begin) { + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return sf_parse_integer_or_decimal(dest, begin, end); + case '"': + return sf_parse_string(dest, begin, end); + case '*': + return sf_parse_token(dest, begin, end); + case ':': + return sf_parse_byteseq(dest, begin, end); + case '?': + return sf_parse_boolean(dest, begin, end); + default: + if (('A' <= *begin && *begin <= 'Z') || ('a' <= *begin && *begin <= 'z')) { + return sf_parse_token(dest, begin, end); + } + return -1; + } +} + +#define sf_discard_sp_end_err(BEGIN, END, ERR) \ + for (;; ++(BEGIN)) { \ + if ((BEGIN) == (END)) { \ + return (ERR); \ + } \ + if (*(BEGIN) != ' ') { \ + break; \ + } \ + } + +static ssize_t sf_parse_params(const uint8_t *begin, const uint8_t *end) { + const uint8_t *p = begin; + ssize_t slen; + + for (; p != end && *p == ';';) { + ++p; + + sf_discard_sp_end_err(p, end, -1); + + slen = sf_parse_key(p, end); + if (slen < 0) { + return -1; + } + + p += slen; + + if (p == end || *p != '=') { + /* Boolean true */ + } else if (++p == end) { + return -1; + } else { + slen = sf_parse_bare_item(NULL, p, end); + if (slen < 0) { + return -1; + } + + p += slen; + } + } + + return p - begin; +} + +static ssize_t sf_parse_item(nghttp2_sf_value *dest, const uint8_t *begin, + const uint8_t *end) { + const uint8_t *p = begin; + ssize_t slen; + + slen = sf_parse_bare_item(dest, p, end); + if (slen < 0) { + return -1; + } + + p += slen; + + slen = sf_parse_params(p, end); + if (slen < 0) { + return -1; + } + + p += slen; + + return p - begin; +} + +ssize_t nghttp2_sf_parse_item(nghttp2_sf_value *dest, const uint8_t *begin, + const uint8_t *end) { + return sf_parse_item(dest, begin, end); +} + +static ssize_t sf_parse_inner_list(nghttp2_sf_value *dest, const uint8_t *begin, + const uint8_t *end) { + const uint8_t *p = begin; + ssize_t slen; + + if (*p++ != '(') { + return -1; + } + + for (;;) { + sf_discard_sp_end_err(p, end, -1); + + if (*p == ')') { + ++p; + + slen = sf_parse_params(p, end); + if (slen < 0) { + return -1; + } + + p += slen; + + if (dest) { + dest->type = NGHTTP2_SF_VALUE_TYPE_INNER_LIST; + } + + return p - begin; + } + + slen = sf_parse_item(NULL, p, end); + if (slen < 0) { + return -1; + } + + p += slen; + + if (p == end || (*p != ' ' && *p != ')')) { + return -1; + } + } +} + +ssize_t nghttp2_sf_parse_inner_list(nghttp2_sf_value *dest, + const uint8_t *begin, const uint8_t *end) { + return sf_parse_inner_list(dest, begin, end); +} + +static ssize_t sf_parse_item_or_inner_list(nghttp2_sf_value *dest, + const uint8_t *begin, + const uint8_t *end) { + if (*begin == '(') { + return sf_parse_inner_list(dest, begin, end); + } + + return sf_parse_item(dest, begin, end); +} + +#define sf_discard_ows(BEGIN, END) \ + for (;; ++(BEGIN)) { \ + if ((BEGIN) == (END)) { \ + goto fin; \ + } \ + if (*(BEGIN) != ' ' && *(BEGIN) != '\t') { \ + break; \ + } \ + } + +#define sf_discard_ows_end_err(BEGIN, END, ERR) \ + for (;; ++(BEGIN)) { \ + if ((BEGIN) == (END)) { \ + return (ERR); \ + } \ + if (*(BEGIN) != ' ' && *(BEGIN) != '\t') { \ + break; \ + } \ + } + +int nghttp2_http_parse_priority(nghttp2_extpri *dest, const uint8_t *value, + size_t valuelen) { + const uint8_t *p = value, *end = value + valuelen; + ssize_t slen; + nghttp2_sf_value val; + nghttp2_extpri pri = *dest; + const uint8_t *key; + size_t keylen; + + for (; p != end && *p == ' '; ++p) + ; + + for (; p != end;) { + slen = sf_parse_key(p, end); + if (slen < 0) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + key = p; + keylen = (size_t)slen; + + p += slen; + + if (p == end || *p != '=') { + /* Boolean true */ + val.type = NGHTTP2_SF_VALUE_TYPE_BOOLEAN; + val.b = 1; + + slen = sf_parse_params(p, end); + if (slen < 0) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + } else if (++p == end) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } else { + slen = sf_parse_item_or_inner_list(&val, p, end); + if (slen < 0) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + } + + p += slen; + + if (keylen == 1) { + switch (key[0]) { + case 'i': + if (val.type != NGHTTP2_SF_VALUE_TYPE_BOOLEAN) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + pri.inc = val.b; + + break; + case 'u': + if (val.type != NGHTTP2_SF_VALUE_TYPE_INTEGER || + val.i < NGHTTP2_EXTPRI_URGENCY_HIGH || + NGHTTP2_EXTPRI_URGENCY_LOW < val.i) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + pri.urgency = (uint32_t)val.i; + + break; + } + } + + sf_discard_ows(p, end); + + if (*p++ != ',') { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + sf_discard_ows_end_err(p, end, NGHTTP2_ERR_INVALID_ARGUMENT); + } + +fin: + *dest = pri; + + return 0; +} diff --git a/lib/nghttp2_http.h b/lib/nghttp2_http.h index dd057cdb..0c3a78ee 100644 --- a/lib/nghttp2_http.h +++ b/lib/nghttp2_http.h @@ -94,4 +94,55 @@ int nghttp2_http_on_data_chunk(nghttp2_stream *stream, size_t n); void nghttp2_http_record_request_method(nghttp2_stream *stream, nghttp2_frame *frame); +/* + * RFC 8941 Structured Field Values. + */ +typedef enum nghttp2_sf_value_type { + NGHTTP2_SF_VALUE_TYPE_BOOLEAN, + NGHTTP2_SF_VALUE_TYPE_INTEGER, + NGHTTP2_SF_VALUE_TYPE_DECIMAL, + NGHTTP2_SF_VALUE_TYPE_STRING, + NGHTTP2_SF_VALUE_TYPE_TOKEN, + NGHTTP2_SF_VALUE_TYPE_BYTESEQ, + NGHTTP2_SF_VALUE_TYPE_INNER_LIST, +} nghttp2_sf_value_type; + +/* + * nghttp2_sf_value stores Structured Field Values item. For Inner + * List, only type is set to NGHTTP2_SF_VALUE_TYPE_INNER_LIST. + */ +typedef struct nghttp2_sf_value { + uint8_t type; + union { + int b; + int64_t i; + double d; + struct { + const uint8_t *base; + size_t len; + } s; + }; +} nghttp2_sf_value; + +/* + * nghttp2_sf_parse_item parses the input sequence [|begin|, |end|) + * and stores the parsed an Item in |dest|. It returns the number of + * bytes consumed if it succeeds, or -1. This function is declared + * here for unit tests. + */ +ssize_t nghttp2_sf_parse_item(nghttp2_sf_value *dest, const uint8_t *begin, + const uint8_t *end); + +/* + * nghttp2_sf_parse_inner_list parses the input sequence [|begin|, |end|) + * and stores the parsed an Inner List in |dest|. It returns the number of + * bytes consumed if it succeeds, or -1. This function is declared + * here for unit tests. + */ +ssize_t nghttp2_sf_parse_inner_list(nghttp2_sf_value *dest, + const uint8_t *begin, const uint8_t *end); + +int nghttp2_http_parse_priority(nghttp2_extpri *dest, const uint8_t *value, + size_t valuelen); + #endif /* NGHTTP2_HTTP_H */ diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c index e6725165..27ea0d04 100644 --- a/lib/nghttp2_session.c +++ b/lib/nghttp2_session.c @@ -36,6 +36,7 @@ #include "nghttp2_option.h" #include "nghttp2_http.h" #include "nghttp2_pq.h" +#include "nghttp2_extpri.h" #include "nghttp2_debug.h" /* @@ -399,13 +400,19 @@ static void active_outbound_item_reset(nghttp2_active_outbound_item *aob, aob->state = NGHTTP2_OB_POP_ITEM; } +#define NGHTTP2_STREAM_MAX_CYCLE_GAP ((uint64_t)NGHTTP2_MAX_FRAME_SIZE_MAX) + static int stream_less(const void *lhsx, const void *rhsx) { const nghttp2_stream *lhs, *rhs; lhs = nghttp2_struct_of(lhsx, nghttp2_stream, pq_entry); rhs = nghttp2_struct_of(rhsx, nghttp2_stream, pq_entry); - return lhs->seq < rhs->seq; + if (lhs->cycle == rhs->cycle) { + return lhs->seq < rhs->seq; + } + + return rhs->cycle - lhs->cycle <= NGHTTP2_STREAM_MAX_CYCLE_GAP; } int nghttp2_enable_strict_preface = 1; @@ -418,6 +425,7 @@ static int session_new(nghttp2_session **session_ptr, size_t nbuffer; size_t max_deflate_dynamic_table_size = NGHTTP2_HD_DEFAULT_MAX_DEFLATE_BUFFER_SIZE; + size_t i; if (mem == NULL) { mem = nghttp2_mem_default(); @@ -595,7 +603,9 @@ static int session_new(nghttp2_session **session_ptr, } } - nghttp2_pq_init(&(*session_ptr)->ob_data, stream_less, mem); + for (i = 0; i < NGHTTP2_EXTPRI_URGENCY_LEVELS; ++i) { + nghttp2_pq_init(&(*session_ptr)->sched[i].ob_data, stream_less, mem); + } return 0; @@ -748,6 +758,7 @@ static void inflight_settings_del(nghttp2_inflight_settings *settings, void nghttp2_session_del(nghttp2_session *session) { nghttp2_mem *mem; nghttp2_inflight_settings *settings; + size_t i; if (session == NULL) { return; @@ -761,7 +772,9 @@ void nghttp2_session_del(nghttp2_session *session) { settings = next; } - nghttp2_pq_free(&session->ob_data); + for (i = 0; i < NGHTTP2_EXTPRI_URGENCY_LEVELS; ++i) { + nghttp2_pq_free(&session->sched[i].ob_data); + } nghttp2_stream_free(&session->root); /* Have to free streams first, so that we can check @@ -857,14 +870,40 @@ int nghttp2_session_reprioritize_stream( return 0; } +static uint64_t pq_get_first_cycle(nghttp2_pq *pq) { + nghttp2_stream *stream; + + if (nghttp2_pq_empty(pq)) { + return 0; + } + + stream = nghttp2_struct_of(nghttp2_pq_top(pq), nghttp2_stream, pq_entry); + return stream->cycle; +} + static int session_ob_data_push(nghttp2_session *session, nghttp2_stream *stream) { int rv; + uint32_t urgency; + int inc; + nghttp2_pq *pq; assert(stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES); assert(stream->queued == 0); - rv = nghttp2_pq_push(&session->ob_data, &stream->pq_entry); + urgency = nghttp2_extpri_uint8_urgency(stream->extpri); + inc = nghttp2_extpri_uint8_inc(stream->extpri); + + assert(urgency < NGHTTP2_EXTPRI_URGENCY_LEVELS); + + pq = &session->sched[urgency].ob_data; + + stream->cycle = pq_get_first_cycle(pq); + if (inc) { + stream->cycle += stream->last_writelen; + } + + rv = nghttp2_pq_push(pq, &stream->pq_entry); if (rv != 0) { return rv; } @@ -876,10 +915,16 @@ static int session_ob_data_push(nghttp2_session *session, static int session_ob_data_remove(nghttp2_session *session, nghttp2_stream *stream) { + uint32_t urgency; + assert(stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES); assert(stream->queued == 1); - nghttp2_pq_remove(&session->ob_data, &stream->pq_entry); + urgency = nghttp2_extpri_uint8_urgency(stream->extpri); + + assert(urgency < NGHTTP2_EXTPRI_URGENCY_LEVELS); + + nghttp2_pq_remove(&session->sched[urgency].ob_data, &stream->pq_entry); stream->queued = 0; @@ -955,6 +1000,84 @@ static int session_resume_deferred_stream_item(nghttp2_session *session, return session_ob_data_push(session, stream); } +static nghttp2_outbound_item * +session_sched_get_next_outbound_item(nghttp2_session *session) { + size_t i; + nghttp2_pq_entry *ent; + nghttp2_stream *stream; + + for (i = 0; i < NGHTTP2_EXTPRI_URGENCY_LEVELS; ++i) { + ent = nghttp2_pq_top(&session->sched[i].ob_data); + if (!ent) { + continue; + } + + stream = nghttp2_struct_of(ent, nghttp2_stream, pq_entry); + return stream->item; + } + + return NULL; +} + +static int session_sched_empty(nghttp2_session *session) { + size_t i; + + for (i = 0; i < NGHTTP2_EXTPRI_URGENCY_LEVELS; ++i) { + if (!nghttp2_pq_empty(&session->sched[i].ob_data)) { + return 0; + } + } + + return 1; +} + +static void session_sched_reschedule_stream(nghttp2_session *session, + nghttp2_stream *stream) { + nghttp2_pq *pq; + uint32_t urgency = nghttp2_extpri_uint8_urgency(stream->extpri); + int inc = nghttp2_extpri_uint8_inc(stream->extpri); + uint64_t penalty = (uint64_t)stream->last_writelen; + int rv; + + (void)rv; + + assert(urgency < NGHTTP2_EXTPRI_URGENCY_LEVELS); + + pq = &session->sched[urgency].ob_data; + + if (!inc || nghttp2_pq_size(pq) == 1) { + return; + } + + nghttp2_pq_remove(pq, &stream->pq_entry); + + stream->cycle += penalty; + + rv = nghttp2_pq_push(pq, &stream->pq_entry); + + assert(0 == rv); +} + +static int session_update_stream_priority(nghttp2_session *session, + nghttp2_stream *stream, + uint8_t u8extpri) { + if (stream->extpri == u8extpri) { + return 0; + } + + if (stream->queued) { + session_ob_data_remove(session, stream); + + stream->extpri = u8extpri; + + return session_ob_data_push(session, stream); + } + + stream->extpri = u8extpri; + + return 0; +} + int nghttp2_session_add_item(nghttp2_session *session, nghttp2_outbound_item *item) { /* TODO Return error if stream is not found for the frame requiring @@ -2489,8 +2612,6 @@ static int session_prep_frame(nghttp2_session *session, nghttp2_outbound_item * nghttp2_session_get_next_ob_item(nghttp2_session *session) { nghttp2_outbound_item *item; - nghttp2_pq_entry *ent; - nghttp2_stream *stream; if (nghttp2_outbound_queue_top(&session->ob_urgent)) { return nghttp2_outbound_queue_top(&session->ob_urgent); @@ -2512,13 +2633,7 @@ nghttp2_session_get_next_ob_item(nghttp2_session *session) { return item; } - ent = nghttp2_pq_top(&session->ob_data); - if (!ent) { - return NULL; - } - - stream = nghttp2_struct_of(ent, nghttp2_stream, pq_entry); - return stream->item; + return session_sched_get_next_outbound_item(session); } return NULL; @@ -2527,8 +2642,6 @@ nghttp2_session_get_next_ob_item(nghttp2_session *session) { nghttp2_outbound_item * nghttp2_session_pop_next_ob_item(nghttp2_session *session) { nghttp2_outbound_item *item; - nghttp2_pq_entry *ent; - nghttp2_stream *stream; item = nghttp2_outbound_queue_top(&session->ob_urgent); if (item) { @@ -2559,13 +2672,7 @@ nghttp2_session_pop_next_ob_item(nghttp2_session *session) { return item; } - ent = nghttp2_pq_top(&session->ob_data); - if (!ent) { - return NULL; - } - - stream = nghttp2_struct_of(ent, nghttp2_stream, pq_entry); - return stream->item; + return session_sched_get_next_outbound_item(session); } return NULL; @@ -2675,10 +2782,20 @@ static int session_close_stream_on_goaway(nghttp2_session *session, return 0; } -static void reschedule_stream(nghttp2_stream *stream) { +static void session_reschedule_stream(nghttp2_session *session, + nghttp2_stream *stream) { stream->last_writelen = stream->item->frame.hd.length; - nghttp2_stream_reschedule(stream); + if (!(stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES)) { + nghttp2_stream_reschedule(stream); + return; + } + + if (!session->server) { + return; + } + + session_sched_reschedule_stream(session, stream); } static int session_update_stream_consumed_size(nghttp2_session *session, @@ -3906,6 +4023,19 @@ static int session_end_stream_headers_received(nghttp2_session *session, nghttp2_frame *frame, nghttp2_stream *stream) { int rv; + + assert(frame->hd.type == NGHTTP2_HEADERS); + + if (session->server && session_enforce_http_messaging(session) && + frame->headers.cat == NGHTTP2_HCAT_REQUEST && + (stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES)) { + rv = session_update_stream_priority(session, stream, stream->http_extpri); + if (rv != 0) { + assert(nghttp2_is_fatal(rv)); + return rv; + } + } + if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) { return 0; } @@ -7080,7 +7210,7 @@ int nghttp2_session_want_write(nghttp2_session *session) { return session->aob.item || nghttp2_outbound_queue_top(&session->ob_urgent) || nghttp2_outbound_queue_top(&session->ob_reg) || ((!nghttp2_pq_empty(&session->root.obq) || - !nghttp2_pq_empty(&session->ob_data)) && + !session_sched_empty(session)) && session->remote_window_size > 0) || (nghttp2_outbound_queue_top(&session->ob_syn) && !session_is_outgoing_concurrent_streams_max(session)); @@ -7474,9 +7604,7 @@ int nghttp2_session_pack_data(nghttp2_session *session, nghttp2_bufs *bufs, return rv; } - if (!(stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES)) { - reschedule_stream(stream); - } + session_reschedule_stream(session, stream); if (frame->hd.length == 0 && (data_flags & NGHTTP2_DATA_FLAG_EOF) && (data_flags & NGHTTP2_DATA_FLAG_NO_END_STREAM)) { diff --git a/lib/nghttp2_session.h b/lib/nghttp2_session.h index d72dac2d..0d2352e1 100644 --- a/lib/nghttp2_session.h +++ b/lib/nghttp2_session.h @@ -203,9 +203,12 @@ struct nghttp2_session { response) frame, which are subject to SETTINGS_MAX_CONCURRENT_STREAMS limit. */ nghttp2_outbound_queue ob_syn; - /* Queue for DATA frames which is used when - SETTINGS_NO_RFC7540_PRIORITIES is enabled. */ - nghttp2_pq ob_data; + /* Queues for DATA frames which is used when + SETTINGS_NO_RFC7540_PRIORITIES is enabled. This implements RFC + 9218 extensible prioritization scheme. */ + struct { + nghttp2_pq ob_data; + } sched[NGHTTP2_EXTPRI_URGENCY_LEVELS]; nghttp2_active_outbound_item aob; nghttp2_inbound_frame iframe; nghttp2_hd_deflater hd_deflater; diff --git a/lib/nghttp2_stream.c b/lib/nghttp2_stream.c index c3b55e1d..b3614a0b 100644 --- a/lib/nghttp2_stream.c +++ b/lib/nghttp2_stream.c @@ -100,6 +100,8 @@ void nghttp2_stream_init(nghttp2_stream *stream, int32_t stream_id, stream->descendant_next_seq = 0; stream->seq = 0; stream->last_writelen = 0; + + stream->extpri = stream->http_extpri = NGHTTP2_EXTPRI_DEFAULT_URGENCY; } void nghttp2_stream_free(nghttp2_stream *stream) { diff --git a/lib/nghttp2_stream.h b/lib/nghttp2_stream.h index 88265708..0a11b4cd 100644 --- a/lib/nghttp2_stream.h +++ b/lib/nghttp2_stream.h @@ -220,6 +220,12 @@ struct nghttp2_stream { this stream. The nonzero does not necessarily mean WINDOW_UPDATE is not queued. */ uint8_t window_update_queued; + /* extpri is a stream priority produced by nghttp2_extpri_to_uint8 + used by RFC 9218 extensible priorities. */ + uint8_t extpri; + /* http_extpri is a stream priority received in HTTP request header + fields and produced by nghttp2_extpri_to_uint8. */ + uint8_t http_extpri; }; void nghttp2_stream_init(nghttp2_stream *stream, int32_t stream_id, diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4250ac3a..bcd0d4af 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -21,6 +21,8 @@ if(HAVE_CUNIT) nghttp2_npn_test.c nghttp2_helper_test.c nghttp2_buf_test.c + nghttp2_http_test.c + nghttp2_extpri_test.c ) add_executable(main EXCLUDE_FROM_ALL diff --git a/tests/Makefile.am b/tests/Makefile.am index c3e43925..8806d127 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -40,14 +40,18 @@ OBJECTS = main.c nghttp2_pq_test.c nghttp2_map_test.c nghttp2_queue_test.c \ nghttp2_hd_test.c \ nghttp2_npn_test.c \ nghttp2_helper_test.c \ - nghttp2_buf_test.c + nghttp2_buf_test.c \ + nghttp2_http_test.c \ + nghttp2_extpri_test.c HFILES = nghttp2_pq_test.h nghttp2_map_test.h nghttp2_queue_test.h \ nghttp2_session_test.h \ nghttp2_frame_test.h nghttp2_stream_test.h nghttp2_hd_test.h \ nghttp2_npn_test.h nghttp2_helper_test.h \ nghttp2_test_helper.h \ - nghttp2_buf_test.h + nghttp2_buf_test.h \ + nghttp2_http_test.h \ + nghttp2_extpri_test.h main_SOURCES = $(HFILES) $(OBJECTS) diff --git a/tests/main.c b/tests/main.c index c015f6ae..ccef9cf5 100644 --- a/tests/main.c +++ b/tests/main.c @@ -40,6 +40,8 @@ #include "nghttp2_npn_test.h" #include "nghttp2_helper_test.h" #include "nghttp2_buf_test.h" +#include "nghttp2_http_test.h" +#include "nghttp2_extpri_test.h" extern int nghttp2_enable_strict_preface; @@ -93,6 +95,8 @@ int main(void) { test_nghttp2_session_recv_headers_early_response) || !CU_add_test(pSuite, "session_recv_headers_for_closed_stream", test_nghttp2_session_recv_headers_for_closed_stream) || + !CU_add_test(pSuite, "session_recv_headers_with_extpri", + test_nghttp2_session_recv_headers_with_extpri) || !CU_add_test(pSuite, "session_server_recv_push_response", test_nghttp2_session_server_recv_push_response) || !CU_add_test(pSuite, "session_recv_premature_headers", @@ -427,7 +431,13 @@ int main(void) { !CU_add_test(pSuite, "bufs_advance", test_nghttp2_bufs_advance) || !CU_add_test(pSuite, "bufs_next_present", test_nghttp2_bufs_next_present) || - !CU_add_test(pSuite, "bufs_realloc", test_nghttp2_bufs_realloc)) { + !CU_add_test(pSuite, "bufs_realloc", test_nghttp2_bufs_realloc) || + !CU_add_test(pSuite, "http_parse_priority", + test_nghttp2_http_parse_priority) || + !CU_add_test(pSuite, "sf_parse_item", test_nghttp2_sf_parse_item) || + !CU_add_test(pSuite, "sf_parse_inner_list", + test_nghttp2_sf_parse_inner_list) || + !CU_add_test(pSuite, "extpri_to_uint8", test_nghttp2_extpri_to_uint8)) { CU_cleanup_registry(); return (int)CU_get_error(); } diff --git a/tests/nghttp2_extpri_test.c b/tests/nghttp2_extpri_test.c new file mode 100644 index 00000000..aa912304 --- /dev/null +++ b/tests/nghttp2_extpri_test.c @@ -0,0 +1,50 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2022 nghttp3 contributors + * Copyright (c) 2022 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_extpri_test.h" + +#include + +#include "nghttp2_extpri.h" +#include "nghttp2_test_helper.h" + +void test_nghttp2_extpri_to_uint8(void) { + { + nghttp2_extpri pri = {1, 0}; + CU_ASSERT(1 == nghttp2_extpri_to_uint8(&pri)); + } + { + nghttp2_extpri pri = {1, 1}; + CU_ASSERT((0x80 | 1) == nghttp2_extpri_to_uint8(&pri)); + } + { + nghttp2_extpri pri = {7, 1}; + CU_ASSERT((0x80 | 7) == nghttp2_extpri_to_uint8(&pri)); + } + { + nghttp2_extpri pri = {7, 0}; + CU_ASSERT(7 == nghttp2_extpri_to_uint8(&pri)); + } +} diff --git a/tests/nghttp2_extpri_test.h b/tests/nghttp2_extpri_test.h new file mode 100644 index 00000000..a8a93b92 --- /dev/null +++ b/tests/nghttp2_extpri_test.h @@ -0,0 +1,35 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2022 nghttp3 contributors + * Copyright (c) 2022 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_EXTPRI_TEST_H +#define NGHTTP2_EXTPRI_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +void test_nghttp2_extpri_to_uint8(void); + +#endif /* NGHTTP2_EXTPRI_TEST_H */ diff --git a/tests/nghttp2_http_test.c b/tests/nghttp2_http_test.c new file mode 100644 index 00000000..748157c5 --- /dev/null +++ b/tests/nghttp2_http_test.c @@ -0,0 +1,655 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2022 nghttp3 contributors + * Copyright (c) 2022 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_http_test.h" + +#include + +#include + +#include "nghttp2_http.h" +#include "nghttp2_test_helper.h" + +void test_nghttp2_http_parse_priority(void) { + int rv; + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = ""; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(0 == rv); + CU_ASSERT((uint32_t)-1 == pri.urgency); + CU_ASSERT(-1 == pri.inc); + } + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "u=7,i"; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(0 == rv); + CU_ASSERT((uint32_t)7 == pri.urgency); + CU_ASSERT(1 == pri.inc); + } + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "u=0,i=?0"; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(0 == rv); + CU_ASSERT((uint32_t)0 == pri.urgency); + CU_ASSERT(0 == pri.inc); + } + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "u=3, i"; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(0 == rv); + CU_ASSERT((uint32_t)3 == pri.urgency); + CU_ASSERT(1 == pri.inc); + } + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "u=0, i, i=?0, u=6"; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(0 == rv); + CU_ASSERT((uint32_t)6 == pri.urgency); + CU_ASSERT(0 == pri.inc); + } + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "u=0,"; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + } + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "u=0, "; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + } + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "u="; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + } + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "u"; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + } + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "i=?1"; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(0 == rv); + CU_ASSERT((uint32_t)-1 == pri.urgency); + CU_ASSERT(1 == pri.inc); + } + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "i=?2"; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + } + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "i=?"; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + } + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "i="; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + } + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "u=-1"; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + } + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "u=8"; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + } + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = + "i=?0, u=1, a=(x y z), u=2; i=?0;foo=\",,,\", i=?1;i=?0; u=6"; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(0 == rv); + CU_ASSERT((uint32_t)2 == pri.urgency); + CU_ASSERT(1 == pri.inc); + } + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = {'u', '='}; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v)); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + } +} + +void test_nghttp2_sf_parse_item(void) { + { + nghttp2_sf_value val; + const uint8_t s[] = "?1"; + val.type = 0xff; + + CU_ASSERT(2 == nghttp2_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_BOOLEAN == val.type); + CU_ASSERT(1 == val.b); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = "?1 "; + val.type = 0xff; + + CU_ASSERT(2 == nghttp2_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_BOOLEAN == val.type); + CU_ASSERT(1 == val.b); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = "?1;foo=bar"; + val.type = 0xff; + + CU_ASSERT(10 == nghttp2_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_BOOLEAN == val.type); + CU_ASSERT(1 == val.b); + } + + { + const uint8_t s[] = {'?', '1', ';', 'f', 'o', 'o', '='}; + + CU_ASSERT(-1 == nghttp2_sf_parse_item(NULL, s, s + sizeof(s))); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = "?0"; + val.type = 0xff; + + CU_ASSERT(2 == nghttp2_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_BOOLEAN == val.type); + CU_ASSERT(0 == val.b); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = "?0 "; + val.type = 0xff; + + CU_ASSERT(2 == nghttp2_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_BOOLEAN == val.type); + CU_ASSERT(0 == val.b); + } + + { + const uint8_t s[] = "?2"; + + CU_ASSERT(-1 == nghttp2_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = "?"; + + CU_ASSERT(-1 == nghttp2_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = "?1"; + + CU_ASSERT(2 == nghttp2_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = ":cHJldGVuZCB0aGlzIGlzIGJpbmFyeSBjb250ZW50Lg==:"; + val.type = 0xff; + + CU_ASSERT(46 == nghttp2_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_BYTESEQ == val.type); + CU_ASSERT(44 == val.s.len); + CU_ASSERT(0 == memcmp("cHJldGVuZCB0aGlzIGlzIGJpbmFyeSBjb250ZW50Lg==", + val.s.base, val.s.len)); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = ":cHJldGVuZCB0aGlzIGlzIGJpbmFyeSBjb250ZW50Lg==: "; + val.type = 0xff; + + CU_ASSERT(46 == nghttp2_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_BYTESEQ == val.type); + CU_ASSERT(44 == val.s.len); + CU_ASSERT(0 == memcmp("cHJldGVuZCB0aGlzIGlzIGJpbmFyeSBjb250ZW50Lg==", + val.s.base, val.s.len)); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = "::"; + val.type = 0xff; + + CU_ASSERT(2 == nghttp2_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_BYTESEQ == val.type); + CU_ASSERT(0 == val.s.len); + } + + { + const uint8_t s[] = ":cHJldGVuZCB0aGlzIGlzIGJpbmFyeSBjb250ZW50Lg=="; + + CU_ASSERT(-1 == nghttp2_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = ":"; + + CU_ASSERT(-1 == nghttp2_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = ":@:"; + + CU_ASSERT(-1 == nghttp2_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = ":foo:"; + + CU_ASSERT(5 == nghttp2_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = + ":abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=:"; + val.type = 0xff; + + CU_ASSERT(67 == nghttp2_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_BYTESEQ == val.type); + CU_ASSERT(65 == val.s.len); + CU_ASSERT( + 0 == + memcmp( + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=", + val.s.base, val.s.len)); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = "foo123/456"; + val.type = 0xff; + + CU_ASSERT(10 == nghttp2_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_TOKEN == val.type); + CU_ASSERT(10 == val.s.len); + CU_ASSERT(0 == memcmp(s, val.s.base, val.s.len)); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = "foo123/456 "; + val.type = 0xff; + + CU_ASSERT(10 == nghttp2_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_TOKEN == val.type); + CU_ASSERT(10 == val.s.len); + CU_ASSERT(0 == memcmp(s, val.s.base, val.s.len)); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = "*"; + val.type = 0xff; + + CU_ASSERT(1 == nghttp2_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_TOKEN == val.type); + CU_ASSERT(1 == val.s.len); + CU_ASSERT(0 == memcmp(s, val.s.base, val.s.len)); + } + + { + const uint8_t s[] = "*"; + + CU_ASSERT(1 == nghttp2_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = "\"hello world\""; + val.type = 0xff; + + CU_ASSERT(13 == nghttp2_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_STRING == val.type); + CU_ASSERT(11 == val.s.len); + CU_ASSERT(0 == memcmp("hello world", val.s.base, val.s.len)); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = "\"hello world\" "; + val.type = 0xff; + + CU_ASSERT(13 == nghttp2_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_STRING == val.type); + CU_ASSERT(11 == val.s.len); + CU_ASSERT(0 == memcmp("hello world", val.s.base, val.s.len)); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = "\"foo\\\"\\\\\""; + val.type = 0xff; + + CU_ASSERT(9 == nghttp2_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_STRING == val.type); + CU_ASSERT(7 == val.s.len); + CU_ASSERT(0 == memcmp("foo\\\"\\\\", val.s.base, val.s.len)); + } + + { + const uint8_t s[] = "\"foo\\x\""; + + CU_ASSERT(-1 == nghttp2_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = "\"foo"; + + CU_ASSERT(-1 == nghttp2_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = "\"\x7f\""; + + CU_ASSERT(-1 == nghttp2_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = "\"\x1f\""; + + CU_ASSERT(-1 == nghttp2_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = "\"foo\""; + + CU_ASSERT(5 == nghttp2_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = "4.5"; + val.type = NGHTTP2_SF_VALUE_TYPE_DECIMAL; + + CU_ASSERT(3 == nghttp2_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_DECIMAL == val.type); + CU_ASSERT(fabs(4.5 - val.d) < 1e-9); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = "4.5 "; + val.type = NGHTTP2_SF_VALUE_TYPE_DECIMAL; + + CU_ASSERT(3 == nghttp2_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_DECIMAL == val.type); + CU_ASSERT(fabs(4.5 - val.d) < 1e-9); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = "-4.5"; + val.type = NGHTTP2_SF_VALUE_TYPE_DECIMAL; + + CU_ASSERT(4 == nghttp2_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_DECIMAL == val.type); + CU_ASSERT(fabs(-4.5 - val.d) < 1e-9); + } + + { + const uint8_t s[] = "4.5"; + + CU_ASSERT(3 == nghttp2_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = "123456789012.123"; + val.type = NGHTTP2_SF_VALUE_TYPE_DECIMAL; + + CU_ASSERT(16 == nghttp2_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_DECIMAL == val.type); + CU_ASSERT(fabs(123456789012.123 - val.d) < 1e-9); + } + + { + const uint8_t s[] = "1123456789012.123"; + + CU_ASSERT(-1 == nghttp2_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = "123456789012.1234"; + + CU_ASSERT(-1 == nghttp2_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = "1."; + + CU_ASSERT(-1 == nghttp2_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = "123456789012345"; + val.type = NGHTTP2_SF_VALUE_TYPE_DECIMAL; + + CU_ASSERT(15 == nghttp2_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_INTEGER == val.type); + CU_ASSERT(123456789012345 == val.i); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = "1 "; + val.type = NGHTTP2_SF_VALUE_TYPE_DECIMAL; + + CU_ASSERT(1 == nghttp2_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_INTEGER == val.type); + CU_ASSERT(1 == val.i); + } + + { + const uint8_t s[] = "1"; + + CU_ASSERT(1 == nghttp2_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = "1234567890123456"; + + CU_ASSERT(-1 == nghttp2_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = "\"foo\";a; b=\"bar\";c=1.3;d=9;e=baz;f=:aaa:"; + val.type = 0xff; + + CU_ASSERT(41 == nghttp2_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_STRING == val.type); + CU_ASSERT(0 == memcmp("foo", val.s.base, val.s.len)); + } + + { + const uint8_t s[] = "\"foo\";a; b=\"bar"; + + CU_ASSERT(-1 == nghttp2_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = "foo;"; + + CU_ASSERT(-1 == nghttp2_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } +} + +void test_nghttp2_sf_parse_inner_list(void) { + { + nghttp2_sf_value val; + const uint8_t s[] = "()"; + val.type = 0xff; + + CU_ASSERT(2 == nghttp2_sf_parse_inner_list(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_INNER_LIST == val.type); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = "( )"; + val.type = 0xff; + + CU_ASSERT(7 == nghttp2_sf_parse_inner_list(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_INNER_LIST == val.type); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = "(a)"; + val.type = 0xff; + + CU_ASSERT(3 == nghttp2_sf_parse_inner_list(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_INNER_LIST == val.type); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = "(a b)"; + val.type = 0xff; + + CU_ASSERT(5 == nghttp2_sf_parse_inner_list(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_INNER_LIST == val.type); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = "( a b )"; + val.type = 0xff; + + CU_ASSERT(10 == nghttp2_sf_parse_inner_list(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_INNER_LIST == val.type); + } + + { + nghttp2_sf_value val; + const uint8_t s[] = "( a;foo=bar)"; + val.type = 0xff; + + CU_ASSERT(12 == nghttp2_sf_parse_inner_list(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP2_SF_VALUE_TYPE_INNER_LIST == val.type); + } + + { + const uint8_t s[] = "("; + + CU_ASSERT(-1 == nghttp2_sf_parse_inner_list(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = "(a"; + + CU_ASSERT(-1 == nghttp2_sf_parse_inner_list(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = "(a "; + + CU_ASSERT(-1 == nghttp2_sf_parse_inner_list(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = "(a;b"; + + CU_ASSERT(-1 == nghttp2_sf_parse_inner_list(NULL, s, s + sizeof(s) - 1)); + } +} diff --git a/tests/nghttp2_http_test.h b/tests/nghttp2_http_test.h new file mode 100644 index 00000000..901d6818 --- /dev/null +++ b/tests/nghttp2_http_test.h @@ -0,0 +1,37 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2022 nghttp3 contributors + * Copyright (c) 2022 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2_HTTP_TEST_H +#define NGHTTP2_HTTP_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +void test_nghttp2_http_parse_priority(void); +void test_nghttp2_sf_parse_item(void); +void test_nghttp2_sf_parse_inner_list(void); + +#endif /* NGHTTP2_HTTP_TEST_H */ diff --git a/tests/nghttp2_session_test.c b/tests/nghttp2_session_test.c index 9dc16a08..71e6b79e 100644 --- a/tests/nghttp2_session_test.c +++ b/tests/nghttp2_session_test.c @@ -35,6 +35,7 @@ #include "nghttp2_helper.h" #include "nghttp2_test_helper.h" #include "nghttp2_priority_spec.h" +#include "nghttp2_extpri.h" typedef struct { uint8_t buf[65535]; @@ -1828,6 +1829,67 @@ void test_nghttp2_session_recv_headers_for_closed_stream(void) { nghttp2_session_del(session); } +void test_nghttp2_session_recv_headers_with_extpri(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_nv *nva; + size_t nvlen; + nghttp2_frame frame; + nghttp2_bufs bufs; + nghttp2_buf *buf; + ssize_t rv; + nghttp2_hd_deflater deflater; + nghttp2_stream *stream; + nghttp2_mem *mem; + const nghttp2_nv extpri_reqnv[] = { + MAKE_NV(":method", "GET"), MAKE_NV(":path", "/"), + MAKE_NV(":scheme", "https"), MAKE_NV(":authority", "localhost"), + MAKE_NV("priority", "i,u=2"), + }; + nghttp2_settings_entry iv; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + iv.settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES; + iv.value = 1; + + nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1); + + nghttp2_hd_deflate_init(&deflater, mem); + + nvlen = ARRLEN(extpri_reqnv); + nghttp2_nv_array_copy(&nva, extpri_reqnv, nvlen, mem); + + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 1, + NGHTTP2_HCAT_HEADERS, NULL, nva, nvlen); + + rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + + nghttp2_frame_headers_free(&frame.headers, mem); + + buf = &bufs.head->buf; + assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf)); + + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + stream = nghttp2_session_get_stream(session, 1); + + CU_ASSERT(2 == nghttp2_extpri_uint8_urgency(stream->extpri)); + CU_ASSERT(1 == nghttp2_extpri_uint8_inc(stream->extpri)); + + nghttp2_bufs_free(&bufs); + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); +} + void test_nghttp2_session_server_recv_push_response(void) { nghttp2_session *session; nghttp2_session_callbacks callbacks; @@ -11209,7 +11271,8 @@ void test_nghttp2_session_no_rfc7540_priorities(void) { mem); CU_ASSERT(0 == nghttp2_session_send(session)); - CU_ASSERT(1 == nghttp2_pq_size(&session->ob_data)); + CU_ASSERT(1 == nghttp2_pq_size( + &session->sched[NGHTTP2_EXTPRI_DEFAULT_URGENCY].ob_data)); CU_ASSERT(nghttp2_pq_empty(&session->root.obq)); nghttp2_session_del(session); diff --git a/tests/nghttp2_session_test.h b/tests/nghttp2_session_test.h index 335a7cfc..6deff433 100644 --- a/tests/nghttp2_session_test.h +++ b/tests/nghttp2_session_test.h @@ -40,6 +40,7 @@ void test_nghttp2_session_recv_headers_with_priority(void); void test_nghttp2_session_recv_headers_with_padding(void); void test_nghttp2_session_recv_headers_early_response(void); void test_nghttp2_session_recv_headers_for_closed_stream(void); +void test_nghttp2_session_recv_headers_with_extpri(void); void test_nghttp2_session_server_recv_push_response(void); void test_nghttp2_session_recv_premature_headers(void); void test_nghttp2_session_recv_unknown_frame(void);