| /** @file |
| * |
| * Fundamental HTTP/2 protocol definitions and parsers. |
| * |
| * @section license License |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "HTTP2.h" |
| #include "HPACK.h" |
| #include "tscore/ink_assert.h" |
| #include "records/P_RecCore.h" |
| #include "records/P_RecProcess.h" |
| |
| const char *const HTTP2_CONNECTION_PREFACE = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"; |
| |
| // Constant strings for pseudo headers |
| const char *HTTP2_VALUE_SCHEME = ":scheme"; |
| const char *HTTP2_VALUE_METHOD = ":method"; |
| const char *HTTP2_VALUE_AUTHORITY = ":authority"; |
| const char *HTTP2_VALUE_PATH = ":path"; |
| const char *HTTP2_VALUE_STATUS = ":status"; |
| |
| const unsigned HTTP2_LEN_SCHEME = countof(":scheme") - 1; |
| const unsigned HTTP2_LEN_METHOD = countof(":method") - 1; |
| const unsigned HTTP2_LEN_AUTHORITY = countof(":authority") - 1; |
| const unsigned HTTP2_LEN_PATH = countof(":path") - 1; |
| const unsigned HTTP2_LEN_STATUS = countof(":status") - 1; |
| |
| static size_t HTTP2_LEN_STATUS_VALUE_STR = 3; |
| static const uint32_t HTTP2_MAX_TABLE_SIZE_LIMIT = 64 * 1024; |
| |
| // Statistics |
| RecRawStatBlock *http2_rsb; |
| static const char *const HTTP2_STAT_CURRENT_CLIENT_SESSION_NAME = "proxy.process.http2.current_client_sessions"; // DEPRECATED |
| static const char *const HTTP2_STAT_CURRENT_CLIENT_CONNECTION_NAME = "proxy.process.http2.current_client_connections"; |
| static const char *const HTTP2_STAT_CURRENT_ACTIVE_CLIENT_CONNECTION_NAME = "proxy.process.http2.current_active_client_connections"; |
| static const char *const HTTP2_STAT_CURRENT_CLIENT_STREAM_NAME = "proxy.process.http2.current_client_streams"; |
| static const char *const HTTP2_STAT_TOTAL_CLIENT_STREAM_NAME = "proxy.process.http2.total_client_streams"; |
| static const char *const HTTP2_STAT_TOTAL_TRANSACTIONS_TIME_NAME = "proxy.process.http2.total_transactions_time"; |
| static const char *const HTTP2_STAT_TOTAL_CLIENT_CONNECTION_NAME = "proxy.process.http2.total_client_connections"; |
| static const char *const HTTP2_STAT_CONNECTION_ERRORS_NAME = "proxy.process.http2.connection_errors"; |
| static const char *const HTTP2_STAT_STREAM_ERRORS_NAME = "proxy.process.http2.stream_errors"; |
| static const char *const HTTP2_STAT_SESSION_DIE_DEFAULT_NAME = "proxy.process.http2.session_die_default"; |
| static const char *const HTTP2_STAT_SESSION_DIE_OTHER_NAME = "proxy.process.http2.session_die_other"; |
| static const char *const HTTP2_STAT_SESSION_DIE_ACTIVE_NAME = "proxy.process.http2.session_die_active"; |
| static const char *const HTTP2_STAT_SESSION_DIE_INACTIVE_NAME = "proxy.process.http2.session_die_inactive"; |
| static const char *const HTTP2_STAT_SESSION_DIE_EOS_NAME = "proxy.process.http2.session_die_eos"; |
| static const char *const HTTP2_STAT_SESSION_DIE_ERROR_NAME = "proxy.process.http2.session_die_error"; |
| static const char *const HTTP2_STAT_SESSION_DIE_HIGH_ERROR_RATE_NAME = "proxy.process.http2.session_die_high_error_rate"; |
| static const char *const HTTP2_STAT_MAX_SETTINGS_PER_FRAME_EXCEEDED_NAME = "proxy.process.http2.max_settings_per_frame_exceeded"; |
| static const char *const HTTP2_STAT_MAX_SETTINGS_PER_MINUTE_EXCEEDED_NAME = "proxy.process.http2.max_settings_per_minute_exceeded"; |
| static const char *const HTTP2_STAT_MAX_SETTINGS_FRAMES_PER_MINUTE_EXCEEDED_NAME = |
| "proxy.process.http2.max_settings_frames_per_minute_exceeded"; |
| static const char *const HTTP2_STAT_MAX_PING_FRAMES_PER_MINUTE_EXCEEDED_NAME = |
| "proxy.process.http2.max_ping_frames_per_minute_exceeded"; |
| static const char *const HTTP2_STAT_MAX_PRIORITY_FRAMES_PER_MINUTE_EXCEEDED_NAME = |
| "proxy.process.http2.max_priority_frames_per_minute_exceeded"; |
| static const char *const HTTP2_STAT_INSUFFICIENT_AVG_WINDOW_UPDATE_NAME = "proxy.process.http2.insufficient_avg_window_update"; |
| |
| union byte_pointer { |
| byte_pointer(void *p) : ptr(p) {} |
| void *ptr; |
| uint8_t *u8; |
| uint16_t *u16; |
| uint32_t *u32; |
| }; |
| |
| template <typename T> union byte_addressable_value { |
| uint8_t bytes[sizeof(T)]; |
| T value; |
| }; |
| |
| static void |
| write_and_advance(byte_pointer &dst, const uint8_t *src, size_t length) |
| { |
| memcpy(dst.u8, src, length); |
| dst.u8 += length; |
| } |
| |
| static void |
| write_and_advance(byte_pointer &dst, uint32_t src) |
| { |
| byte_addressable_value<uint32_t> pval; |
| |
| pval.value = htonl(src); |
| memcpy(dst.u8, pval.bytes, sizeof(pval.bytes)); |
| dst.u8 += sizeof(pval.bytes); |
| } |
| |
| static void |
| write_and_advance(byte_pointer &dst, uint16_t src) |
| { |
| byte_addressable_value<uint16_t> pval; |
| |
| pval.value = htons(src); |
| memcpy(dst.u8, pval.bytes, sizeof(pval.bytes)); |
| dst.u8 += sizeof(pval.bytes); |
| } |
| |
| static void |
| write_and_advance(byte_pointer &dst, uint8_t src) |
| { |
| *dst.u8 = src; |
| dst.u8++; |
| } |
| |
| template <unsigned N> |
| static void |
| memcpy_and_advance(uint8_t (&dst)[N], byte_pointer &src) |
| { |
| memcpy(dst, src.u8, N); |
| src.u8 += N; |
| } |
| |
| static void |
| memcpy_and_advance(uint8_t(&dst), byte_pointer &src) |
| { |
| dst = *src.u8; |
| ++src.u8; |
| } |
| |
| bool |
| http2_frame_header_is_valid(const Http2FrameHeader &hdr, unsigned max_frame_size) |
| { |
| // 6.1 If a DATA frame is received whose stream identifier field is 0x0, the recipient MUST |
| // respond with a connection error (Section 5.4.1) of type PROTOCOL_ERROR. |
| if (hdr.type == HTTP2_FRAME_TYPE_DATA && hdr.streamid == 0) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool |
| http2_settings_parameter_is_valid(const Http2SettingsParameter ¶m) |
| { |
| // Static maximum values for Settings parameters. |
| static const uint32_t settings_max[HTTP2_SETTINGS_MAX] = { |
| 0, |
| UINT_MAX, // HTTP2_SETTINGS_HEADER_TABLE_SIZE |
| 1, // HTTP2_SETTINGS_ENABLE_PUSH |
| UINT_MAX, // HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS |
| HTTP2_MAX_WINDOW_SIZE, // HTTP2_SETTINGS_INITIAL_WINDOW_SIZE |
| 16777215, // HTTP2_SETTINGS_MAX_FRAME_SIZE |
| UINT_MAX, // HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE |
| }; |
| |
| if (param.id == 0 || param.id >= HTTP2_SETTINGS_MAX) { |
| // Do nothing - 6.5.2 Unsupported parameters MUST be ignored |
| return true; |
| } |
| |
| if (param.value > settings_max[param.id]) { |
| return false; |
| } |
| |
| if (param.id == HTTP2_SETTINGS_ENABLE_PUSH && param.value != 0 && param.value != 1) { |
| return false; |
| } |
| |
| if (param.id == HTTP2_SETTINGS_MAX_FRAME_SIZE && (param.value < (1 << 14) || param.value > (1 << 24) - 1)) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| // 4.1. Frame Format |
| // |
| // 0 1 2 3 |
| // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 |
| // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| // | Length (24) | |
| // +---------------+---------------+---------------+ |
| // | Type (8) | Flags (8) | |
| // +-+-+-----------+---------------+-------------------------------+ |
| // |R| Stream Identifier (31) | |
| // +=+=============================================================+ |
| // | Frame Payload (0...) ... |
| // +---------------------------------------------------------------+ |
| |
| bool |
| http2_parse_frame_header(IOVec iov, Http2FrameHeader &hdr) |
| { |
| byte_pointer ptr(iov.iov_base); |
| byte_addressable_value<uint32_t> length_and_type; |
| byte_addressable_value<uint32_t> streamid; |
| |
| if (unlikely(iov.iov_len < HTTP2_FRAME_HEADER_LEN)) { |
| return false; |
| } |
| |
| memcpy_and_advance(length_and_type.bytes, ptr); |
| memcpy_and_advance(hdr.flags, ptr); |
| memcpy_and_advance(streamid.bytes, ptr); |
| |
| hdr.length = ntohl(length_and_type.value) >> 8; |
| hdr.type = ntohl(length_and_type.value) & 0xff; |
| streamid.bytes[0] &= 0x7f; // Clear the high reserved bit |
| hdr.streamid = ntohl(streamid.value); |
| |
| return true; |
| } |
| |
| bool |
| http2_write_frame_header(const Http2FrameHeader &hdr, IOVec iov) |
| { |
| byte_pointer ptr(iov.iov_base); |
| |
| if (unlikely(iov.iov_len < HTTP2_FRAME_HEADER_LEN)) { |
| return false; |
| } |
| |
| byte_addressable_value<uint32_t> length; |
| length.value = htonl(hdr.length); |
| // MSB length.bytes[0] is unused. |
| write_and_advance(ptr, length.bytes[1]); |
| write_and_advance(ptr, length.bytes[2]); |
| write_and_advance(ptr, length.bytes[3]); |
| |
| write_and_advance(ptr, hdr.type); |
| write_and_advance(ptr, hdr.flags); |
| write_and_advance(ptr, hdr.streamid); |
| |
| return true; |
| } |
| |
| bool |
| http2_write_rst_stream(uint32_t error_code, IOVec iov) |
| { |
| byte_pointer ptr(iov.iov_base); |
| |
| write_and_advance(ptr, error_code); |
| |
| return true; |
| } |
| |
| bool |
| http2_write_settings(const Http2SettingsParameter ¶m, const IOVec &iov) |
| { |
| byte_pointer ptr(iov.iov_base); |
| |
| if (unlikely(iov.iov_len < HTTP2_SETTINGS_PARAMETER_LEN)) { |
| return false; |
| } |
| |
| write_and_advance(ptr, param.id); |
| write_and_advance(ptr, param.value); |
| |
| return true; |
| } |
| |
| bool |
| http2_write_ping(const uint8_t *opaque_data, IOVec iov) |
| { |
| byte_pointer ptr(iov.iov_base); |
| |
| if (unlikely(iov.iov_len < HTTP2_PING_LEN)) { |
| return false; |
| } |
| |
| write_and_advance(ptr, opaque_data, HTTP2_PING_LEN); |
| |
| return true; |
| } |
| |
| bool |
| http2_write_goaway(const Http2Goaway &goaway, IOVec iov) |
| { |
| byte_pointer ptr(iov.iov_base); |
| |
| if (unlikely(iov.iov_len < HTTP2_GOAWAY_LEN)) { |
| return false; |
| } |
| |
| write_and_advance(ptr, goaway.last_streamid); |
| write_and_advance(ptr, static_cast<uint32_t>(goaway.error_code)); |
| |
| return true; |
| } |
| |
| bool |
| http2_write_window_update(const uint32_t new_size, const IOVec &iov) |
| { |
| byte_pointer ptr(iov.iov_base); |
| write_and_advance(ptr, new_size); |
| |
| return true; |
| } |
| |
| bool |
| http2_write_push_promise(const Http2PushPromise &push_promise, const uint8_t *src, size_t length, const IOVec &iov) |
| { |
| byte_pointer ptr(iov.iov_base); |
| write_and_advance(ptr, push_promise.promised_streamid); |
| write_and_advance(ptr, src, length); |
| return true; |
| } |
| |
| bool |
| http2_parse_headers_parameter(IOVec iov, Http2HeadersParameter ¶ms) |
| { |
| byte_pointer ptr(iov.iov_base); |
| memcpy_and_advance(params.pad_length, ptr); |
| |
| return true; |
| } |
| |
| bool |
| http2_parse_priority_parameter(IOVec iov, Http2Priority &priority) |
| { |
| byte_pointer ptr(iov.iov_base); |
| byte_addressable_value<uint32_t> dependency; |
| |
| memcpy_and_advance(dependency.bytes, ptr); |
| |
| priority.exclusive_flag = dependency.bytes[0] & 0x80; |
| |
| dependency.bytes[0] &= 0x7f; // Clear the highest bit for exclusive flag |
| priority.stream_dependency = ntohl(dependency.value); |
| |
| memcpy_and_advance(priority.weight, ptr); |
| |
| return true; |
| } |
| |
| bool |
| http2_parse_rst_stream(IOVec iov, Http2RstStream &rst_stream) |
| { |
| byte_pointer ptr(iov.iov_base); |
| byte_addressable_value<uint32_t> ec; |
| |
| memcpy_and_advance(ec.bytes, ptr); |
| |
| rst_stream.error_code = ntohl(ec.value); |
| |
| return true; |
| } |
| |
| bool |
| http2_parse_settings_parameter(IOVec iov, Http2SettingsParameter ¶m) |
| { |
| byte_pointer ptr(iov.iov_base); |
| byte_addressable_value<uint16_t> pid; |
| byte_addressable_value<uint32_t> pval; |
| |
| if (unlikely(iov.iov_len < HTTP2_SETTINGS_PARAMETER_LEN)) { |
| return false; |
| } |
| |
| memcpy_and_advance(pid.bytes, ptr); |
| memcpy_and_advance(pval.bytes, ptr); |
| |
| param.id = ntohs(pid.value); |
| param.value = ntohl(pval.value); |
| |
| return true; |
| } |
| |
| bool |
| http2_parse_goaway(IOVec iov, Http2Goaway &goaway) |
| { |
| byte_pointer ptr(iov.iov_base); |
| byte_addressable_value<uint32_t> sid; |
| byte_addressable_value<uint32_t> ec; |
| |
| memcpy_and_advance(sid.bytes, ptr); |
| memcpy_and_advance(ec.bytes, ptr); |
| |
| goaway.last_streamid = ntohl(sid.value); |
| goaway.error_code = static_cast<Http2ErrorCode>(ntohl(ec.value)); |
| return true; |
| } |
| |
| bool |
| http2_parse_window_update(IOVec iov, uint32_t &size) |
| { |
| byte_pointer ptr(iov.iov_base); |
| byte_addressable_value<uint32_t> s; |
| |
| memcpy_and_advance(s.bytes, ptr); |
| |
| size = ntohl(s.value); |
| |
| return true; |
| } |
| |
| ParseResult |
| http2_convert_header_from_2_to_1_1(HTTPHdr *headers) |
| { |
| MIMEField *field; |
| |
| ink_assert(http_hdr_type_get(headers->m_http) != HTTP_TYPE_UNKNOWN); |
| |
| if (http_hdr_type_get(headers->m_http) == HTTP_TYPE_REQUEST) { |
| const char *scheme, *authority, *path, *method; |
| int scheme_len, authority_len, path_len, method_len; |
| |
| // Get values of :scheme, :authority and :path to assemble requested URL |
| if ((field = headers->field_find(HTTP2_VALUE_SCHEME, HTTP2_LEN_SCHEME)) != nullptr && field->value_is_valid()) { |
| scheme = field->value_get(&scheme_len); |
| } else { |
| return PARSE_RESULT_ERROR; |
| } |
| |
| if ((field = headers->field_find(HTTP2_VALUE_AUTHORITY, HTTP2_LEN_AUTHORITY)) != nullptr && field->value_is_valid()) { |
| authority = field->value_get(&authority_len); |
| } else { |
| return PARSE_RESULT_ERROR; |
| } |
| |
| if ((field = headers->field_find(HTTP2_VALUE_PATH, HTTP2_LEN_PATH)) != nullptr && field->value_is_valid()) { |
| path = field->value_get(&path_len); |
| } else { |
| return PARSE_RESULT_ERROR; |
| } |
| |
| // Parse URL |
| Arena arena; |
| size_t url_length = scheme_len + 3 + authority_len + path_len; |
| char *url = arena.str_alloc(url_length); |
| const char *url_start = url; |
| |
| memcpy(url, scheme, scheme_len); |
| memcpy(url + scheme_len, "://", 3); |
| memcpy(url + scheme_len + 3, authority, authority_len); |
| memcpy(url + scheme_len + 3 + authority_len, path, path_len); |
| url_parse(headers->m_heap, headers->m_http->u.req.m_url_impl, &url_start, url + url_length, true); |
| arena.str_free(url); |
| |
| // Get value of :method |
| if ((field = headers->field_find(HTTP2_VALUE_METHOD, HTTP2_LEN_METHOD)) != nullptr && field->value_is_valid()) { |
| method = field->value_get(&method_len); |
| |
| int method_wks_idx = hdrtoken_tokenize(method, method_len); |
| http_hdr_method_set(headers->m_heap, headers->m_http, method, method_wks_idx, method_len, false); |
| } else { |
| return PARSE_RESULT_ERROR; |
| } |
| |
| // Combine Cookie headers ([RFC 7540] 8.1.2.5.) |
| field = headers->field_find(MIME_FIELD_COOKIE, MIME_LEN_COOKIE); |
| if (field) { |
| headers->field_combine_dups(field, true, ';'); |
| } |
| |
| // Convert HTTP version to 1.1 |
| int32_t version = HTTP_VERSION(1, 1); |
| http_hdr_version_set(headers->m_http, version); |
| |
| // Remove HTTP/2 style headers |
| headers->field_delete(HTTP2_VALUE_SCHEME, HTTP2_LEN_SCHEME); |
| headers->field_delete(HTTP2_VALUE_METHOD, HTTP2_LEN_METHOD); |
| headers->field_delete(HTTP2_VALUE_AUTHORITY, HTTP2_LEN_AUTHORITY); |
| headers->field_delete(HTTP2_VALUE_PATH, HTTP2_LEN_PATH); |
| } else { |
| int status_len; |
| const char *status; |
| |
| if ((field = headers->field_find(HTTP2_VALUE_STATUS, HTTP2_LEN_STATUS)) != nullptr) { |
| status = field->value_get(&status_len); |
| headers->status_set(http_parse_status(status, status + status_len)); |
| } else { |
| return PARSE_RESULT_ERROR; |
| } |
| |
| // Remove HTTP/2 style headers |
| headers->field_delete(HTTP2_VALUE_STATUS, HTTP2_LEN_STATUS); |
| } |
| |
| // Check validity of all names and values |
| MIMEFieldIter iter; |
| for (const MIMEField *field = headers->iter_get_first(&iter); field != nullptr; field = headers->iter_get_next(&iter)) { |
| if (!field->name_is_valid() || !field->value_is_valid()) { |
| return PARSE_RESULT_ERROR; |
| } |
| } |
| |
| return PARSE_RESULT_DONE; |
| } |
| |
| void |
| http2_generate_h2_header_from_1_1(HTTPHdr *headers, HTTPHdr *h2_headers) |
| { |
| h2_headers->create(http_hdr_type_get(headers->m_http)); |
| |
| if (http_hdr_type_get(headers->m_http) == HTTP_TYPE_RESPONSE) { |
| // Add ':status' header field |
| char status_str[HTTP2_LEN_STATUS_VALUE_STR + 1]; |
| snprintf(status_str, sizeof(status_str), "%d", headers->status_get()); |
| MIMEField *status_field = h2_headers->field_create(HTTP2_VALUE_STATUS, HTTP2_LEN_STATUS); |
| status_field->value_set(h2_headers->m_heap, h2_headers->m_mime, status_str, HTTP2_LEN_STATUS_VALUE_STR); |
| h2_headers->field_attach(status_field); |
| |
| } else if (http_hdr_type_get(headers->m_http) == HTTP_TYPE_REQUEST) { |
| MIMEField *field; |
| const char *value; |
| int value_len; |
| |
| // Add ':authority' header field |
| field = h2_headers->field_create(HTTP2_VALUE_AUTHORITY, HTTP2_LEN_AUTHORITY); |
| value = headers->host_get(&value_len); |
| if (headers->is_port_in_header()) { |
| int port = headers->port_get(); |
| char *host_and_port = (char *)ats_malloc(value_len + 8); |
| value_len = snprintf(host_and_port, value_len + 8, "%.*s:%d", value_len, value, port); |
| field->value_set(h2_headers->m_heap, h2_headers->m_mime, host_and_port, value_len); |
| ats_free(host_and_port); |
| } else { |
| field->value_set(h2_headers->m_heap, h2_headers->m_mime, value, value_len); |
| } |
| h2_headers->field_attach(field); |
| |
| // Add ':method' header field |
| field = h2_headers->field_create(HTTP2_VALUE_METHOD, HTTP2_LEN_METHOD); |
| value = headers->method_get(&value_len); |
| field->value_set(h2_headers->m_heap, h2_headers->m_mime, value, value_len); |
| h2_headers->field_attach(field); |
| |
| // Add ':path' header field |
| field = h2_headers->field_create(HTTP2_VALUE_PATH, HTTP2_LEN_PATH); |
| value = headers->path_get(&value_len); |
| char *path = (char *)ats_malloc(value_len + 1); |
| path[0] = '/'; |
| memcpy(path + 1, value, value_len); |
| field->value_set(h2_headers->m_heap, h2_headers->m_mime, path, value_len + 1); |
| ats_free(path); |
| h2_headers->field_attach(field); |
| |
| // Add ':scheme' header field |
| field = h2_headers->field_create(HTTP2_VALUE_SCHEME, HTTP2_LEN_SCHEME); |
| value = headers->scheme_get(&value_len); |
| field->value_set(h2_headers->m_heap, h2_headers->m_mime, value, value_len); |
| h2_headers->field_attach(field); |
| } |
| |
| // Copy headers |
| // Intermediaries SHOULD remove connection-specific header fields. |
| MIMEFieldIter field_iter; |
| for (MIMEField *field = headers->iter_get_first(&field_iter); field != nullptr; field = headers->iter_get_next(&field_iter)) { |
| const char *name; |
| int name_len; |
| const char *value; |
| int value_len; |
| name = field->name_get(&name_len); |
| if ((name_len == MIME_LEN_CONNECTION && strncasecmp(name, MIME_FIELD_CONNECTION, name_len) == 0) || |
| (name_len == MIME_LEN_KEEP_ALIVE && strncasecmp(name, MIME_FIELD_KEEP_ALIVE, name_len) == 0) || |
| (name_len == MIME_LEN_PROXY_CONNECTION && strncasecmp(name, MIME_FIELD_PROXY_CONNECTION, name_len) == 0) || |
| (name_len == MIME_LEN_TRANSFER_ENCODING && strncasecmp(name, MIME_FIELD_TRANSFER_ENCODING, name_len) == 0) || |
| (name_len == MIME_LEN_UPGRADE && strncasecmp(name, MIME_FIELD_UPGRADE, name_len) == 0)) { |
| continue; |
| } |
| MIMEField *newfield; |
| name = field->name_get(&name_len); |
| newfield = h2_headers->field_create(name, name_len); |
| value = field->value_get(&value_len); |
| newfield->value_set(h2_headers->m_heap, h2_headers->m_mime, value, value_len); |
| h2_headers->field_attach(newfield); |
| } |
| } |
| |
| Http2ErrorCode |
| http2_encode_header_blocks(HTTPHdr *in, uint8_t *out, uint32_t out_len, uint32_t *len_written, HpackHandle &handle, |
| int32_t maximum_table_size) |
| { |
| // Limit the maximum table size to the configured value or 64kB at maximum, which is the size advertised by major clients |
| maximum_table_size = |
| std::min(maximum_table_size, static_cast<int32_t>(std::min(Http2::header_table_size_limit, HTTP2_MAX_TABLE_SIZE_LIMIT))); |
| // Set maximum table size only if it is different from current maximum size |
| if (maximum_table_size == hpack_get_maximum_table_size(handle)) { |
| maximum_table_size = -1; |
| } |
| // TODO: It would be better to split Cookie header value |
| int64_t result = hpack_encode_header_block(handle, out, out_len, in, maximum_table_size); |
| if (result < 0) { |
| return Http2ErrorCode::HTTP2_ERROR_COMPRESSION_ERROR; |
| } |
| if (len_written) { |
| *len_written = result; |
| } |
| return Http2ErrorCode::HTTP2_ERROR_NO_ERROR; |
| } |
| |
| /* |
| * Decode Header Blocks to Header List. |
| */ |
| Http2ErrorCode |
| http2_decode_header_blocks(HTTPHdr *hdr, const uint8_t *buf_start, const uint32_t buf_len, uint32_t *len_read, HpackHandle &handle, |
| bool &trailing_header, uint32_t maximum_table_size) |
| { |
| const MIMEField *field; |
| const char *value; |
| int len; |
| bool is_trailing_header = trailing_header; |
| int64_t result = hpack_decode_header_block(handle, hdr, buf_start, buf_len, Http2::max_header_list_size, maximum_table_size); |
| |
| if (result < 0) { |
| if (result == HPACK_ERROR_COMPRESSION_ERROR) { |
| return Http2ErrorCode::HTTP2_ERROR_COMPRESSION_ERROR; |
| } else if (result == HPACK_ERROR_SIZE_EXCEEDED_ERROR) { |
| return Http2ErrorCode::HTTP2_ERROR_ENHANCE_YOUR_CALM; |
| } |
| |
| return Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR; |
| } |
| if (len_read) { |
| *len_read = result; |
| } |
| |
| MIMEFieldIter iter; |
| unsigned int expected_pseudo_header_count = 4; |
| unsigned int pseudo_header_count = 0; |
| |
| if (is_trailing_header) { |
| expected_pseudo_header_count = 0; |
| } |
| for (field = hdr->iter_get_first(&iter); field != nullptr; field = hdr->iter_get_next(&iter)) { |
| value = field->name_get(&len); |
| // Pseudo headers must appear before regular headers |
| if (len && value[0] == ':') { |
| ++pseudo_header_count; |
| if (pseudo_header_count > expected_pseudo_header_count) { |
| return Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR; |
| } |
| } else if (len <= 0) { |
| return Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR; |
| } else { |
| if (pseudo_header_count != expected_pseudo_header_count) { |
| return Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR; |
| } |
| } |
| // Check whether header field name is lower case |
| // This check should be here but it will fail because WKSs in MIMEField is old fashioned. |
| // for (uint32_t i = 0; i < len; ++i) { |
| // if (ParseRules::is_upalpha(value[i])) { |
| // return Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR; |
| // } |
| // } |
| } |
| |
| // rfc7540,sec8.1.2.2: Any message containing connection-specific header |
| // fields MUST be treated as malformed |
| if (hdr->field_find(MIME_FIELD_CONNECTION, MIME_LEN_CONNECTION) != nullptr || |
| hdr->field_find(MIME_FIELD_KEEP_ALIVE, MIME_LEN_KEEP_ALIVE) != nullptr || |
| hdr->field_find(MIME_FIELD_PROXY_CONNECTION, MIME_LEN_PROXY_CONNECTION) != nullptr || |
| hdr->field_find(MIME_FIELD_TRANSFER_ENCODING, MIME_LEN_TRANSFER_ENCODING) != nullptr || |
| hdr->field_find(MIME_FIELD_UPGRADE, MIME_LEN_UPGRADE) != nullptr) { |
| return Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR; |
| } |
| |
| // :path pseudo header MUST NOT empty for http or https URIs |
| field = hdr->field_find(HTTP2_VALUE_PATH, HTTP2_LEN_PATH); |
| if (field) { |
| field->value_get(&len); |
| if (len == 0) { |
| return Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR; |
| } |
| } |
| |
| // turn on that we have a trailer header |
| const char trailer_name[] = "trailer"; |
| field = hdr->field_find(trailer_name, sizeof(trailer_name) - 1); |
| if (field) { |
| trailing_header = true; |
| } |
| |
| // when The TE header field is received, it MUST NOT contain any |
| // value other than "trailers". |
| field = hdr->field_find(MIME_FIELD_TE, MIME_LEN_TE); |
| if (field) { |
| value = field->value_get(&len); |
| if (!(len == 8 && memcmp(value, "trailers", 8) == 0)) { |
| return Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR; |
| } |
| } |
| |
| if (!is_trailing_header) { |
| // Check pseudo headers |
| if (hdr->fields_count() >= 4) { |
| if (hdr->field_find(HTTP2_VALUE_SCHEME, HTTP2_LEN_SCHEME) == nullptr || |
| hdr->field_find(HTTP2_VALUE_METHOD, HTTP2_LEN_METHOD) == nullptr || |
| hdr->field_find(HTTP2_VALUE_PATH, HTTP2_LEN_PATH) == nullptr || |
| hdr->field_find(HTTP2_VALUE_AUTHORITY, HTTP2_LEN_AUTHORITY) == nullptr || |
| hdr->field_find(HTTP2_VALUE_STATUS, HTTP2_LEN_STATUS) != nullptr) { |
| // Decoded header field is invalid |
| return Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR; |
| } |
| } else { |
| // Pseudo headers is insufficient |
| return Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR; |
| } |
| } |
| |
| return Http2ErrorCode::HTTP2_ERROR_NO_ERROR; |
| } |
| |
| // Initialize this subsystem with librecords configs (for now) |
| uint32_t Http2::max_concurrent_streams_in = 100; |
| uint32_t Http2::min_concurrent_streams_in = 10; |
| uint32_t Http2::max_active_streams_in = 0; |
| bool Http2::throttling = false; |
| uint32_t Http2::stream_priority_enabled = 0; |
| uint32_t Http2::initial_window_size = 65535; |
| uint32_t Http2::max_frame_size = 16384; |
| uint32_t Http2::header_table_size = 4096; |
| uint32_t Http2::max_header_list_size = 4294967295; |
| uint32_t Http2::accept_no_activity_timeout = 120; |
| uint32_t Http2::no_activity_timeout_in = 120; |
| uint32_t Http2::active_timeout_in = 0; |
| uint32_t Http2::push_diary_size = 256; |
| uint32_t Http2::zombie_timeout_in = 0; |
| float Http2::stream_error_rate_threshold = 0.1; |
| uint32_t Http2::max_settings_per_frame = 7; |
| uint32_t Http2::max_settings_per_minute = 14; |
| uint32_t Http2::max_settings_frames_per_minute = 14; |
| uint32_t Http2::max_ping_frames_per_minute = 60; |
| uint32_t Http2::max_priority_frames_per_minute = 120; |
| float Http2::min_avg_window_update = 2560.0; |
| uint32_t Http2::con_slow_log_threshold = 0; |
| uint32_t Http2::stream_slow_log_threshold = 0; |
| uint32_t Http2::header_table_size_limit = 65536; |
| |
| void |
| Http2::init() |
| { |
| REC_EstablishStaticConfigInt32U(max_concurrent_streams_in, "proxy.config.http2.max_concurrent_streams_in"); |
| REC_EstablishStaticConfigInt32U(min_concurrent_streams_in, "proxy.config.http2.min_concurrent_streams_in"); |
| REC_EstablishStaticConfigInt32U(max_active_streams_in, "proxy.config.http2.max_active_streams_in"); |
| REC_EstablishStaticConfigInt32U(stream_priority_enabled, "proxy.config.http2.stream_priority_enabled"); |
| REC_EstablishStaticConfigInt32U(initial_window_size, "proxy.config.http2.initial_window_size_in"); |
| REC_EstablishStaticConfigInt32U(max_frame_size, "proxy.config.http2.max_frame_size"); |
| REC_EstablishStaticConfigInt32U(header_table_size, "proxy.config.http2.header_table_size"); |
| REC_EstablishStaticConfigInt32U(max_header_list_size, "proxy.config.http2.max_header_list_size"); |
| REC_EstablishStaticConfigInt32U(accept_no_activity_timeout, "proxy.config.http2.accept_no_activity_timeout"); |
| REC_EstablishStaticConfigInt32U(no_activity_timeout_in, "proxy.config.http2.no_activity_timeout_in"); |
| REC_EstablishStaticConfigInt32U(active_timeout_in, "proxy.config.http2.active_timeout_in"); |
| REC_EstablishStaticConfigInt32U(push_diary_size, "proxy.config.http2.push_diary_size"); |
| REC_EstablishStaticConfigInt32U(zombie_timeout_in, "proxy.config.http2.zombie_debug_timeout_in"); |
| REC_EstablishStaticConfigFloat(stream_error_rate_threshold, "proxy.config.http2.stream_error_rate_threshold"); |
| REC_EstablishStaticConfigInt32U(max_settings_per_frame, "proxy.config.http2.max_settings_per_frame"); |
| REC_EstablishStaticConfigInt32U(max_settings_per_minute, "proxy.config.http2.max_settings_per_minute"); |
| REC_EstablishStaticConfigInt32U(max_settings_frames_per_minute, "proxy.config.http2.max_settings_frames_per_minute"); |
| REC_EstablishStaticConfigInt32U(max_ping_frames_per_minute, "proxy.config.http2.max_ping_frames_per_minute"); |
| REC_EstablishStaticConfigInt32U(max_priority_frames_per_minute, "proxy.config.http2.max_priority_frames_per_minute"); |
| REC_EstablishStaticConfigFloat(min_avg_window_update, "proxy.config.http2.min_avg_window_update"); |
| REC_EstablishStaticConfigInt32U(con_slow_log_threshold, "proxy.config.http2.connection.slow.log.threshold"); |
| REC_EstablishStaticConfigInt32U(stream_slow_log_threshold, "proxy.config.http2.stream.slow.log.threshold"); |
| REC_EstablishStaticConfigInt32U(header_table_size_limit, "proxy.config.http2.header_table_size_limit"); |
| |
| // If any settings is broken, ATS should not start |
| ink_release_assert(http2_settings_parameter_is_valid({HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, max_concurrent_streams_in})); |
| ink_release_assert(http2_settings_parameter_is_valid({HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, min_concurrent_streams_in})); |
| ink_release_assert(http2_settings_parameter_is_valid({HTTP2_SETTINGS_INITIAL_WINDOW_SIZE, initial_window_size})); |
| ink_release_assert(http2_settings_parameter_is_valid({HTTP2_SETTINGS_MAX_FRAME_SIZE, max_frame_size})); |
| ink_release_assert(http2_settings_parameter_is_valid({HTTP2_SETTINGS_HEADER_TABLE_SIZE, header_table_size})); |
| ink_release_assert(http2_settings_parameter_is_valid({HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE, max_header_list_size})); |
| |
| #define HTTP2_CLEAR_DYN_STAT(x) \ |
| do { \ |
| RecSetRawStatSum(http2_rsb, x, 0); \ |
| RecSetRawStatCount(http2_rsb, x, 0); \ |
| } while (0); |
| |
| // Setup statistics |
| http2_rsb = RecAllocateRawStatBlock(static_cast<int>(HTTP2_N_STATS)); |
| RecRegisterRawStat(http2_rsb, RECT_PROCESS, HTTP2_STAT_CURRENT_CLIENT_SESSION_NAME, RECD_INT, RECP_NON_PERSISTENT, |
| static_cast<int>(HTTP2_STAT_CURRENT_CLIENT_SESSION_COUNT), RecRawStatSyncSum); |
| RecRegisterRawStat(http2_rsb, RECT_PROCESS, HTTP2_STAT_CURRENT_CLIENT_CONNECTION_NAME, RECD_INT, RECP_NON_PERSISTENT, |
| static_cast<int>(HTTP2_STAT_CURRENT_CLIENT_SESSION_COUNT), RecRawStatSyncSum); |
| HTTP2_CLEAR_DYN_STAT(HTTP2_STAT_CURRENT_CLIENT_SESSION_COUNT); |
| RecRegisterRawStat(http2_rsb, RECT_PROCESS, HTTP2_STAT_CURRENT_ACTIVE_CLIENT_CONNECTION_NAME, RECD_INT, RECP_NON_PERSISTENT, |
| static_cast<int>(HTTP2_STAT_CURRENT_ACTIVE_CLIENT_CONNECTION_COUNT), RecRawStatSyncSum); |
| HTTP2_CLEAR_DYN_STAT(HTTP2_STAT_CURRENT_ACTIVE_CLIENT_CONNECTION_COUNT); |
| RecRegisterRawStat(http2_rsb, RECT_PROCESS, HTTP2_STAT_CURRENT_CLIENT_STREAM_NAME, RECD_INT, RECP_NON_PERSISTENT, |
| static_cast<int>(HTTP2_STAT_CURRENT_CLIENT_STREAM_COUNT), RecRawStatSyncSum); |
| HTTP2_CLEAR_DYN_STAT(HTTP2_STAT_CURRENT_CLIENT_STREAM_COUNT); |
| RecRegisterRawStat(http2_rsb, RECT_PROCESS, HTTP2_STAT_TOTAL_CLIENT_STREAM_NAME, RECD_INT, RECP_PERSISTENT, |
| static_cast<int>(HTTP2_STAT_TOTAL_CLIENT_STREAM_COUNT), RecRawStatSyncCount); |
| RecRegisterRawStat(http2_rsb, RECT_PROCESS, HTTP2_STAT_TOTAL_TRANSACTIONS_TIME_NAME, RECD_INT, RECP_PERSISTENT, |
| static_cast<int>(HTTP2_STAT_TOTAL_TRANSACTIONS_TIME), RecRawStatSyncSum); |
| RecRegisterRawStat(http2_rsb, RECT_PROCESS, HTTP2_STAT_TOTAL_CLIENT_CONNECTION_NAME, RECD_INT, RECP_PERSISTENT, |
| static_cast<int>(HTTP2_STAT_TOTAL_CLIENT_CONNECTION_COUNT), RecRawStatSyncSum); |
| RecRegisterRawStat(http2_rsb, RECT_PROCESS, HTTP2_STAT_CONNECTION_ERRORS_NAME, RECD_INT, RECP_PERSISTENT, |
| static_cast<int>(HTTP2_STAT_CONNECTION_ERRORS_COUNT), RecRawStatSyncSum); |
| RecRegisterRawStat(http2_rsb, RECT_PROCESS, HTTP2_STAT_STREAM_ERRORS_NAME, RECD_INT, RECP_PERSISTENT, |
| static_cast<int>(HTTP2_STAT_STREAM_ERRORS_COUNT), RecRawStatSyncSum); |
| RecRegisterRawStat(http2_rsb, RECT_PROCESS, HTTP2_STAT_SESSION_DIE_DEFAULT_NAME, RECD_INT, RECP_PERSISTENT, |
| static_cast<int>(HTTP2_STAT_SESSION_DIE_DEFAULT), RecRawStatSyncSum); |
| RecRegisterRawStat(http2_rsb, RECT_PROCESS, HTTP2_STAT_SESSION_DIE_OTHER_NAME, RECD_INT, RECP_PERSISTENT, |
| static_cast<int>(HTTP2_STAT_SESSION_DIE_OTHER), RecRawStatSyncSum); |
| RecRegisterRawStat(http2_rsb, RECT_PROCESS, HTTP2_STAT_SESSION_DIE_EOS_NAME, RECD_INT, RECP_PERSISTENT, |
| static_cast<int>(HTTP2_STAT_SESSION_DIE_EOS), RecRawStatSyncSum); |
| RecRegisterRawStat(http2_rsb, RECT_PROCESS, HTTP2_STAT_SESSION_DIE_ACTIVE_NAME, RECD_INT, RECP_PERSISTENT, |
| static_cast<int>(HTTP2_STAT_SESSION_DIE_ACTIVE), RecRawStatSyncSum); |
| RecRegisterRawStat(http2_rsb, RECT_PROCESS, HTTP2_STAT_SESSION_DIE_INACTIVE_NAME, RECD_INT, RECP_PERSISTENT, |
| static_cast<int>(HTTP2_STAT_SESSION_DIE_INACTIVE), RecRawStatSyncSum); |
| RecRegisterRawStat(http2_rsb, RECT_PROCESS, HTTP2_STAT_SESSION_DIE_ERROR_NAME, RECD_INT, RECP_PERSISTENT, |
| static_cast<int>(HTTP2_STAT_SESSION_DIE_ERROR), RecRawStatSyncSum); |
| RecRegisterRawStat(http2_rsb, RECT_PROCESS, HTTP2_STAT_SESSION_DIE_HIGH_ERROR_RATE_NAME, RECD_INT, RECP_PERSISTENT, |
| static_cast<int>(HTTP2_STAT_SESSION_DIE_HIGH_ERROR_RATE), RecRawStatSyncSum); |
| RecRegisterRawStat(http2_rsb, RECT_PROCESS, HTTP2_STAT_MAX_SETTINGS_PER_FRAME_EXCEEDED_NAME, RECD_INT, RECP_PERSISTENT, |
| static_cast<int>(HTTP2_STAT_MAX_SETTINGS_PER_FRAME_EXCEEDED), RecRawStatSyncSum); |
| RecRegisterRawStat(http2_rsb, RECT_PROCESS, HTTP2_STAT_MAX_SETTINGS_PER_MINUTE_EXCEEDED_NAME, RECD_INT, RECP_PERSISTENT, |
| static_cast<int>(HTTP2_STAT_MAX_SETTINGS_PER_MINUTE_EXCEEDED), RecRawStatSyncSum); |
| RecRegisterRawStat(http2_rsb, RECT_PROCESS, HTTP2_STAT_MAX_SETTINGS_FRAMES_PER_MINUTE_EXCEEDED_NAME, RECD_INT, RECP_PERSISTENT, |
| static_cast<int>(HTTP2_STAT_MAX_SETTINGS_FRAMES_PER_MINUTE_EXCEEDED), RecRawStatSyncSum); |
| RecRegisterRawStat(http2_rsb, RECT_PROCESS, HTTP2_STAT_MAX_PING_FRAMES_PER_MINUTE_EXCEEDED_NAME, RECD_INT, RECP_PERSISTENT, |
| static_cast<int>(HTTP2_STAT_MAX_PING_FRAMES_PER_MINUTE_EXCEEDED), RecRawStatSyncSum); |
| RecRegisterRawStat(http2_rsb, RECT_PROCESS, HTTP2_STAT_MAX_PRIORITY_FRAMES_PER_MINUTE_EXCEEDED_NAME, RECD_INT, RECP_PERSISTENT, |
| static_cast<int>(HTTP2_STAT_MAX_PRIORITY_FRAMES_PER_MINUTE_EXCEEDED), RecRawStatSyncSum); |
| RecRegisterRawStat(http2_rsb, RECT_PROCESS, HTTP2_STAT_INSUFFICIENT_AVG_WINDOW_UPDATE_NAME, RECD_INT, RECP_PERSISTENT, |
| static_cast<int>(HTTP2_STAT_INSUFFICIENT_AVG_WINDOW_UPDATE), RecRawStatSyncSum); |
| } |
| |
| #if TS_HAS_TESTS |
| |
| void forceLinkRegressionHPACK(); |
| void |
| forceLinkRegressionHPACKCaller() |
| { |
| forceLinkRegressionHPACK(); |
| } |
| |
| #include "tscore/TestBox.h" |
| |
| /*********************************************************************************** |
| * * |
| * Regression test for HTTP/2 * |
| * * |
| ***********************************************************************************/ |
| |
| const static struct { |
| uint8_t ftype; |
| uint8_t fflags; |
| bool valid; |
| } http2_frame_flags_test_case[] = {{HTTP2_FRAME_TYPE_DATA, 0x00, true}, |
| {HTTP2_FRAME_TYPE_DATA, 0x01, true}, |
| {HTTP2_FRAME_TYPE_DATA, 0x02, false}, |
| {HTTP2_FRAME_TYPE_DATA, 0x04, false}, |
| {HTTP2_FRAME_TYPE_DATA, 0x08, true}, |
| {HTTP2_FRAME_TYPE_DATA, 0x10, false}, |
| {HTTP2_FRAME_TYPE_DATA, 0x20, false}, |
| {HTTP2_FRAME_TYPE_DATA, 0x40, false}, |
| {HTTP2_FRAME_TYPE_DATA, 0x80, false}, |
| {HTTP2_FRAME_TYPE_HEADERS, 0x00, true}, |
| {HTTP2_FRAME_TYPE_HEADERS, 0x01, true}, |
| {HTTP2_FRAME_TYPE_HEADERS, 0x02, false}, |
| {HTTP2_FRAME_TYPE_HEADERS, 0x04, true}, |
| {HTTP2_FRAME_TYPE_HEADERS, 0x08, true}, |
| {HTTP2_FRAME_TYPE_HEADERS, 0x10, false}, |
| {HTTP2_FRAME_TYPE_HEADERS, 0x20, true}, |
| {HTTP2_FRAME_TYPE_HEADERS, 0x40, false}, |
| {HTTP2_FRAME_TYPE_HEADERS, 0x80, false}, |
| {HTTP2_FRAME_TYPE_PRIORITY, 0x00, true}, |
| {HTTP2_FRAME_TYPE_PRIORITY, 0x01, false}, |
| {HTTP2_FRAME_TYPE_PRIORITY, 0x02, false}, |
| {HTTP2_FRAME_TYPE_PRIORITY, 0x04, false}, |
| {HTTP2_FRAME_TYPE_PRIORITY, 0x08, false}, |
| {HTTP2_FRAME_TYPE_PRIORITY, 0x10, false}, |
| {HTTP2_FRAME_TYPE_PRIORITY, 0x20, false}, |
| {HTTP2_FRAME_TYPE_PRIORITY, 0x40, false}, |
| {HTTP2_FRAME_TYPE_PRIORITY, 0x80, false}, |
| {HTTP2_FRAME_TYPE_RST_STREAM, 0x00, true}, |
| {HTTP2_FRAME_TYPE_RST_STREAM, 0x01, false}, |
| {HTTP2_FRAME_TYPE_RST_STREAM, 0x02, false}, |
| {HTTP2_FRAME_TYPE_RST_STREAM, 0x04, false}, |
| {HTTP2_FRAME_TYPE_RST_STREAM, 0x08, false}, |
| {HTTP2_FRAME_TYPE_RST_STREAM, 0x10, false}, |
| {HTTP2_FRAME_TYPE_RST_STREAM, 0x20, false}, |
| {HTTP2_FRAME_TYPE_RST_STREAM, 0x40, false}, |
| {HTTP2_FRAME_TYPE_RST_STREAM, 0x80, false}, |
| {HTTP2_FRAME_TYPE_SETTINGS, 0x00, true}, |
| {HTTP2_FRAME_TYPE_SETTINGS, 0x01, true}, |
| {HTTP2_FRAME_TYPE_SETTINGS, 0x02, false}, |
| {HTTP2_FRAME_TYPE_SETTINGS, 0x04, false}, |
| {HTTP2_FRAME_TYPE_SETTINGS, 0x08, false}, |
| {HTTP2_FRAME_TYPE_SETTINGS, 0x10, false}, |
| {HTTP2_FRAME_TYPE_SETTINGS, 0x20, false}, |
| {HTTP2_FRAME_TYPE_SETTINGS, 0x40, false}, |
| {HTTP2_FRAME_TYPE_SETTINGS, 0x80, false}, |
| {HTTP2_FRAME_TYPE_PUSH_PROMISE, 0x00, true}, |
| {HTTP2_FRAME_TYPE_PUSH_PROMISE, 0x01, false}, |
| {HTTP2_FRAME_TYPE_PUSH_PROMISE, 0x02, false}, |
| {HTTP2_FRAME_TYPE_PUSH_PROMISE, 0x04, true}, |
| {HTTP2_FRAME_TYPE_PUSH_PROMISE, 0x08, true}, |
| {HTTP2_FRAME_TYPE_PUSH_PROMISE, 0x10, false}, |
| {HTTP2_FRAME_TYPE_PUSH_PROMISE, 0x20, false}, |
| {HTTP2_FRAME_TYPE_PUSH_PROMISE, 0x40, false}, |
| {HTTP2_FRAME_TYPE_PUSH_PROMISE, 0x80, false}, |
| {HTTP2_FRAME_TYPE_PING, 0x00, true}, |
| {HTTP2_FRAME_TYPE_PING, 0x01, true}, |
| {HTTP2_FRAME_TYPE_PING, 0x02, false}, |
| {HTTP2_FRAME_TYPE_PING, 0x04, false}, |
| {HTTP2_FRAME_TYPE_PING, 0x08, false}, |
| {HTTP2_FRAME_TYPE_PING, 0x10, false}, |
| {HTTP2_FRAME_TYPE_PING, 0x20, false}, |
| {HTTP2_FRAME_TYPE_PING, 0x40, false}, |
| {HTTP2_FRAME_TYPE_PING, 0x80, false}, |
| {HTTP2_FRAME_TYPE_GOAWAY, 0x00, true}, |
| {HTTP2_FRAME_TYPE_GOAWAY, 0x01, false}, |
| {HTTP2_FRAME_TYPE_GOAWAY, 0x02, false}, |
| {HTTP2_FRAME_TYPE_GOAWAY, 0x04, false}, |
| {HTTP2_FRAME_TYPE_GOAWAY, 0x08, false}, |
| {HTTP2_FRAME_TYPE_GOAWAY, 0x10, false}, |
| {HTTP2_FRAME_TYPE_GOAWAY, 0x20, false}, |
| {HTTP2_FRAME_TYPE_GOAWAY, 0x40, false}, |
| {HTTP2_FRAME_TYPE_GOAWAY, 0x80, false}, |
| {HTTP2_FRAME_TYPE_WINDOW_UPDATE, 0x00, true}, |
| {HTTP2_FRAME_TYPE_WINDOW_UPDATE, 0x01, false}, |
| {HTTP2_FRAME_TYPE_WINDOW_UPDATE, 0x02, false}, |
| {HTTP2_FRAME_TYPE_WINDOW_UPDATE, 0x04, false}, |
| {HTTP2_FRAME_TYPE_WINDOW_UPDATE, 0x08, false}, |
| {HTTP2_FRAME_TYPE_WINDOW_UPDATE, 0x10, false}, |
| {HTTP2_FRAME_TYPE_WINDOW_UPDATE, 0x20, false}, |
| {HTTP2_FRAME_TYPE_WINDOW_UPDATE, 0x40, false}, |
| {HTTP2_FRAME_TYPE_WINDOW_UPDATE, 0x80, false}, |
| {HTTP2_FRAME_TYPE_CONTINUATION, 0x00, true}, |
| {HTTP2_FRAME_TYPE_CONTINUATION, 0x01, false}, |
| {HTTP2_FRAME_TYPE_CONTINUATION, 0x02, false}, |
| {HTTP2_FRAME_TYPE_CONTINUATION, 0x04, true}, |
| {HTTP2_FRAME_TYPE_CONTINUATION, 0x08, false}, |
| {HTTP2_FRAME_TYPE_CONTINUATION, 0x10, false}, |
| {HTTP2_FRAME_TYPE_CONTINUATION, 0x20, false}, |
| {HTTP2_FRAME_TYPE_CONTINUATION, 0x40, false}, |
| {HTTP2_FRAME_TYPE_CONTINUATION, 0x80, false}, |
| {HTTP2_FRAME_TYPE_MAX, 0x00, true}, |
| {HTTP2_FRAME_TYPE_MAX, 0x01, true}, |
| {HTTP2_FRAME_TYPE_MAX, 0x02, true}, |
| {HTTP2_FRAME_TYPE_MAX, 0x04, true}, |
| {HTTP2_FRAME_TYPE_MAX, 0x08, true}, |
| {HTTP2_FRAME_TYPE_MAX, 0x10, true}, |
| {HTTP2_FRAME_TYPE_MAX, 0x20, true}, |
| {HTTP2_FRAME_TYPE_MAX, 0x40, true}, |
| {HTTP2_FRAME_TYPE_MAX, 0x80, true}}; |
| |
| static const uint8_t HTTP2_FRAME_FLAGS_MASKS[HTTP2_FRAME_TYPE_MAX] = { |
| HTTP2_FLAGS_DATA_MASK, HTTP2_FLAGS_HEADERS_MASK, HTTP2_FLAGS_PRIORITY_MASK, HTTP2_FLAGS_RST_STREAM_MASK, |
| HTTP2_FLAGS_SETTINGS_MASK, HTTP2_FLAGS_PUSH_PROMISE_MASK, HTTP2_FLAGS_PING_MASK, HTTP2_FLAGS_GOAWAY_MASK, |
| HTTP2_FLAGS_WINDOW_UPDATE_MASK, HTTP2_FLAGS_CONTINUATION_MASK, |
| }; |
| |
| REGRESSION_TEST(HTTP2_FRAME_FLAGS)(RegressionTest *t, int, int *pstatus) |
| { |
| TestBox box(t, pstatus); |
| box = REGRESSION_TEST_PASSED; |
| |
| for (auto i : http2_frame_flags_test_case) { |
| box.check((i.ftype >= HTTP2_FRAME_TYPE_MAX || (i.fflags & ~HTTP2_FRAME_FLAGS_MASKS[i.ftype]) == 0) == i.valid, |
| "Validation of frame flags (type: %d, flags: %d) are expected %d, but not", i.ftype, i.fflags, i.valid); |
| } |
| } |
| |
| #endif /* TS_HAS_TESTS */ |