|
|
|
/*
|
|
|
|
* h2 - HTTP/2 protocol layer
|
|
|
|
*
|
|
|
|
* Copyright(c) 2020 Glenn Strauss gstrauss()gluelogic.com All rights reserved
|
|
|
|
* License: BSD 3-clause (same as lighttpd)
|
|
|
|
*/
|
|
|
|
#include "first.h"
|
|
|
|
#include "h2.h"
|
|
|
|
|
|
|
|
#include <arpa/inet.h> /* htonl() */
|
|
|
|
#include <stdint.h> /* INT32_MAX INT32_MIN */
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
#include "base.h"
|
|
|
|
#include "buffer.h"
|
|
|
|
#include "chunk.h"
|
|
|
|
#include "fdevent.h" /* FDEVENT_STREAM_REQUEST_BUFMIN */
|
|
|
|
#include "http_date.h"
|
|
|
|
#include "http_header.h"
|
|
|
|
#include "log.h"
|
|
|
|
#include "request.h"
|
|
|
|
#include "response.h" /* http_response_omit_header() */
|
|
|
|
|
|
|
|
|
|
|
|
/* lowercased field-names
|
|
|
|
* (32-byte record (power-2) and single block of memory for memory locality) */
|
|
|
|
static const char http_header_lc[][32] = {
|
|
|
|
[HTTP_HEADER_OTHER] = ""
|
|
|
|
,[HTTP_HEADER_ACCEPT] = "accept"
|
|
|
|
,[HTTP_HEADER_ACCEPT_ENCODING] = "accept-encoding"
|
|
|
|
,[HTTP_HEADER_ACCEPT_LANGUAGE] = "accept-language"
|
|
|
|
,[HTTP_HEADER_ACCEPT_RANGES] = "accept-ranges"
|
|
|
|
,[HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN]="access-control-allow-origin"
|
|
|
|
,[HTTP_HEADER_AGE] = "age"
|
|
|
|
,[HTTP_HEADER_ALLOW] = "allow"
|
|
|
|
,[HTTP_HEADER_ALT_SVC] = "alt-svc"
|
|
|
|
,[HTTP_HEADER_ALT_USED] = "alt-used"
|
|
|
|
,[HTTP_HEADER_AUTHORIZATION] = "authorization"
|
|
|
|
,[HTTP_HEADER_CACHE_CONTROL] = "cache-control"
|
|
|
|
,[HTTP_HEADER_CONNECTION] = "connection"
|
|
|
|
,[HTTP_HEADER_CONTENT_ENCODING] = "content-encoding"
|
|
|
|
,[HTTP_HEADER_CONTENT_LENGTH] = "content-length"
|
|
|
|
,[HTTP_HEADER_CONTENT_LOCATION] = "content-location"
|
|
|
|
,[HTTP_HEADER_CONTENT_RANGE] = "content-range"
|
|
|
|
,[HTTP_HEADER_CONTENT_SECURITY_POLICY] = "content-security-policy"
|
|
|
|
,[HTTP_HEADER_CONTENT_TYPE] = "content-type"
|
|
|
|
,[HTTP_HEADER_COOKIE] = "cookie"
|
|
|
|
,[HTTP_HEADER_DATE] = "date"
|
|
|
|
,[HTTP_HEADER_DNT] = "dnt"
|
|
|
|
,[HTTP_HEADER_ETAG] = "etag"
|
|
|
|
,[HTTP_HEADER_EXPECT] = "expect"
|
|
|
|
,[HTTP_HEADER_EXPECT_CT] = "expect-ct"
|
|
|
|
,[HTTP_HEADER_EXPIRES] = "expires"
|
|
|
|
,[HTTP_HEADER_FORWARDED] = "forwarded"
|
|
|
|
,[HTTP_HEADER_HOST] = "host"
|
|
|
|
,[HTTP_HEADER_HTTP2_SETTINGS] = "http2-settings"
|
|
|
|
,[HTTP_HEADER_IF_MATCH] = "if-match"
|
|
|
|
,[HTTP_HEADER_IF_MODIFIED_SINCE] = "if-modified-since"
|
|
|
|
,[HTTP_HEADER_IF_NONE_MATCH] = "if-none-match"
|
|
|
|
,[HTTP_HEADER_IF_RANGE] = "if-range"
|
|
|
|
,[HTTP_HEADER_IF_UNMODIFIED_SINCE] = "if-unmodified-since"
|
|
|
|
,[HTTP_HEADER_LAST_MODIFIED] = "last-modified"
|
|
|
|
,[HTTP_HEADER_LINK] = "link"
|
|
|
|
,[HTTP_HEADER_LOCATION] = "location"
|
|
|
|
,[HTTP_HEADER_ONION_LOCATION] = "onion-location"
|
|
|
|
,[HTTP_HEADER_P3P] = "p3p"
|
|
|
|
,[HTTP_HEADER_PRAGMA] = "pragma"
|
|
|
|
,[HTTP_HEADER_PRIORITY] = "priority"
|
|
|
|
,[HTTP_HEADER_RANGE] = "range"
|
|
|
|
,[HTTP_HEADER_REFERER] = "referer"
|
|
|
|
,[HTTP_HEADER_REFERRER_POLICY] = "referrer-policy"
|
|
|
|
,[HTTP_HEADER_SERVER] = "server"
|
|
|
|
,[HTTP_HEADER_SET_COOKIE] = "set-cookie"
|
|
|
|
,[HTTP_HEADER_STATUS] = "status"
|
|
|
|
,[HTTP_HEADER_STRICT_TRANSPORT_SECURITY] = "strict-transport-security"
|
|
|
|
,[HTTP_HEADER_TE] = "te"
|
|
|
|
,[HTTP_HEADER_TRANSFER_ENCODING] = "transfer-encoding"
|
|
|
|
,[HTTP_HEADER_UPGRADE] = "upgrade"
|
|
|
|
,[HTTP_HEADER_UPGRADE_INSECURE_REQUESTS] = "upgrade-insecure-requests"
|
|
|
|
,[HTTP_HEADER_USER_AGENT] = "user-agent"
|
|
|
|
,[HTTP_HEADER_VARY] = "vary"
|
|
|
|
,[HTTP_HEADER_WWW_AUTHENTICATE] = "www-authenticate"
|
|
|
|
,[HTTP_HEADER_X_CONTENT_TYPE_OPTIONS] = "x-content-type-options"
|
|
|
|
,[HTTP_HEADER_X_FORWARDED_FOR] = "x-forwarded-for"
|
|
|
|
,[HTTP_HEADER_X_FORWARDED_PROTO] = "x-forwarded-proto"
|
|
|
|
,[HTTP_HEADER_X_FRAME_OPTIONS] = "x-frame-options"
|
|
|
|
,[HTTP_HEADER_X_XSS_PROTECTION] = "x-xss-protection"
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/* future optimization: could conceivably store static XXH32() hash values for
|
|
|
|
* field-name (e.g. for benefit of entries marked LSHPACK_HDR_UNKNOWN) to
|
|
|
|
* incrementally reduce cost of calculating hash values for field-name on each
|
|
|
|
* request where those headers are used. Might also store single element
|
|
|
|
* static caches for "date:" value (updated each time static buffer is updated)
|
|
|
|
* and for "server:" value (often global to server), keyed on r->conf.server_tag
|
|
|
|
* pointer addr. HTTP_HEADER_STATUS could be overloaded for ":status", since
|
|
|
|
* lighttpd should not send "Status:" response header (should not happen) */
|
|
|
|
|
|
|
|
static const uint8_t http_header_lshpack_idx[] = {
|
|
|
|
[HTTP_HEADER_OTHER] = LSHPACK_HDR_UNKNOWN
|
|
|
|
,[HTTP_HEADER_ACCEPT_ENCODING] = LSHPACK_HDR_ACCEPT_ENCODING
|
|
|
|
,[HTTP_HEADER_AUTHORIZATION] = LSHPACK_HDR_AUTHORIZATION
|
|
|
|
,[HTTP_HEADER_CACHE_CONTROL] = LSHPACK_HDR_CACHE_CONTROL
|
|
|
|
,[HTTP_HEADER_CONNECTION] = LSHPACK_HDR_UNKNOWN
|
|
|
|
,[HTTP_HEADER_CONTENT_ENCODING] = LSHPACK_HDR_CONTENT_ENCODING
|
|
|
|
,[HTTP_HEADER_CONTENT_LENGTH] = LSHPACK_HDR_CONTENT_LENGTH
|
|
|
|
,[HTTP_HEADER_CONTENT_LOCATION] = LSHPACK_HDR_CONTENT_LOCATION
|
|
|
|
,[HTTP_HEADER_CONTENT_TYPE] = LSHPACK_HDR_CONTENT_TYPE
|
|
|
|
,[HTTP_HEADER_COOKIE] = LSHPACK_HDR_COOKIE
|
|
|
|
,[HTTP_HEADER_DATE] = LSHPACK_HDR_DATE
|
|
|
|
,[HTTP_HEADER_ETAG] = LSHPACK_HDR_ETAG
|
|
|
|
,[HTTP_HEADER_EXPECT] = LSHPACK_HDR_EXPECT
|
|
|
|
,[HTTP_HEADER_FORWARDED] = LSHPACK_HDR_UNKNOWN
|
|
|
|
,[HTTP_HEADER_HOST] = LSHPACK_HDR_HOST
|
|
|
|
,[HTTP_HEADER_IF_MODIFIED_SINCE] = LSHPACK_HDR_IF_MODIFIED_SINCE
|
|
|
|
,[HTTP_HEADER_IF_NONE_MATCH] = LSHPACK_HDR_IF_NONE_MATCH
|
|
|
|
,[HTTP_HEADER_LAST_MODIFIED] = LSHPACK_HDR_LAST_MODIFIED
|
|
|
|
,[HTTP_HEADER_LOCATION] = LSHPACK_HDR_LOCATION
|
|
|
|
,[HTTP_HEADER_RANGE] = LSHPACK_HDR_RANGE
|
|
|
|
,[HTTP_HEADER_SERVER] = LSHPACK_HDR_SERVER
|
|
|
|
,[HTTP_HEADER_SET_COOKIE] = LSHPACK_HDR_SET_COOKIE
|
|
|
|
,[HTTP_HEADER_STATUS] = LSHPACK_HDR_UNKNOWN
|
|
|
|
,[HTTP_HEADER_TRANSFER_ENCODING] = LSHPACK_HDR_TRANSFER_ENCODING
|
|
|
|
,[HTTP_HEADER_UPGRADE] = LSHPACK_HDR_UNKNOWN
|
|
|
|
,[HTTP_HEADER_USER_AGENT] = LSHPACK_HDR_USER_AGENT
|
|
|
|
,[HTTP_HEADER_VARY] = LSHPACK_HDR_VARY
|
|
|
|
,[HTTP_HEADER_X_FORWARDED_FOR] = LSHPACK_HDR_UNKNOWN
|
|
|
|
,[HTTP_HEADER_X_FORWARDED_PROTO] = LSHPACK_HDR_UNKNOWN
|
|
|
|
,[HTTP_HEADER_HTTP2_SETTINGS] = LSHPACK_HDR_UNKNOWN
|
|
|
|
,[HTTP_HEADER_ACCEPT] = LSHPACK_HDR_ACCEPT
|
|
|
|
,[HTTP_HEADER_ACCEPT_LANGUAGE] = LSHPACK_HDR_ACCEPT_LANGUAGE
|
|
|
|
,[HTTP_HEADER_ACCEPT_RANGES] = LSHPACK_HDR_ACCEPT_RANGES
|
|
|
|
,[HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN]=LSHPACK_HDR_ACCESS_CONTROL_ALLOW_ORIGIN
|
|
|
|
,[HTTP_HEADER_AGE] = LSHPACK_HDR_AGE
|
|
|
|
,[HTTP_HEADER_ALLOW] = LSHPACK_HDR_ALLOW
|
|
|
|
,[HTTP_HEADER_ALT_SVC] = LSHPACK_HDR_UNKNOWN
|
|
|
|
,[HTTP_HEADER_ALT_USED] = LSHPACK_HDR_UNKNOWN
|
|
|
|
,[HTTP_HEADER_CONTENT_RANGE] = LSHPACK_HDR_CONTENT_RANGE
|
|
|
|
,[HTTP_HEADER_CONTENT_SECURITY_POLICY] = LSHPACK_HDR_UNKNOWN
|
|
|
|
,[HTTP_HEADER_DNT] = LSHPACK_HDR_UNKNOWN
|
|
|
|
,[HTTP_HEADER_EXPECT_CT] = LSHPACK_HDR_UNKNOWN
|
|
|
|
,[HTTP_HEADER_EXPIRES] = LSHPACK_HDR_EXPIRES
|
|
|
|
,[HTTP_HEADER_IF_MATCH] = LSHPACK_HDR_IF_MATCH
|
|
|
|
,[HTTP_HEADER_IF_RANGE] = LSHPACK_HDR_IF_RANGE
|
|
|
|
,[HTTP_HEADER_IF_UNMODIFIED_SINCE] = LSHPACK_HDR_IF_UNMODIFIED_SINCE
|
|
|
|
,[HTTP_HEADER_LINK] = LSHPACK_HDR_LINK
|
|
|
|
,[HTTP_HEADER_ONION_LOCATION] = LSHPACK_HDR_UNKNOWN
|
|
|
|
,[HTTP_HEADER_P3P] = LSHPACK_HDR_UNKNOWN
|
|
|
|
,[HTTP_HEADER_PRAGMA] = LSHPACK_HDR_UNKNOWN
|
|
|
|
,[HTTP_HEADER_PRIORITY] = LSHPACK_HDR_UNKNOWN
|
|
|
|
,[HTTP_HEADER_REFERER] = LSHPACK_HDR_REFERER
|
|
|
|
,[HTTP_HEADER_REFERRER_POLICY] = LSHPACK_HDR_UNKNOWN
|
|
|
|
,[HTTP_HEADER_STRICT_TRANSPORT_SECURITY] = LSHPACK_HDR_STRICT_TRANSPORT_SECURITY
|
|
|
|
,[HTTP_HEADER_TE] = LSHPACK_HDR_UNKNOWN
|
|
|
|
,[HTTP_HEADER_UPGRADE_INSECURE_REQUESTS] = LSHPACK_HDR_UNKNOWN
|
|
|
|
,[HTTP_HEADER_WWW_AUTHENTICATE] = LSHPACK_HDR_WWW_AUTHENTICATE
|
|
|
|
,[HTTP_HEADER_X_CONTENT_TYPE_OPTIONS] = LSHPACK_HDR_UNKNOWN
|
|
|
|
,[HTTP_HEADER_X_FRAME_OPTIONS] = LSHPACK_HDR_UNKNOWN
|
|
|
|
,[HTTP_HEADER_X_XSS_PROTECTION] = LSHPACK_HDR_UNKNOWN
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/* Note: must be kept in sync with ls-hpack/lshpack.h:lshpack_static_hdr_idx[]*/
|
|
|
|
static const int8_t lshpack_idx_http_header[] = {
|
|
|
|
[LSHPACK_HDR_UNKNOWN] = HTTP_HEADER_H2_UNKNOWN
|
|
|
|
,[LSHPACK_HDR_AUTHORITY] = HTTP_HEADER_H2_AUTHORITY
|
|
|
|
,[LSHPACK_HDR_METHOD_GET] = HTTP_HEADER_H2_METHOD_GET
|
|
|
|
,[LSHPACK_HDR_METHOD_POST] = HTTP_HEADER_H2_METHOD_POST
|
|
|
|
,[LSHPACK_HDR_PATH] = HTTP_HEADER_H2_PATH
|
|
|
|
,[LSHPACK_HDR_PATH_INDEX_HTML] = HTTP_HEADER_H2_PATH_INDEX_HTML
|
|
|
|
,[LSHPACK_HDR_SCHEME_HTTP] = HTTP_HEADER_H2_SCHEME_HTTP
|
|
|
|
,[LSHPACK_HDR_SCHEME_HTTPS] = HTTP_HEADER_H2_SCHEME_HTTPS
|
|
|
|
,[LSHPACK_HDR_STATUS_200] = HTTP_HEADER_H2_UNKNOWN
|
|
|
|
,[LSHPACK_HDR_STATUS_204] = HTTP_HEADER_H2_UNKNOWN
|
|
|
|
,[LSHPACK_HDR_STATUS_206] = HTTP_HEADER_H2_UNKNOWN
|
|
|
|
,[LSHPACK_HDR_STATUS_304] = HTTP_HEADER_H2_UNKNOWN
|
|
|
|
,[LSHPACK_HDR_STATUS_400] = HTTP_HEADER_H2_UNKNOWN
|
|
|
|
,[LSHPACK_HDR_STATUS_404] = HTTP_HEADER_H2_UNKNOWN
|
|
|
|
,[LSHPACK_HDR_STATUS_500] = HTTP_HEADER_H2_UNKNOWN
|
|
|
|
,[LSHPACK_HDR_ACCEPT_CHARSET] = HTTP_HEADER_OTHER
|
|
|
|
,[LSHPACK_HDR_ACCEPT_ENCODING] = HTTP_HEADER_ACCEPT_ENCODING
|
|
|
|
,[LSHPACK_HDR_ACCEPT_LANGUAGE] = HTTP_HEADER_ACCEPT_LANGUAGE
|
|
|
|
,[LSHPACK_HDR_ACCEPT_RANGES] = HTTP_HEADER_ACCEPT_RANGES
|
|
|
|
,[LSHPACK_HDR_ACCEPT] = HTTP_HEADER_ACCEPT
|
|
|
|
,[LSHPACK_HDR_ACCESS_CONTROL_ALLOW_ORIGIN]=HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN
|
|
|
|
,[LSHPACK_HDR_AGE] = HTTP_HEADER_AGE
|
|
|
|
,[LSHPACK_HDR_ALLOW] = HTTP_HEADER_ALLOW
|
|
|
|
,[LSHPACK_HDR_AUTHORIZATION] = HTTP_HEADER_AUTHORIZATION
|
|
|
|
,[LSHPACK_HDR_CACHE_CONTROL] = HTTP_HEADER_CACHE_CONTROL
|
|
|
|
,[LSHPACK_HDR_CONTENT_DISPOSITION] = HTTP_HEADER_OTHER
|
|
|
|
,[LSHPACK_HDR_CONTENT_ENCODING] = HTTP_HEADER_CONTENT_ENCODING
|
|
|
|
,[LSHPACK_HDR_CONTENT_LANGUAGE] = HTTP_HEADER_OTHER
|
|
|
|
,[LSHPACK_HDR_CONTENT_LENGTH] = HTTP_HEADER_CONTENT_LENGTH
|
|
|
|
,[LSHPACK_HDR_CONTENT_LOCATION] = HTTP_HEADER_CONTENT_LOCATION
|
|
|
|
,[LSHPACK_HDR_CONTENT_RANGE] = HTTP_HEADER_CONTENT_RANGE
|
|
|
|
,[LSHPACK_HDR_CONTENT_TYPE] = HTTP_HEADER_CONTENT_TYPE
|
|
|
|
,[LSHPACK_HDR_COOKIE] = HTTP_HEADER_COOKIE
|
|
|
|
,[LSHPACK_HDR_DATE] = HTTP_HEADER_DATE
|
|
|
|
,[LSHPACK_HDR_ETAG] = HTTP_HEADER_ETAG
|
|
|
|
,[LSHPACK_HDR_EXPECT] = HTTP_HEADER_EXPECT
|
|
|
|
,[LSHPACK_HDR_EXPIRES] = HTTP_HEADER_EXPIRES
|
|
|
|
,[LSHPACK_HDR_FROM] = HTTP_HEADER_OTHER
|
|
|
|
,[LSHPACK_HDR_HOST] = HTTP_HEADER_HOST
|
|
|
|
,[LSHPACK_HDR_IF_MATCH] = HTTP_HEADER_IF_MATCH
|
|
|
|
,[LSHPACK_HDR_IF_MODIFIED_SINCE] = HTTP_HEADER_IF_MODIFIED_SINCE
|
|
|
|
,[LSHPACK_HDR_IF_NONE_MATCH] = HTTP_HEADER_IF_NONE_MATCH
|
|
|
|
,[LSHPACK_HDR_IF_RANGE] = HTTP_HEADER_IF_RANGE
|
|
|
|
,[LSHPACK_HDR_IF_UNMODIFIED_SINCE] = HTTP_HEADER_IF_UNMODIFIED_SINCE
|
|
|
|
,[LSHPACK_HDR_LAST_MODIFIED] = HTTP_HEADER_LAST_MODIFIED
|
|
|
|
,[LSHPACK_HDR_LINK] = HTTP_HEADER_LINK
|
|
|
|
,[LSHPACK_HDR_LOCATION] = HTTP_HEADER_LOCATION
|
|
|
|
,[LSHPACK_HDR_MAX_FORWARDS] = HTTP_HEADER_OTHER
|
|
|
|
,[LSHPACK_HDR_PROXY_AUTHENTICATE] = HTTP_HEADER_OTHER
|
|
|
|
,[LSHPACK_HDR_PROXY_AUTHORIZATION] = HTTP_HEADER_OTHER
|
|
|
|
,[LSHPACK_HDR_RANGE] = HTTP_HEADER_RANGE
|
|
|
|
,[LSHPACK_HDR_REFERER] = HTTP_HEADER_REFERER
|
|
|
|
,[LSHPACK_HDR_REFRESH] = HTTP_HEADER_OTHER
|
|
|
|
,[LSHPACK_HDR_RETRY_AFTER] = HTTP_HEADER_OTHER
|
|
|
|
,[LSHPACK_HDR_SERVER] = HTTP_HEADER_SERVER
|
|
|
|
,[LSHPACK_HDR_SET_COOKIE] = HTTP_HEADER_SET_COOKIE
|
|
|
|
,[LSHPACK_HDR_STRICT_TRANSPORT_SECURITY] = HTTP_HEADER_STRICT_TRANSPORT_SECURITY
|
|
|
|
,[LSHPACK_HDR_TRANSFER_ENCODING] = HTTP_HEADER_TRANSFER_ENCODING
|
|
|
|
,[LSHPACK_HDR_USER_AGENT] = HTTP_HEADER_USER_AGENT
|
|
|
|
,[LSHPACK_HDR_VARY] = HTTP_HEADER_VARY
|
|
|
|
,[LSHPACK_HDR_VIA] = HTTP_HEADER_OTHER
|
|
|
|
,[LSHPACK_HDR_WWW_AUTHENTICATE] = HTTP_HEADER_WWW_AUTHENTICATE
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static request_st * h2_init_stream (request_st * const h2r, connection * const con);
|
|
|
|
|
|
|
|
|
|
|
|
__attribute_pure__
|
|
|
|
static inline uint32_t
|
|
|
|
h2_u32 (const uint8_t * const s)
|
|
|
|
{
|
|
|
|
return ((uint32_t)s[0] << 24)
|
|
|
|
| ((uint32_t)s[1] << 16)
|
|
|
|
| ((uint32_t)s[2] << 8)
|
|
|
|
| (uint32_t)s[3];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
__attribute_pure__
|
|
|
|
static inline uint32_t
|
|
|
|
h2_u31 (const uint8_t * const s)
|
|
|
|
{
|
|
|
|
return h2_u32(s) & ~0x80000000u;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
__attribute_pure__
|
|
|
|
static inline uint32_t
|
|
|
|
h2_u24 (const uint8_t * const s)
|
|
|
|
{
|
|
|
|
#if 1
|
|
|
|
/* XXX: optimization is valid only for how this is used in h2.c
|
|
|
|
* where we have checked that frame header received is at least
|
|
|
|
* 9 chars, and where s containing frame length (3-bytes) is
|
|
|
|
* followed by at least 1 additional char. */
|
|
|
|
return h2_u32(s) >> 8;
|
|
|
|
#else
|
|
|
|
return ((uint32_t)s[0] << 16)
|
|
|
|
| ((uint32_t)s[1] << 8)
|
|
|
|
| (uint32_t)s[2];
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
__attribute_pure__
|
|
|
|
static inline uint16_t
|
|
|
|
h2_u16 (const uint8_t * const s)
|
|
|
|
{
|
|
|
|
return ((uint16_t)s[0] << 8)
|
|
|
|
| (uint16_t)s[1];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
h2_send_settings_ack (connection * const con)
|
|
|
|
{
|
|
|
|
static const uint8_t settings_ack[] = {
|
|
|
|
/* SETTINGS w/ ACK */
|
|
|
|
0x00, 0x00, 0x00 /* frame length */
|
|
|
|
,H2_FTYPE_SETTINGS /* frame type */
|
|
|
|
,H2_FLAG_ACK /* frame flags */
|
|
|
|
,0x00, 0x00, 0x00, 0x00 /* stream identifier */
|
|
|
|
};
|
|
|
|
|
|
|
|
chunkqueue_append_mem(con->write_queue,
|
|
|
|
(const char *)settings_ack, sizeof(settings_ack));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
__attribute_cold__
|
|
|
|
static void
|
|
|
|
h2_send_rst_stream_id (uint32_t h2id, connection * const con, const request_h2error_t e)
|
|
|
|
{
|
|
|
|
union {
|
|
|
|
uint8_t c[16];
|
|
|
|
uint32_t u[4]; /*(alignment)*/
|
|
|
|
} rst_stream = { { /*(big-endian numbers)*/
|
|
|
|
0x00, 0x00, 0x00 /* padding for alignment; do not send */
|
|
|
|
/* RST_STREAM */
|
|
|
|
,0x00, 0x00, 0x04 /* frame length */
|
|
|
|
,H2_FTYPE_RST_STREAM /* frame type */
|
|
|
|
,0x00 /* frame flags */
|
|
|
|
,0x00, 0x00, 0x00, 0x00 /* stream identifier (fill in below) */
|
|
|
|
,0x00, 0x00, 0x00, 0x00 /* error code; (fill in below) */
|
|
|
|
} };
|
|
|
|
|
|
|
|
rst_stream.u[2] = htonl(h2id);
|
|
|
|
rst_stream.u[3] = htonl(e);
|
|
|
|
chunkqueue_append_mem(con->write_queue, /*(+3 to skip over align padding)*/
|
|
|
|
(const char *)rst_stream.c+3, sizeof(rst_stream)-3);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
__attribute_cold__
|
|
|
|
static void
|
|
|
|
h2_send_rst_stream_state (request_st * const r, h2con * const h2c)
|
|
|
|
{
|
|
|
|
if (r->h2state != H2_STATE_HALF_CLOSED_REMOTE
|
|
|
|
&& r->h2state != H2_STATE_CLOSED) {
|
|
|
|
/* set timestamp for comparison; not tracking individual stream ids */
|
|
|
|
h2c->half_closed_ts = log_monotonic_secs;
|
|
|
|
}
|
|
|
|
r->state = CON_STATE_ERROR;
|
|
|
|
r->h2state = H2_STATE_CLOSED;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
__attribute_cold__
|
|
|
|
static void
|
|
|
|
h2_send_rst_stream (request_st * const r, connection * const con, const request_h2error_t e)
|
|
|
|
{
|
|
|
|
h2_send_rst_stream_state(r, con->h2);/*(sets r->h2state = H2_STATE_CLOSED)*/
|
|
|
|
h2_send_rst_stream_id(r->h2id, con, e);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
__attribute_cold__
|
|
|
|
static void
|
|
|
|
h2_send_goaway_rst_stream (connection * const con)
|
|
|
|
{
|
|
|
|
h2con * const h2c = con->h2;
|
|
|
|
const int sent_goaway = h2c->sent_goaway;
|
|
|
|
for (uint32_t i = 0, rused = h2c->rused; i < rused; ++i) {
|
|
|
|
request_st * const r = h2c->r[i];
|
|
|
|
if (r->h2state == H2_STATE_CLOSED) continue;
|
|
|
|
h2_send_rst_stream_state(r, h2c);/*(sets r->h2state = H2_STATE_CLOSED)*/
|
|
|
|
/*(XXX: might consider always sending RST_STREAM)*/
|
|
|
|
if (sent_goaway)
|
|
|
|
h2_send_rst_stream_id(r->h2id, con, H2_E_PROTOCOL_ERROR);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
h2_send_goaway (connection * const con, const request_h2error_t e)
|
|
|
|
{
|
|
|
|
/* future: RFC 7540 Section 6.8 notes that server initiating graceful
|
|
|
|
* connection shutdown SHOULD send GOAWAY with stream id 2^31-1 and a
|
|
|
|
* NO_ERROR code, and later send another GOAWAY with an updated last
|
|
|
|
* stream identifier. (This is not done here, but doing so would be
|
|
|
|
* friendlier to clients that send streaming requests which the client
|
|
|
|
* is unable to retry.) */
|
|
|
|
|
|
|
|
if (e != H2_E_NO_ERROR)
|
|
|
|
h2_send_goaway_rst_stream(con);
|
|
|
|
/*XXX: else should send RST_STREAM w/ CANCEL for any active PUSH_PROMISE */
|
|
|
|
|
|
|
|
h2con * const h2c = con->h2;
|
|
|
|
if (h2c->sent_goaway && (h2c->sent_goaway > 0 || e == H2_E_NO_ERROR))
|
|
|
|
return;
|
|
|
|
h2c->sent_goaway = (e == H2_E_NO_ERROR) ? -1 : (int32_t)e;
|
|
|
|
|
|
|
|
union {
|
|
|
|
uint8_t c[20];
|
|
|
|
uint32_t u[5]; /*(alignment)*/
|
|
|
|
} goaway = { { /*(big-endian numbers)*/
|
|
|
|
0x00, 0x00, 0x00 /* padding for alignment; do not send */
|
|
|
|
/* GOAWAY */
|
|
|
|
,0x00, 0x00, 0x08 /* frame length */
|
|
|
|
,H2_FTYPE_GOAWAY /* frame type */
|
|
|
|
,0x00 /* frame flags */
|
|
|
|
,0x00, 0x00, 0x00, 0x00 /* stream identifier */
|
|
|
|
,0x00, 0x00, 0x00, 0x00 /* last-stream-id (fill in below) */
|
|
|
|
,0x00, 0x00, 0x00, 0x00 /* error code (fill in below) */
|
|
|
|
/* additional debug data (*); (optional)
|
|
|
|
* adjust frame length if any additional
|
|
|
|
* debug data is sent */
|
|
|
|
} };
|
|
|
|
|
|
|
|
goaway.u[3] = htonl(h2c->h2_cid); /* last-stream-id */
|
|
|
|
goaway.u[4] = htonl(e);
|
|
|
|
chunkqueue_append_mem(con->write_queue, /*(+3 to skip over align padding)*/
|
|
|
|
(const char *)goaway.c+3, sizeof(goaway)-3);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
__attribute_cold__
|
|
|
|
static void
|
|
|
|
h2_send_goaway_e (connection * const con, const request_h2error_t e)
|
|
|
|
{
|
|
|
|
h2_send_goaway(con, e);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
__attribute_cold__
|
|
|
|
static int
|
|
|
|
h2_send_refused_stream (uint32_t h2id, connection * const con)
|
|
|
|
{
|
|
|
|
h2con * const h2c = con->h2;
|
|
|
|
|
|
|
|
if (h2c->sent_settings) { /*(see h2_recv_settings() comments)*/
|
|
|
|
/* client connected and immediately sent flurry of request streams
|
|
|
|
* (h2c->sent_settings is non-zero if sent SETTINGS frame to
|
|
|
|
* client and have not yet received SETTINGS ACK from client)
|
|
|
|
* lighttpd sends SETTINGS_MAX_CONCURRENT_STREAMS <limit> with
|
|
|
|
* server Connection Preface, so a well-behaved client will
|
|
|
|
* adjust after it sends its initial requests.
|
|
|
|
* (e.g. h2load -n 100 -m 100 sends 100 requests upon connect)
|
|
|
|
*
|
|
|
|
* Check if active streams have pending request body. If all active
|
|
|
|
* streams have pending request body, then must refuse new stream as
|
|
|
|
* progress might be blocked if active streams all wait for DATA. */
|
|
|
|
for (uint32_t i = 0, rused = h2c->rused; i < rused; ++i) {
|
|
|
|
const request_st * const r = h2c->r[i];
|
|
|
|
if (r->reqbody_length == r->reqbody_queue.bytes_in) {
|
|
|
|
/* no pending request body; at least this request may proceed,
|
|
|
|
* though others waiting for request body may block until new
|
|
|
|
* request streams become active if new request streams precede
|
|
|
|
* DATA frames for active streams
|
|
|
|
*
|
|
|
|
* alternative to sending refused stream:
|
|
|
|
* stop processing frames and defer processing this HEADERS
|
|
|
|
* frame until num active streams drops below limit. */
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* overload h2c->half_closed_ts to discard DATA (in h2_recv_data())
|
|
|
|
* from refused streams while waiting for SETTINGS ackn from client
|
|
|
|
* (instead of additional h2 con init time check in h2_recv_data())
|
|
|
|
* (though h2c->half_closed_ts is not unset when SETTINGS ackn received)
|
|
|
|
* (fuzzy discard; imprecise; see further comments in h2_recv_data()) */
|
|
|
|
h2c->half_closed_ts = h2c->sent_settings;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* too many active streams; refuse new stream */
|
|
|
|
h2c->h2_cid = h2id;
|
|
|
|
h2_send_rst_stream_id(h2id, con, H2_E_REFUSED_STREAM);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
h2_recv_goaway (connection * const con, const uint8_t * const s, uint32_t len)
|
|
|
|
{
|
|
|
|
/*(s must be entire GOAWAY frame and len the frame length field)*/
|
|
|
|
/*assert(s[3] == H2_FTYPE_GOAWAY);*/
|
|
|
|
if (len < 8) { /*(GOAWAY frame length must be >= 8)*/
|
|
|
|
h2_send_goaway_e(con, H2_E_FRAME_SIZE_ERROR);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (0 != h2_u31(s+5)) { /*(GOAWAY stream id must be 0)*/
|
|
|
|
h2_send_goaway_e(con, H2_E_PROTOCOL_ERROR);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
const uint32_t e = h2_u32(s+13);
|
|
|
|
#if 0
|
|
|
|
/* XXX: debug: could log error code sent by peer */
|
|
|
|
#endif
|
|
|
|
#if 0
|
|
|
|
/* XXX: debug: could log additional debug info (if any) sent by peer */
|
|
|
|
if (len > 8) {
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
#if 0
|
|
|
|
/* XXX: could validate/use Last-Stream-ID sent by peer */
|
|
|
|
const uint32_t last_id = h2_u31(s+9);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* send PROTOCOL_ERROR back to peer if peer sent an error code
|
|
|
|
* (i.e. not NO_ERROR) in order to terminate connection more quickly */
|
|
|
|
h2_send_goaway(con, e==H2_E_NO_ERROR ? H2_E_NO_ERROR : H2_E_PROTOCOL_ERROR);
|
|
|
|
h2con * const h2c = con->h2;
|
|
|
|
if (0 == h2c->rused) return 0;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
h2_recv_rst_stream (connection * const con, const uint8_t * const s, const uint32_t len)
|
|
|
|
{
|
|
|
|
/*(s must be entire RST_STREAM frame and len the frame length field)*/
|
|
|
|
/*assert(s[3] == H2_FTYPE_RST_STREAM);*/
|
|
|
|
if (4 != len) { /*(RST_STREAM frame length must be 4)*/
|
|
|
|
h2_send_goaway_e(con, H2_E_FRAME_SIZE_ERROR);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const uint32_t id = h2_u31(s+5);
|
|
|
|
if (0 == id) { /*(RST_STREAM id must not be 0)*/
|
|
|
|
h2_send_goaway_e(con, H2_E_PROTOCOL_ERROR);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
h2con * const h2c = con->h2;
|
|
|
|
for (uint32_t i = 0, rused = h2c->rused; i < rused; ++i) {
|
|
|
|
request_st * const r = h2c->r[i];
|
|
|
|
if (r->h2id != id) continue;
|
|
|
|
if (r->h2state == H2_STATE_IDLE) {
|
|
|
|
/*(RST_STREAM must not be for stream in "idle" state)*/
|
|
|
|
h2_send_goaway_e(con, H2_E_PROTOCOL_ERROR);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
/* XXX: ? add debug trace including error code from RST_STREAM ? */
|
|
|
|
r->state = CON_STATE_ERROR;
|
|
|
|
r->h2state = H2_STATE_CLOSED;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
/* unknown/inactive stream id
|
|
|
|
* XXX: how should we handle RST_STREAM for unknown/inactive stream id?
|
|
|
|
* (stream id may have been closed recently and server forgot about it,
|
|
|
|
* but client (peer) sent RST_STREAM prior to receiving stream end from
|
|
|
|
* server)*/
|
|
|
|
#if 0
|
|
|
|
if (h2c->sent_goaway && h2c->h2_cid < id) return;
|
|
|
|
h2_send_goaway_e(con, H2_E_PROTOCOL_ERROR);
|
|
|
|
#else
|
|
|
|
if (h2c->h2_cid < id) {
|
|
|
|
h2_send_goaway_e(con, H2_E_PROTOCOL_ERROR);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
h2_recv_ping (connection * const con, uint8_t * const s, const uint32_t len)
|
|
|
|
{
|
|
|
|
#if 0
|
|
|
|
union {
|
|
|
|
uint8_t c[20];
|
|
|
|
uint32_t u[5]; /*(alignment)*/
|
|
|
|
} ping = { { /*(big-endian numbers)*/
|
|
|
|
0x00, 0x00, 0x00 /* padding for alignment; do not send */
|
|
|
|
/* PING */
|
|
|
|
,0x00, 0x00, 0x08 /* frame length */
|
|
|
|
,H2_FTYPE_PING /* frame type */
|
|
|
|
,H2_FLAG_ACK /* frame flags */
|
|
|
|
,0x00, 0x00, 0x00, 0x00 /* stream identifier */
|
|
|
|
,0x00, 0x00, 0x00, 0x00 /* opaque (fill in below) */
|
|
|
|
,0x00, 0x00, 0x00, 0x00
|
|
|
|
} };
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/*(s must be entire PING frame and len the frame length field)*/
|
|
|
|
/*assert(s[3] == H2_FTYPE_PING);*/
|
|
|
|
if (8 != len) { /*(PING frame length must be 8)*/
|
|
|
|
h2_send_goaway_e(con, H2_E_FRAME_SIZE_ERROR);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
s[5] &= ~0x80; /* reserved bit must be ignored */
|
|
|
|
if (0 != h2_u31(s+5)) { /*(PING stream id must be 0)*/
|
|
|
|
h2_send_goaway_e(con, H2_E_PROTOCOL_ERROR);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (s[4] & H2_FLAG_ACK) /*(ignore; unexpected if we did not send PING)*/
|
|
|
|
return;
|
|
|
|
/* reflect PING back to peer with frame flag ACK */
|
|
|
|
/* (9 byte frame header plus 8 byte PING payload = 17 bytes)*/
|
|
|
|
s[4] = H2_FLAG_ACK;
|
|
|
|
chunkqueue_append_mem(con->write_queue, (const char *)s, 17);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
h2_apply_priority_update (h2con * const h2c, const request_st * const r, const uint32_t rpos)
|
|
|
|
{
|
|
|
|
const request_st ** const rr = (const request_st **)h2c->r;
|
|
|
|
uint32_t npos = rpos;
|
|
|
|
while (npos
|
|
|
|
&& (rr[npos-1]->h2_prio > r->h2_prio
|
|
|
|
|| (rr[npos-1]->h2_prio == r->h2_prio
|
|
|
|
&& rr[npos-1]->h2id > r->h2id)))
|
|
|
|
--npos;
|
|
|
|
if (rpos - npos) {
|
|
|
|
memmove(rr+npos+1, rr+npos, (rpos - npos)*sizeof(request_st *));
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
while (npos+1 < h2c->rused
|
|
|
|
&& (rr[npos+1]->h2_prio < r->h2_prio
|
|
|
|
|| (rr[npos+1]->h2_prio == r->h2_prio
|
|
|
|
&& rr[npos+1]->h2id < r->h2id)))
|
|
|
|
++npos;
|
|
|
|
if (npos - rpos == 0)
|
|
|
|
return; /*(no movement)*/
|
|
|
|
memmove(rr+rpos, rr+rpos+1, (npos - rpos)*sizeof(request_st *));
|
|
|
|
}
|
|
|
|
rr[npos] = r;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
__attribute_noinline__
|
|
|
|
__attribute_nonnull__()
|
|
|
|
__attribute_pure__
|
|
|
|
static uint8_t
|
|
|
|
h2_parse_priority_update (const char * const prio, const uint32_t len)
|
|
|
|
{
|
|
|
|
/* parse priority string (structured field values: dictionary)
|
|
|
|
* (resets urgency (u) and incremental (i) to defaults if len == 0)
|
|
|
|
* (upon parse error, cease parsing and use defaults for remaining items) */
|
|
|
|
int urg = 3, incr = 0;
|
|
|
|
for (uint32_t i = 0; i < len; ++i) {
|
|
|
|
if (prio[i] == ' ' || prio[i] == '\t' || prio[i] == ',') continue;
|
|
|
|
if (prio[i] == 'u') { /* value: 0 - 7 */
|
|
|
|
if (i+2 < len && prio[i+1] == '=') {
|
|
|
|
if ((uint32_t)(prio[i+2] - '0') < 8)
|
|
|
|
urg = prio[i+2] - '0';
|
|
|
|
else
|
|
|
|
break; /* cease parsing if invalid syntax */
|
|
|
|
i += 2;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
break; /* cease parsing if invalid syntax */
|
|
|
|
}
|
|
|
|
if (prio[i] == 'i') { /* value: 0 or 1 (boolean) */
|
|
|
|
if (i+3 < len && prio[i+1] == '=' && prio[i+2] == '?') {
|
|
|
|
if ((uint32_t)(prio[i+3] - '0') <= 1) /* 0 or 1 */
|
|
|
|
incr = prio[i+3] - '0';
|
|
|
|
else
|
|
|
|
break; /* cease parsing if invalid syntax */
|
|
|
|
i += 3;
|
|
|
|
}
|
|
|
|
else if (i+1 == len
|
|
|
|
|| prio[i+1]==' ' || prio[i+1]=='\t' || prio[i+1]==',')
|
|
|
|
incr = 1;
|
|
|
|
else
|
|
|
|
break; /* cease parsing if invalid syntax */
|
|
|
|
}
|
|
|
|
do { ++i; } while (i < len && prio[i] != ','); /*advance to next token*/
|
|
|
|
}
|
|
|
|
/* combine priority 'urgency' value and invert 'incremental' boolean
|
|
|
|
* for easy (ascending) sorting by urgency and then incremental before
|
|
|
|
* non-incremental */
|
|
|
|
return (uint8_t)(urg << 1 | !incr);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
h2_recv_priority_update (connection * const con, const uint8_t * const s, const uint32_t len)
|
|
|
|
{
|
|
|
|
/*(s must be entire PRIORITY_UPDATE frame and len the frame length field)*/
|
|
|
|
/*assert(s[3] == H2_FTYPE_PRIORITY_UPDATE);*/
|
|
|
|
if (len < 4) { /*(PRIORITY_UPDATE frame len must be >=4)*/
|
|
|
|
h2_send_goaway_e(con, H2_E_FRAME_SIZE_ERROR);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const uint32_t id = h2_u31(s+5);
|
|
|
|
if (0 != id) { /*(PRIORITY_UPDATE id must be 0)*/
|
|
|
|
h2_send_goaway_e(con, H2_E_PROTOCOL_ERROR);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const uint32_t prid = h2_u31(s+9);
|
|
|
|
if (0 == prid) { /*(prioritized stream id must not be 0)*/
|
|
|
|
h2_send_goaway_e(con, H2_E_PROTOCOL_ERROR);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
h2con * const h2c = con->h2;
|
|
|
|
for (uint32_t i = 0, rused = h2c->rused; i < rused; ++i) {
|
|
|
|
request_st * const r = h2c->r[i];
|
|
|
|
if (r->h2id != prid) continue;
|
|
|
|
uint8_t prio = h2_parse_priority_update((char *)s+13, len-4);
|
|
|
|
if (r->h2_prio != prio) {
|
|
|
|
r->h2_prio = prio;
|
|
|
|
h2_apply_priority_update(h2c, r, i);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
#if 0
|
|
|
|
/*(note: not checking if prid applies to PUSH_PROMISE ids; unused in h2.c)*/
|
|
|
|
if (h2c->sent_goaway)
|
|
|
|
return;
|
|
|
|
if (h2c->h2_cid < prid) {
|
|
|
|
/* TODO: parse out urgency and incremental values,
|
|
|
|
* and then save for prid of future stream
|
|
|
|
* (see h2_recv_headers() for where to check and apply)
|
|
|
|
* (ignore for now; probably more worthwhile to do in HTTP/3;
|
|
|
|
* in HTTP/2, client might sent PRIORITY_UPDATE before HEADERS,
|
|
|
|
* but that is not handled here, and is not expected since the
|
|
|
|
* Priority request header can be used instead.) */
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
/*(choosing to ignore frames for unmatched prid)*/
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
__attribute_cold__
|
|
|
|
__attribute_noinline__
|
|
|
|
static void
|
|
|
|
h2_recv_priority (connection * const con, const uint8_t * const s, const uint32_t len)
|
|
|
|
{
|
|
|
|
/*(s must be entire PRIORITY frame and len the frame length field)*/
|
|
|
|
/*assert(s[3] == H2_FTYPE_PRIORITY);*/
|
|
|
|
if (5 != len) { /*(PRIORITY frame length must be 5)*/
|
|
|
|
h2_send_goaway_e(con, H2_E_FRAME_SIZE_ERROR);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const uint32_t id = h2_u31(s+5);
|
|
|
|
if (0 == id) { /*(PRIORITY id must not be 0)*/
|
|
|
|
h2_send_goaway_e(con, H2_E_PROTOCOL_ERROR);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const uint32_t prio = h2_u31(s+9);
|
|
|
|
#if 0
|
|
|
|
uint32_t exclusive_dependency = (s[9] & 0x80) ? 1 : 0;
|
|
|
|
/*(ignore dependency prid and exclusive_dependency,
|
|
|
|
* and attempt to scale PRIORITY weight (weight+1 default is 16)
|
|
|
|
* to PRIORITY_UPDATE (default urgency 3) (see h2_init_stream()))*/
|
|
|
|
uint8_t weight = s[13] >> 2;
|
|
|
|
weight = ((weight < 8 ? weight : 7) << 1) | !0;
|
|
|
|
#endif
|
|
|
|
h2con * const h2c = con->h2;
|
|
|
|
for (uint32_t i = 0, rused = h2c->rused; i < rused; ++i) {
|
|
|
|
request_st * const r = h2c->r[i];
|
|
|
|
if (r->h2id != id) continue;
|
|
|
|
/* XXX: TODO: update priority info */
|
|
|
|
if (prio == id) {
|
|
|
|
h2_send_rst_stream(r, con, H2_E_PROTOCOL_ERROR);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
#if 0
|
|
|
|
else if (r->h2_prio != weight) {
|
|
|
|
r->h2_prio = weight;
|
|
|
|
h2_apply_priority_update(h2c, r, i);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
/* XXX: TODO: update priority info for unknown/inactive stream */
|
|
|
|
/*if (h2c->sent_goaway && h2c->h2_cid < id) return;*/
|
|
|
|
if (prio == id) {
|
|
|
|
h2_send_rst_stream_id(id, con, H2_E_PROTOCOL_ERROR);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
h2_recv_window_update (connection * const con, const uint8_t * const s, const uint32_t len)
|
|
|
|
{
|
|
|
|
/*(s must be entire WINDOW_UPDATE frame and len the frame length field)*/
|
|
|
|
/*assert(s[3] == H2_FTYPE_WINDOW_UPDATE);*/
|
|
|
|
if (4 != len) { /*(WINDOW_UPDATE frame length must be 4)*/
|
|
|
|
h2_send_goaway_e(con, H2_E_FRAME_SIZE_ERROR);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const uint32_t id = h2_u31(s+5);
|
|
|
|
const int32_t v = (int32_t)h2_u31(s+9);
|
|
|
|
request_st *r = NULL;
|
|
|
|
if (0 == id)
|
|
|
|
r = &con->request;
|
|
|
|
else {
|
|
|
|
h2con * const h2c = con->h2;
|
|
|
|
for (uint32_t i = 0, rused = h2c->rused; i < rused; ++i) {
|
|
|
|
request_st * const rr = h2c->r[i];
|
|
|
|
if (rr->h2id != id) continue;
|
|
|
|
r = rr;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
/* peer should not send WINDOW_UPDATE for an inactive stream,
|
|
|
|
* but RFC 7540 does not explicitly call this out. On the other hand,
|
|
|
|
* since there may be a temporary mismatch in stream state between
|
|
|
|
* peers, ignore window update if stream id is unknown/inactive.
|
|
|
|
* Also, it is not an error if GOAWAY sent and h2c->h2_cid < id */
|
|
|
|
if (NULL == r) {
|
|
|
|
if (h2c->h2_cid < id && 0 == h2c->sent_goaway)
|
|
|
|
h2_send_goaway_e(con, H2_E_PROTOCOL_ERROR);
|
|
|
|
#if 0
|
|
|
|
/*(needed for h2spec if testing with response < 16k+1 over TLS
|
|
|
|
* or response <= socket send buffer size over cleartext, due to
|
|
|
|
* completing response too quickly for the test frame sequence) */
|
|
|
|
if (v == 0) /* h2spec: 6.9-2 (after we retired id 1) */
|
|
|
|
h2_send_rst_stream_id(id, con, H2_E_PROTOCOL_ERROR);
|
|
|
|
if (v == INT32_MAX)/* h2spec: 6.9.1-3 (after we retired id 1) */
|
|
|
|
h2_send_rst_stream_id(id, con, H2_E_FLOW_CONTROL_ERROR);
|
|
|
|
#endif
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
/* MUST NOT be treated as error if stream is in closed state; ignore */
|
|
|
|
if (r->h2state == H2_STATE_CLOSED
|
|
|
|
|| r->h2state == H2_STATE_HALF_CLOSED_LOCAL) return;
|
|
|
|
}
|
|
|
|
if (0 == v || r->h2_swin > INT32_MAX - v) {
|
|
|
|
request_h2error_t e = (0 == v)
|
|
|
|
? H2_E_PROTOCOL_ERROR
|
|
|
|
: H2_E_FLOW_CONTROL_ERROR;
|
|
|
|
if (0 == id)
|
|
|
|
h2_send_goaway_e(con, e);
|
|
|
|
else
|
|
|
|
h2_send_rst_stream(r, con, e);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
r->h2_swin += v;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
h2_send_window_update (connection * const con, uint32_t h2id, const uint32_t len)
|
|
|
|
{
|
|
|
|
if (0 == len) return;
|
|
|
|
union {
|
|
|
|
uint8_t c[16];
|
|
|
|
uint32_t u[4]; /*(alignment)*/
|
|
|
|
} window_upd = { { /*(big-endian numbers)*/
|
|
|
|
0x00, 0x00, 0x00 /* padding for alignment; do not send */
|
|
|
|
/* WINDOW_UPDATE */
|
|
|
|
,0x00, 0x00, 0x04 /* frame length */
|
|
|
|
,H2_FTYPE_WINDOW_UPDATE /* frame type */
|
|
|
|
,0x00 /* frame flags */
|
|
|
|
,0x00, 0x00, 0x00, 0x00 /* stream identifier (fill in below) */
|
|
|
|
,0x00, 0x00, 0x00, 0x00 /* window update increase (fill in below) */
|
|
|
|
} };
|
|
|
|
|
|
|
|
window_upd.u[2] = htonl(h2id);
|
|
|
|
window_upd.u[3] = htonl(len);
|
|
|
|
chunkqueue_append_mem(con->write_queue, /*(+3 to skip over align padding)*/
|
|
|
|
(const char *)window_upd.c+3, sizeof(window_upd)-3);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
__attribute_noinline__
|
|
|
|
static void
|
|
|
|
h2_send_window_update_unit (connection * const con, request_st * const r, const uint32_t len)
|
|
|
|
{
|
|
|
|
r->h2_rwin_fudge -= (int16_t)len;
|
|
|
|
if (r->h2_rwin_fudge < 0) {
|
|
|
|
r->h2_rwin_fudge += 16384;
|
|
|
|
h2_send_window_update(con, r->h2id, 16384); /*(r->h2_rwin)*/
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
h2_parse_frame_settings (connection * const con, const uint8_t *s, uint32_t len)
|
|
|
|
{
|
|
|
|
/*(s and len must be SETTINGS frame payload)*/
|
|
|
|
/*(caller must validate frame len, frame type == 0x04, frame id == 0)*/
|
|
|
|
h2con * const h2c = con->h2;
|
|
|
|
for (; len >= 6; len -= 6, s += 6) {
|
|
|
|
uint32_t v = h2_u32(s+2);
|
|
|
|
switch (h2_u16(s)) {
|
|
|
|
case H2_SETTINGS_HEADER_TABLE_SIZE:
|
|
|
|
/* encoder may use any table size <= value sent by peer */
|
|
|
|
/* For simple compliance with RFC and constrained memory use,
|
|
|
|
* choose to not increase table size beyond the default 4096,
|
|
|
|
* but allow smaller sizes to be set and then reset up to 4096,
|
|
|
|
* e.g. set to 0 to evict all dynamic table entries,
|
|
|
|
* and then set to 4096 to restore dynamic table use */
|
|
|
|
if (v > 4096) v = 4096;
|
|
|
|
if (v == h2c->s_header_table_size) break;
|
|
|
|
h2c->s_header_table_size = v;
|
|
|
|
lshpack_enc_set_max_capacity(&h2c->encoder, v);
|
|
|
|
break;
|
|
|
|
case H2_SETTINGS_ENABLE_PUSH:
|
|
|
|
if ((v|1) != 1) { /*(v == 0 || v == 1)*/
|
|
|
|
h2_send_goaway_e(con, H2_E_PROTOCOL_ERROR);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
h2c->s_enable_push = v;
|
|
|
|
break;
|
|
|
|
case H2_SETTINGS_MAX_CONCURRENT_STREAMS:
|
|
|
|
h2c->s_max_concurrent_streams = v;
|
|
|
|
break;
|
|
|
|
case H2_SETTINGS_INITIAL_WINDOW_SIZE:
|
|
|
|
if (v > INT32_MAX) { /*(2^31 - 1)*/
|
|
|
|
h2_send_goaway_e(con, H2_E_FLOW_CONTROL_ERROR);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else if (h2c->rused) { /*(update existing streams)*/
|
|
|
|
/*(underflow is ok; unsigned integer math)*/
|
|
|
|
/*(h2c->s_initial_window_size is >= 0)*/
|
|
|
|
int32_t diff =
|
|
|
|
(int32_t)((uint32_t)v - (uint32_t)h2c->s_initial_window_size);
|
|
|
|
for (uint32_t i = 0, rused = h2c->rused; i < rused; ++i) {
|
|
|
|
request_st * const r = h2c->r[i];
|
|
|
|
const int32_t swin = r->h2_swin;
|
|
|
|
if (r->h2state == H2_STATE_HALF_CLOSED_LOCAL
|
|
|
|
|| r->h2state == H2_STATE_CLOSED) continue;
|
|
|
|
if (diff >= 0
|
|
|
|
? swin > INT32_MAX - diff
|
|
|
|
: swin < INT32_MIN - diff) {
|
|
|
|
h2_send_rst_stream(r, con, H2_E_FLOW_CONTROL_ERROR);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
r->h2_swin += diff;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
h2c->s_initial_window_size = (int32_t)v;
|
|
|
|
break;
|
|
|
|
case H2_SETTINGS_MAX_FRAME_SIZE:
|
|
|
|
if (v < 16384 || v > 16777215) { /*[(2^14),(2^24-1)]*/
|
|
|
|
h2_send_goaway_e(con, H2_E_PROTOCOL_ERROR);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
h2c->s_max_frame_size = v;
|
|
|
|
break;
|
|
|
|
case H2_SETTINGS_MAX_HEADER_LIST_SIZE:
|
|
|
|
h2c->s_max_header_list_size = v;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|