libmicrohttpd2

HTTP server C library (MHD 2.x, alpha)
Log | Files | Refs | README | LICENSE

commit 8b600d7ca3c4e0ed124370c50d3522e78873c0ec
parent 12e09d4cdb2b35e58113bd20aef738be42a60563
Author: Evgeny Grin (Karlson2k) <k2k@drgrin.dev>
Date:   Mon, 15 Sep 2025 09:56:08 +0200

Implemented HPACK

Diffstat:
Msrc/mhd2/Makefile.am | 4+++-
Asrc/mhd2/h2/hpack/mhd_hpack_codec.c | 6572+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/h2/hpack/mhd_hpack_codec.h | 576+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/h2/hpack/mhd_hpack_dec_types.h | 76++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/h2/hpack/mhd_hpack_enc_types.h | 85+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 7312 insertions(+), 1 deletion(-)

diff --git a/src/mhd2/Makefile.am b/src/mhd2/Makefile.am @@ -109,7 +109,9 @@ endif httptwo_OPTSOURCES = \ h2/hpack/h2_huffman_codec.c \ - h2/hpack/h2_huffman_codec.h + h2/hpack/h2_huffman_codec.h \ + h2/hpack/mhd_hpack_codec.c \ + h2/hpack/mhd_hpack_codec.h threads_OPTSOURCES = \ mhd_threads.c mhd_threads.h sys_thread_entry_type.h diff --git a/src/mhd2/h2/hpack/mhd_hpack_codec.c b/src/mhd2/h2/hpack/mhd_hpack_codec.c @@ -0,0 +1,6572 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2025 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/h2/hpack/mhd_hpack_codec.c + * @brief The implementation of the HPACK header-compression codec functions. + * @author Karlson2k (Evgeny Grin) + * + * The sizes of all strings are intentionally limited to 32 bits (4GiB). + * The sizes of all strings in the dynamic table are limited to 32 or 16 bits, + * depending on value of #mhd_HPACK_DTBL_BITS macro. + */ + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" +#include "sys_base_types.h" +#include "sys_malloc.h" +#include <string.h> + +#include "mhd_constexpr.h" +#include "mhd_align.h" + +#include "mhd_assert.h" +#include "mhd_unreachable.h" +#include "mhd_predict.h" + +#include "mhd_bithelpers.h" + +#include "mhd_str_types.h" +#include "mhd_str_macros.h" +#include "mhd_buffer.h" + +#include "mhd_tristate.h" +#include "mhd_hpack_dec_types.h" +#include "mhd_hpack_enc_types.h" + +#include "mhd_hpack_codec.h" +#if ! defined(mhd_HPACK_TESTING_TABLES_ONLY) || ! defined(MHD_UNIT_TESTING) +# include "h2_huffman_codec.h" +# include "h2_huffman_est.h" +#endif + + +/** + * Number of entries in the static table + */ +#define mhd_HPACK_STBL_ENTRIES (61u) + +/** + * The last HPACK index number in the static table + */ +#define mhd_HPACK_STBL_LAST_IDX mhd_HPACK_STBL_ENTRIES + + +/* ****** ----------------- Dynamic table handling ----------------- ****** */ + +/* ======================================================================== + * + * The dynamic tables should be accessed only by mhd_* functions. + * + * All functions prefixed with dtbl_* are internal helpers and should not + * be used directly. + * + * ======================================================================== + */ + +#if mhd_HPACK_DTBL_BITS == 32 +/** + * A type used to store sizes of dynamic table elements. + * + * This is a compact type; it uses the minimal amount of memory. + */ +typedef uint_least32_t dtbl_size_t; +/** + * A type used to operate on sizes of dynamic and static table elements + * + * This type should be more friendly for faster processing by CPU. + * It could be the same underlying type as @a dtbl_size_t + */ +typedef uint_fast32_t dtbl_size_ft; +/** + * A type used to store the number of dynamic and static table elements + * + * This is a compact type; it uses the minimal amount of memory. + */ +typedef uint_least32_t dtbl_idx_t; +/** + * A type used to operate and address dynamic and static table elements + * + * This type should be more friendly for faster processing by CPU. + * It could be the same underlying type as @a dtbl_idx_t + */ +typedef uint_fast32_t dtbl_idx_ft; +/** + * Check whether value @a val fits 32 bits type. + * + * If any non-zero bit is set above the lowest 32 bits, the macro returns + * boolean false. + * + * This macro strictly checks whether the provided value is suitable for use + * in dynamic table elements. Even if the underlying type uint_least32_t is + * wider than 32 bits, this macro enforces the limit to 32 bits only. + * + * This macro is designed to work only with unsigned types. No signed types + * are used in dynamic table data. + * + * The parameter is evaluated only once. + */ +# define mhd_DTBL_VALUE_FITS(val) (0xFFFFFFFFu == ((val) | 0xFFFFFFFFu)) +#elif mhd_HPACK_DTBL_BITS == 16 +/** + * A type used to store sizes of dynamic table elements. + * + * This is a compact type; it uses the minimal amount of memory. + */ +typedef uint_least16_t dtbl_size_t; +/** + * A type used to operate sizes of dynamic and static table elements + * + * This type should be more friendly for faster processing by CPU. + * It could be the same underlying type as @a dtbl_size_t + */ +typedef uint_fast16_t dtbl_size_ft; +/** + * A type used to store the number of dynamic and static table elements + * + * This is a compact type; it uses the minimal amount of memory. + */ +typedef uint_least16_t dtbl_idx_t; +/** + * A type used to operate and address dynamic and static table elements + * + * This type should be more friendly for faster processing by CPU. + * It could be the same underlying type as @a dtbl_idx_t + */ +typedef uint_fast16_t dtbl_idx_ft; +/** + * Check whether value @a val fits 16 bits type. + * + * If any non-zero bit is set above the lowest 16 bits, the macro returns + * boolean false. + * + * This macro strictly checks whether the provided value is suitable for use + * in dynamic table elements. Even if the underlying type uint_least16_t is + * wider than 16 bits, this macro enforces the limit to 16 bits only. + * + * This macro is designed to work only with unsigned types. No signed types + * are used in dynamic table data. + * + * The parameter is evaluated only once. + */ +# define mhd_DTBL_VALUE_FITS(val) (0xFFFFu == ((val) | 0xFFFFu)) +#else +#error Unsupported mhd_HPACK_DTBL_BITS value +#endif + + +/** + * The data for a dynamic table entry + */ +struct mhd_HpackDTblEntryInfo +{ + /** + * The offset of the name in the buffer + */ + dtbl_size_t offset; + /** + * The length of the name string. + * The name string is not zero-terminated. + */ + dtbl_size_t name_len; + /** + * The length of the value string. + * The value is located at @a offset + @a name_len. + * The value string is not zero-terminated. + */ + dtbl_size_t val_len; +}; + +/** + * Size (in bytes) of one dynamic-table entry-information record. + */ +#define mhd_DTBL_ENTRY_INFO_SIZE \ + ((dtbl_size_t) (sizeof(struct mhd_HpackDTblEntryInfo))) + +/** + * HPACK dynamic-table per-entry overhead, in bytes (RFC 7541 4.1). + * + * The macro is needed to statically initialise mhd_dtbl_entry_slack + * in C11 mode (as 'static const' variable). + */ +#define mhd_HPACK_ENTRY_OVERHEAD (32u) + +/** + * HPACK dynamic-table per-entry overhead, in bytes (RFC 7541 4.1). + * The size of a dynamic-table entry is: + * 32 + length(header field name) + length(header field value), + * where both lengths are in bytes as defined in RFC 7541 5.2. + */ +mhd_constexpr dtbl_size_t mhd_dtbl_entry_overhead = + mhd_HPACK_ENTRY_OVERHEAD; + + +/** + * The extra slack between entries in the strings buffer. + * Used when there is extra space while adding a new entry. + * This extra slack reduces the need to move strings in the buffer when the + * entry is evicted and the strings are replaced with the new entry's strings. + * + * If strings are placed optimally (with this slack), then one entry took + * exactly the formal HPACK size in the buffer (strings + entry information + * data). + */ +mhd_constexpr dtbl_size_t mhd_dtbl_entry_slack = + mhd_HPACK_ENTRY_OVERHEAD - mhd_DTBL_ENTRY_INFO_SIZE; + +/** + * The first HPACK index in the dynamic table + */ +mhd_constexpr dtbl_idx_t mhd_dtbl_hpack_idx_offset = + mhd_HPACK_STBL_LAST_IDX + 1u; + +/** + * The maximum possible HPACK index when largest possible size of the dynamic + * table is used + */ +#define mhd_HPACK_MAX_POSSIBLE_IDX \ + ((mhd_DTBL_MAX_SIZE / mhd_HPACK_ENTRY_OVERHEAD) \ + + mhd_HPACK_STBL_LAST_IDX) + +/** + * Get the formal HPACK size of the potential new entry. + * @param strings_len the total size of the strings (the length of the name of + * the field + the length of the value of the field) + * @return the formal HPACK size of the potential new entry + */ +MHD_FN_PURE_ mhd_static_inline dtbl_size_t +dtbl_new_entry_strs_size_formal (dtbl_size_ft strings_len) +{ + dtbl_size_ft formal_size = strings_len + mhd_dtbl_entry_overhead; + mhd_assert (strings_len < formal_size); + mhd_assert (mhd_DTBL_VALUE_FITS (strings_len)); + mhd_assert (mhd_DTBL_VALUE_FITS (formal_size)); + return (dtbl_size_t) formal_size; +} + + +/** + * Get the formal HPACK size of the potential new entry. + * @param name_len the length of the name of the field + * @param val_len the length of the value of the field + * @return the formal HPACK size of the potential new entry + */ +MHD_FN_PURE_ mhd_static_inline dtbl_size_t +dtbl_new_entry_size_formal (dtbl_size_ft name_len, + dtbl_size_ft val_len) +{ + const dtbl_size_ft entry_strs_size = name_len + val_len; + mhd_assert (val_len <= entry_strs_size); + mhd_assert (mhd_DTBL_VALUE_FITS (entry_strs_size)); + mhd_assert (mhd_DTBL_VALUE_FITS (name_len)); + mhd_assert (mhd_DTBL_VALUE_FITS (val_len)); + return dtbl_new_entry_strs_size_formal (entry_strs_size); +} + + +/** + * Get the total size of the strings of the entry. + * This is the minimal size required for the entry in the strings buffer. + * @param entr_inf the pointer to the entry info + * @return the total size of the strings of the entry + */ +MHD_FN_PURE_ mhd_static_inline dtbl_size_t +dtbl_entr_strs_size_min (const struct mhd_HpackDTblEntryInfo *entr_inf) +{ + return entr_inf->name_len + entr_inf->val_len; +} + + +/** + * Get the total size of the strings of the entry plus standard slack size. + * This is the optimal size used for the entry in the strings buffer when the + * current insertion slot has enough space. + * @param entr_inf the pointer to the entry info + * @return the total size of the strings of the entry plus standard slack size + */ +MHD_FN_PURE_ mhd_static_inline dtbl_size_t +dtbl_entr_strs_size_optm (const struct mhd_HpackDTblEntryInfo *entr_inf) +{ + return dtbl_entr_strs_size_min (entr_inf) + mhd_dtbl_entry_slack; +} + + +/** + * Get the formal HPACK size of the entry. + * The formal size of the entry is the size of the strings plus fixed + * HPACK per-entry overhead. + * @param entr_inf the pointer to the entry info + * @return the formal HPACK size of the entry + */ +MHD_FN_PURE_ mhd_static_inline dtbl_size_t +dtbl_entr_size_formal (const struct mhd_HpackDTblEntryInfo *entr_inf) +{ + const dtbl_size_t ret = dtbl_new_entry_size_formal (entr_inf->name_len, + entr_inf->val_len); + mhd_assert (dtbl_entr_strs_size_min (entr_inf) + mhd_dtbl_entry_overhead == \ + ret); + return ret; +} + + +/** + * Get the position (offset) of the (inclusive) start of the entry's strings + * in the strings buffer. + * This points to the first byte of the entry's strings. If the entry has + * zero-length strings, the pointer denotes a (possibly zero-sized) area + * that may coincide with the start of the entry's slack (if any) or with + * the next entry's strings start (if present). + * @param entr_inf the pointer to the entry info + * @return the position (offset) of the (inclusive) start of the entry's strings + */ +MHD_FN_PURE_ mhd_static_inline dtbl_size_t +dtbl_entr_strs_start (const struct mhd_HpackDTblEntryInfo *entr_inf) +{ + return entr_inf->offset; +} + + +/** + * Get the position of the (exclusive) end of the entry's strings in the + * strings buffer. + * This points to the next char (byte) after the strings of the entry. + * @param entr_inf the pointer to the entry info + * @return the position of the end of the entry's strings in the strings buffer + */ +MHD_FN_PURE_ mhd_static_inline dtbl_size_t +dtbl_entr_strs_end_min (const struct mhd_HpackDTblEntryInfo *entr_inf) +{ + return dtbl_entr_strs_start (entr_inf) + dtbl_entr_strs_size_min (entr_inf); +} + + +/** + * Get the position (offset) immediately after the standard slack following the + * end of the entry's strings in the strings buffer. + * This points to the preferred position of the next entry's strings. + * @param entr_inf the pointer to the entry info + * @return the position (offset) immediately after the standard slack + */ +MHD_FN_PURE_ mhd_static_inline dtbl_size_t +dtbl_entr_strs_end_optm (const struct mhd_HpackDTblEntryInfo *entr_inf) +{ + return dtbl_entr_strs_start (entr_inf) + dtbl_entr_strs_size_optm (entr_inf); +} + + +/* + * The dynamic HPACK table is organised as follows: + * + The shared buffer is placed immediately after mhd_HpackDTblContext in + * memory. + * + The buffer stores both the strings (names and values) and the entry info + * data (one mhd_HpackDTblEntryInfo per entry). + * + Strings grow upward from the bottom of the buffer (lower addresses), while + * entry-info data grow downward from the top of the buffer (higher + * addresses). + * + Because the buffer is shared, the same area may be used either by strings + * (few entries with large strings) or by entry info data (many entries with + * small strings). + * + The topmost entry info data corresponds to the bottommost strings, and + * vice versa. + * + Both regions (strings and entry info data) effectively form two circular + * buffers that dynamically share the same memory space region: the bottom + * part is strings area (filled from bottom to up) and the upper part is + * entries info data area (filled from top to down). See also "zero position" + * and the "edge entry" below. + * + The table data tracks the newest entry; the new entries are added at + * higher (than the newest entry) location numbers. + * + HPACK indices are counted in the opposite direction (the smallest HPACK + * index refers to the newest entry; the next entry's location number is the + * newest location minus one). + * + Because the size of an entry info (sizeof(struct mhd_HpackDTblEntryInfo)) + * is smaller than the mandatory HPACK per-entry overhead (32 bytes), + * strings are inserted with an additional slack when there is enough space + * before the next entry's strings. + * + * Terminology used below: + * + "entry info" (or "entry information data") -- mhd_HpackDTblEntryInfo data. + * + "zero position entry" -- the entry whose strings are at the bottom of the + * buffer and whose entry info data is at the very top of the buffer. This + * is the first entry added to an empty table. + * + "edge entry" -- the entry whose strings lie above all other strings and + * whose entry info data lies below all other entry info data. Any space + * between this entry's strings and its entry info data is not used by + * other entries. + * + "newest" (or latest) entry -- the most recently added entry (the + * lowest HPACK index). + * + "oldest" entry -- the entry added before all other current entries; its + * strings immediately follow the newest entry's strings (or are at location + * zero if the newest entry is the edge entry). Its entry info data + * immediately precedes the newest entry's data (or is at the top if the + * newest entry is the edge entry). + */ + +/** + * Dynamic HPACK table data + */ +struct mhd_HpackDTblContext +{ + /** + * The size of the allocated buffer. + * The buffer is located in memory right after this structure. + */ + dtbl_size_t buf_alloc_size; + + /** + * The current number of entries used + */ + dtbl_idx_t num_entries; + + /** + * Offset of the current newest (most recently added) entry; it also has + * the lowest HPACK index. + * The "next" entry (newest_pos + 1, or 0 when the newest entry is the + * edge entry (newest_pos == num_entries - 1)) is the oldest entry and + * is evicted first if needed. + * When a new entry is added, newest_pos is incremented or wrapped to 0 + * (when the newest entry is at the edge and insertion wraps). + */ + dtbl_idx_t newest_pos; + + /** + * The cached value of the official table size (as defined by HPACK). + * Used to speed up calculations. Can be re-created from entries information. + */ + dtbl_size_t cur_size; + + /** + * The dynamic table size limit as defined by HPACK + */ + dtbl_size_t size_limit; +}; + + +/* **** ---------- Dynamic table internal helpers -------------- **** */ + +/* ** Basic table information ** */ + + +/** + * Get the number of entries in the table + * @param dyn the pointer to the dynamic table structure + * @return the number of entries in the table + */ +MHD_FN_PURE_ mhd_static_inline dtbl_idx_t +dtbl_get_num_entries (const struct mhd_HpackDTblContext *dyn) +{ + return dyn->num_entries; +} + + +/** + * Check whether the table is empty (no entries) + * @param dyn the pointer to the dynamic table structure + * @return 'true' if table has no entries, + * 'false' otherwise + */ +MHD_FN_PURE_ mhd_static_inline bool +dtbl_is_empty (const struct mhd_HpackDTblContext *dyn) +{ + mhd_assert ((0u == dyn->num_entries) == (0u == dyn->cur_size)); + return (0u == dtbl_get_num_entries (dyn)); +} + + +/** + * Get the pointer to the strings buffer + * @param dyn the pointer to the dynamic table structure + * @return the pointer to the strings buffer + */ +MHD_FN_CONST_ mhd_static_inline char * +dtbl_get_strs_buff (struct mhd_HpackDTblContext *dyn) +{ + return (char*) + (dyn + 1u); +} + + +/** + * Get a const pointer to the strings buffer + * @param dyn the pointer to the dynamic table structure + * @return const pointer to the strings buffer + */ +MHD_FN_CONST_ mhd_static_inline const char * +dtbl_get_strs_buffc (const struct mhd_HpackDTblContext *dyn) +{ + return (const char*) + (dyn + 1u); +} + + +/** + * Get the pointer to the top (by location in memory) dynamic table entry data. + * The entries info data grow downward. + * @param dyn the pointer to the dynamic table structure + * @return the pointer to the top (by location in memory) entry data + */ +MHD_FN_PURE_ mhd_static_inline struct mhd_HpackDTblEntryInfo * +dtbl_get_infos (struct mhd_HpackDTblContext *dyn) +{ + return ((struct mhd_HpackDTblEntryInfo *) + (void *) + (dtbl_get_strs_buff (dyn) + dyn->buf_alloc_size)) - 1u; +} + + +/** + * Get a const pointer to the top (by location in memory) dynamic table entry + * data. + * The entries info data grow downward. + * @param dyn const pointer to the dynamic table structure + * @return const pointer to the top (by location in memory) entry data + */ +MHD_FN_PURE_ mhd_static_inline const struct mhd_HpackDTblEntryInfo * +dtbl_get_infosc (const struct mhd_HpackDTblContext *dyn) +{ + return ((const struct mhd_HpackDTblEntryInfo *) + (const void *) + (dtbl_get_strs_buffc (dyn) + dyn->buf_alloc_size)) - 1u; +} + + +/** + * Get the position of the entry located at the edge of the buffer. + * + * This is the entry with the strings located above all other strings + * and the entry information data located below all other entries information + * data. + * + * If any space is left between entry's strings data and information data, this + * space is not used by other entries. + * + * The result is undefined if the table has no entries. + * @param dyn the pointer to the dynamic table structure + * @return the position of the edge entry, + * undefined if the table has no entries + */ +MHD_FN_PURE_ mhd_static_inline dtbl_idx_t +dtbl_get_pos_edge (const struct mhd_HpackDTblContext *dyn) +{ + mhd_assert (! dtbl_is_empty (dyn)); + mhd_assert (dyn->buf_alloc_size >= + mhd_DTBL_ENTRY_INFO_SIZE * dyn->num_entries); + return (dtbl_idx_t) (dyn->num_entries - 1u); +} + + +/** + * Get the position of the previous entry for the specified entry position. + * + * This is a position of the entry previous to the specified entry position. + * The returned value is one less than the specified position or wraps to the + * edge position when the specified position is zero. + * + * The result is undefined if the table has no entries. + * @param dyn the pointer to the dynamic table structure + * @param loc_pos the number of location position + * @return the position of the previous entry for specified entry position, + * undefined if the table has no entries + */ +MHD_FN_PURE_ mhd_static_inline dtbl_idx_t +dtbl_get_pos_prev (const struct mhd_HpackDTblContext *dyn, + dtbl_idx_ft loc_pos) +{ + mhd_assert (! dtbl_is_empty (dyn)); + mhd_assert (loc_pos <= dtbl_get_pos_edge (dyn)); +#ifdef MHD_USE_CODE_HARDENING + if (0u == loc_pos) + return dtbl_get_pos_edge (dyn); + return (dtbl_idx_t) (loc_pos - 1u); +#else /* ! MHD_USE_CODE_HARDENING */ + return (dtbl_idx_t) ((dyn->num_entries + loc_pos - 1u) % dyn->num_entries); +#endif /* ! MHD_USE_CODE_HARDENING */ +} + + +/** + * Get the position of the next entry for the specified entry position. + * + * This is a position of the entry next to the specified entry position. + * The returned value is greater by one than specified position or wraps to + * zero if the specified position is edge position. + * + * The result is undefined if the table has no entries. + * @param dyn the pointer to the dynamic table structure + * @param loc_pos the number of location position + * @return the position of the next entry for the specified entry position, + * undefined if the table has no entries + */ +MHD_FN_PURE_ mhd_static_inline dtbl_idx_t +dtbl_get_pos_next (const struct mhd_HpackDTblContext *dyn, + dtbl_idx_ft loc_pos) +{ + mhd_assert (! dtbl_is_empty (dyn)); + mhd_assert (loc_pos <= dtbl_get_pos_edge (dyn)); +#ifdef MHD_USE_CODE_HARDENING + if (dtbl_get_pos_edge (dyn) == loc_pos) + return 0u; + return (dtbl_idx_t) (loc_pos + 1u); +#else /* ! MHD_USE_CODE_HARDENING */ + return (dtbl_idx_t) ((dyn->num_entries + loc_pos + 1u) % dyn->num_entries); +#endif /* ! MHD_USE_CODE_HARDENING */ +} + + +/** + * Get the position of the newest entry. + * + * This is a position of the last added entry. + * + * The result is undefined if the table has no entries. + * @param dyn the pointer to the dynamic table structure + * @return the position of the newest entry, + * undefined if the table has no entries + */ +MHD_FN_PURE_ mhd_static_inline dtbl_idx_t +dtbl_get_pos_newest (const struct mhd_HpackDTblContext *dyn) +{ + mhd_assert (! dtbl_is_empty (dyn)); + return dyn->newest_pos; +} + + +/** + * Get the position of the oldest entry. + * + * This is a position of the current oldest entry in the table. This entry + * is evicted first if eviction is needed. + * + * The result is undefined if the table has no entries. + * @param dyn the pointer to the dynamic table structure + * @return the position of the oldest entry, + * undefined if the table has no entries + */ +MHD_FN_PURE_ mhd_static_inline dtbl_idx_t +dtbl_get_pos_oldest (const struct mhd_HpackDTblContext *dyn) +{ + return dtbl_get_pos_next (dyn, + dtbl_get_pos_newest (dyn)); +} + + +/** + * Convert an HPACK table index to the position number in the dynamic table. + * + * The result is undefined if the specified index is less than or equal to the + * number of entries in the static table. + * The result is undefined if the specified index is larger than the last valid + * HPACK index in the table. + * @param dyn the pointer to the dynamic table structure + * @param hpack_idx the HPACK index of the entry + * @return the position of the requested entry in the table, + * undefined if the @a hpack_idx is not valid for the table + */ +MHD_FN_PURE_ mhd_static_inline dtbl_idx_t +dtbl_get_pos_from_hpack_idx (const struct mhd_HpackDTblContext *dyn, + dtbl_idx_ft hpack_idx) +{ + dtbl_idx_ft pos_back_from_newest = + (dtbl_idx_ft) (hpack_idx - mhd_dtbl_hpack_idx_offset); + mhd_assert (mhd_DTBL_VALUE_FITS (hpack_idx)); + mhd_assert (mhd_HPACK_STBL_LAST_IDX < hpack_idx); + mhd_assert (dtbl_get_num_entries (dyn) + mhd_dtbl_hpack_idx_offset > \ + hpack_idx); + +#ifdef MHD_USE_CODE_HARDENING + if (dtbl_get_pos_newest (dyn) >= pos_back_from_newest) + return (dtbl_idx_t) (dtbl_get_pos_newest (dyn) - pos_back_from_newest); + return (dtbl_idx_t) (dtbl_get_num_entries (dyn) + dtbl_get_pos_newest (dyn) + - pos_back_from_newest); +#else /* ! MHD_USE_CODE_HARDENING */ + return + (dtbl_idx_t) + ((dtbl_get_num_entries (dyn) + + dtbl_get_pos_newest (dyn) - pos_back_from_newest) + % dtbl_get_num_entries (dyn)); +#endif /* ! MHD_USE_CODE_HARDENING */ +} + + +/** + * Convert a dynamic-table location position to the corresponding HPACK index. + * + * This is the inverse of #dtbl_get_pos_from_hpack_idx(). + * The returned HPACK index is strictly greater than the last index in the + * static table (#mhd_HPACK_STBL_LAST_IDX). + * + * Behaviour is undefined if @a loc_pos is not a valid position for @a dyn. + * @param dyn the pointer to the dynamic table structure + * @param loc_pos the location position number (0 .. #dtbl_get_pos_edge()) + * @return the HPACK index corresponding to @a loc_pos + * undefined if the @a loc_pos is not valid for the table + */ +MHD_FN_PURE_ mhd_static_inline dtbl_idx_t +dtbl_get_hpack_idx_from_pos (const struct mhd_HpackDTblContext *dyn, + dtbl_idx_ft loc_pos) +{ + mhd_assert (mhd_DTBL_VALUE_FITS (loc_pos)); + mhd_assert (dtbl_get_pos_edge (dyn) >= loc_pos); + +#ifdef MHD_USE_CODE_HARDENING + if (dtbl_get_pos_newest (dyn) >= loc_pos) + return (dtbl_idx_t) (dtbl_get_pos_newest (dyn) - loc_pos + + mhd_dtbl_hpack_idx_offset); + return (dtbl_idx_t) (dtbl_get_num_entries (dyn) + dtbl_get_pos_newest (dyn) + - loc_pos + mhd_dtbl_hpack_idx_offset); +#else /* ! MHD_USE_CODE_HARDENING */ + return + (dtbl_idx_t) + (((dtbl_get_num_entries (dyn) + dtbl_get_pos_newest (dyn) - loc_pos)) + % dtbl_get_num_entries (dyn) + mhd_dtbl_hpack_idx_offset); +#endif /* ! MHD_USE_CODE_HARDENING */ +} + + +/** + * Get the current exclusive upper bound (in bytes) for valid offsets + * within the strings region. + + * This equals the distance from the start of the strings region to the first + * byte occupied by entry-information data. As more entry information is added, + * this limit decreases. For an empty table, the limit equals buf_alloc_size. + * @param dyn the pointer to the dynamic table structure + * @return the current exclusive upper bound for offsets in the strings region + */ +MHD_FN_PURE_ mhd_static_inline dtbl_size_t +dtbl_get_strs_ceiling (const struct mhd_HpackDTblContext *dyn) +{ + dtbl_size_ft ceiling = + dyn->buf_alloc_size + - (dtbl_size_ft) mhd_DTBL_ENTRY_INFO_SIZE * dyn->num_entries; + + mhd_assert (dyn->buf_alloc_size >= + mhd_DTBL_ENTRY_INFO_SIZE * dyn->num_entries); + mhd_assert (mhd_DTBL_VALUE_FITS (ceiling)); + + return (dtbl_size_t) ceiling; +} + + +/** + * Get the formal maximum HPACK size in the table. + * @param dyn the pointer to the dynamic table structure + * @return the formal HPACK size in the table + */ +MHD_FN_PURE_ mhd_static_inline dtbl_size_t +dtbl_get_size_max_formal (const struct mhd_HpackDTblContext *dyn) +{ + return dyn->size_limit; +} + + +/** + * Get the amount of formal HPACK free space in the table. + * @param dyn the pointer to the dynamic table structure + * @return the formal HPACK free space in the table + */ +MHD_FN_PURE_ mhd_static_inline dtbl_size_t +dtbl_get_free_formal (const struct mhd_HpackDTblContext *dyn) +{ + mhd_assert (dyn->size_limit >= dyn->cur_size); + return dyn->size_limit - dyn->cur_size; +} + + +/** + * Get the amount of formal HPACK used space in the table. + * @param dyn the pointer to the dynamic table structure + * @return the formal HPACK used space in the table + */ +MHD_FN_PURE_ mhd_static_inline dtbl_size_t +dtbl_get_used_formal (const struct mhd_HpackDTblContext *dyn) +{ + mhd_assert (dyn->size_limit >= dyn->cur_size); + return dyn->cur_size; +} + + +/* ** Location of entry information data based on entry position in the + table ** */ + +/** + * Get the pointer to the dynamic table entry by location position number. + * This is not the same as HPACK index. + * The result is undefined if the table has no entries. + * @param dyn the pointer to the dynamic table structure + * @param loc_pos the number of location position + * @return the pointer to the dynamic table entry, + * undefined if the table has no entries + */ +MHD_FN_PURE_ mhd_static_inline struct mhd_HpackDTblEntryInfo * +dtbl_pos_entry_info (struct mhd_HpackDTblContext *dyn, + dtbl_idx_ft loc_pos) +{ + mhd_assert (mhd_DTBL_VALUE_FITS (loc_pos)); + mhd_assert (! dtbl_is_empty (dyn)); + mhd_assert (dyn->num_entries > loc_pos); + mhd_assert (dyn->buf_alloc_size >= + mhd_DTBL_ENTRY_INFO_SIZE * dyn->num_entries); + return dtbl_get_infos (dyn) - loc_pos; +} + + +/** + * Get a const pointer to the dynamic table entry by location position number. + * This is not the same as HPACK index. + * The result is undefined if the table has no entries. + * @param dyn const pointer to the dynamic table structure + * @param loc_pos the number of location position + * @return the pointer to the dynamic table entry, + * undefined if the table has no entries + */ +MHD_FN_PURE_ mhd_static_inline const struct mhd_HpackDTblEntryInfo * +dtbl_pos_entry_infoc (const struct mhd_HpackDTblContext *dyn, + dtbl_idx_ft loc_pos) +{ + mhd_assert (mhd_DTBL_VALUE_FITS (loc_pos)); + mhd_assert (! dtbl_is_empty (dyn)); + mhd_assert (dyn->num_entries > loc_pos); + mhd_assert (dyn->buf_alloc_size >= + mhd_DTBL_ENTRY_INFO_SIZE * dyn->num_entries); + return dtbl_get_infosc (dyn) - loc_pos; +} + + +/** + * Get the pointer to the zero location entry information data. + * This is the highest address of the entries data location in the table. + * The result is undefined if the table has no entries. + * @param dyn the pointer to the dynamic table structure + * @return the pointer to the zero location entry info data, + * undefined if the table has no entries + */ +MHD_FN_PURE_ mhd_static_inline struct mhd_HpackDTblEntryInfo * +dtbl_zero_entry_info (struct mhd_HpackDTblContext *dyn) +{ + return dtbl_pos_entry_info (dyn, + 0u); +} + + +/** + * Get a const pointer to the zero location entry information data. + * This is the highest address of the entries data location in the table. + * The result is undefined if the table has no entries. + * @param dyn the pointer to the dynamic table structure + * @return const pointer to the zero location entry information data, + * undefined if the table has no entries + */ +MHD_FN_PURE_ mhd_static_inline const struct mhd_HpackDTblEntryInfo * +dtbl_zero_entry_infoc (const struct mhd_HpackDTblContext *dyn) +{ + return dtbl_pos_entry_infoc (dyn, + 0u); +} + + +/** + * Get the pointer to the table's edge entry information data. + * This is the lowest address of the entries data location in the table. + * The result is undefined if the table has no entries. + * @param dyn the pointer to the dynamic table structure + * @return the pointer to the table's edge entry information data, + * undefined if the table has no entries + */ +MHD_FN_PURE_ mhd_static_inline struct mhd_HpackDTblEntryInfo * +dtbl_edge_entry_info (struct mhd_HpackDTblContext *dyn) +{ + struct mhd_HpackDTblEntryInfo *const ptr = + dtbl_pos_entry_info (dyn, + dtbl_get_pos_edge (dyn)); + mhd_assert (((const void *) ptr) == \ + ((const void *) (dtbl_get_strs_buffc (dyn) + + dtbl_get_strs_ceiling (dyn)))); + return ptr; +} + + +/** + * Get a const pointer to the table edge entry information data. + * This is the lowest address of the entries data location in the table. + * The result is undefined if the table has no entries. + * @param dyn the pointer to the dynamic table structure + * @return const pointer to the table edge entry information data, + * undefined if the table has no entries + */ +MHD_FN_PURE_ mhd_static_inline const struct mhd_HpackDTblEntryInfo * +dtbl_edge_entry_infoc (const struct mhd_HpackDTblContext *dyn) +{ + const struct mhd_HpackDTblEntryInfo *const ptr = + dtbl_pos_entry_infoc (dyn, + dtbl_get_pos_edge (dyn)); + mhd_assert (((const void *) ptr) == \ + ((const void *) (dtbl_get_strs_buffc (dyn) + + dtbl_get_strs_ceiling (dyn)))); + return ptr; +} + + +/** + * Get the pointer to the newest entry information data. + * The result is undefined if the table has no entries. + * @param dyn the pointer to the dynamic table structure + * @return the pointer to the newest entry information data, + * undefined if the table has no entries + */ +MHD_FN_PURE_ mhd_static_inline struct mhd_HpackDTblEntryInfo * +dtbl_newest_entry_info (struct mhd_HpackDTblContext *dyn) +{ + return dtbl_pos_entry_info (dyn, + dtbl_get_pos_newest (dyn)); +} + + +/** + * Get a const pointer to the newest entry information data. + * The result is undefined if the table has no entries. + * @param dyn const pointer to the dynamic table structure + * @return const pointer to the newest entry information data, + * undefined if the table has no entries + */ +MHD_FN_PURE_ mhd_static_inline const struct mhd_HpackDTblEntryInfo * +dtbl_newest_entry_infoc (const struct mhd_HpackDTblContext *dyn) +{ + return dtbl_pos_entry_infoc (dyn, + dtbl_get_pos_newest (dyn)); +} + + +/** + * Get the pointer to the oldest entry information data. + * The result is undefined if the table has no entries. + * @param dyn the pointer to the dynamic table structure + * @return the pointer to the oldest entry information data, + * undefined if the table has no entries + */ +MHD_FN_PURE_ mhd_static_inline struct mhd_HpackDTblEntryInfo * +dtbl_oldest_entry_info (struct mhd_HpackDTblContext *dyn) +{ + return dtbl_pos_entry_info (dyn, + dtbl_get_pos_oldest (dyn)); +} + + +/** + * Get a const pointer to the oldest entry information data. + * The result is undefined if the table has no entries. + * @param dyn const pointer to the dynamic table structure + * @return const pointer to the oldest entry information data, + * undefined if the table has no entries + */ +MHD_FN_PURE_ mhd_static_inline const struct mhd_HpackDTblEntryInfo * +dtbl_oldest_entry_infoc (const struct mhd_HpackDTblContext *dyn) +{ + return dtbl_pos_entry_infoc (dyn, + dtbl_get_pos_oldest (dyn)); +} + + +/* ** Entries strings information based on the entry position in the table ** */ + +/** + * Get the total size of the strings of the entry. + * This is the minimal size required for the entry in the strings buffer. + * @param dyn the pointer to the dynamic table structure + * @param loc_pos the number of location position + * @return the total size of the strings of the entry + */ +MHD_FN_PURE_ mhd_static_inline dtbl_size_t +dtbl_pos_strs_size_min (const struct mhd_HpackDTblContext *dyn, + dtbl_idx_ft loc_pos) +{ + return dtbl_entr_strs_size_min (dtbl_pos_entry_infoc (dyn, + loc_pos)); +} + + +/** + * Get the total size of the strings of the entry plus standard slack size. + * This is the optimal size used for the entry in the strings buffer when the + * current insertion slot has enough space. + * @param dyn the pointer to the dynamic table structure + * @param loc_pos the number of location position + * @return the total size of the strings of the entry plus standard slack size + */ +MHD_FN_PURE_ mhd_static_inline dtbl_size_t +dtbl_pos_strs_size_optm (const struct mhd_HpackDTblContext *dyn, + dtbl_idx_ft loc_pos) +{ + return dtbl_entr_strs_size_optm (dtbl_pos_entry_infoc (dyn, + loc_pos)); +} + + +/** + * Get the formal HPACK size of the entry. + * The formal size of the entry is the size of the strings plus fixed + * HPACK per-entry overhead. + * @param dyn the pointer to the dynamic table structure + * @param loc_pos the number of location position + * @return the formal HPACK size of the entry + */ +MHD_FN_PURE_ mhd_static_inline dtbl_size_t +dtbl_pos_size_formal (const struct mhd_HpackDTblContext *dyn, + dtbl_idx_ft loc_pos) +{ + return dtbl_entr_size_formal (dtbl_pos_entry_infoc (dyn, + loc_pos)); +} + + +/** + * Get the position (offset) of the (inclusive) start of the entry's strings + * in the strings buffer. + * This points to the first byte of the entry's strings. If the entry has + * zero-length strings, the pointer denotes a (possibly zero-sized) area + * that may coincide with the start of the entry's slack (if any) or with + * the next entry's strings start (if present). + * @param dyn the pointer to the dynamic table structure + * @param loc_pos the number of location position + * @return the position (offset) of the (inclusive) start of the entry's strings + */ +MHD_FN_PURE_ mhd_static_inline dtbl_size_t +dtbl_pos_strs_start (const struct mhd_HpackDTblContext *dyn, + dtbl_idx_ft loc_pos) +{ + return dtbl_entr_strs_start (dtbl_pos_entry_infoc (dyn, + loc_pos)); +} + + +/** + * Get the position of the (exclusive) end of the entry's strings in the + * strings buffer. + * This points to the next char (byte) after the strings of the entry. + * @param dyn the pointer to the dynamic table structure + * @param loc_pos the number of location position + * @return the position of the end of the entry's strings in the strings buffer + */ +MHD_FN_PURE_ mhd_static_inline dtbl_size_t +dtbl_pos_strs_end_min (const struct mhd_HpackDTblContext *dyn, + dtbl_idx_ft loc_pos) +{ + return dtbl_entr_strs_end_min (dtbl_pos_entry_infoc (dyn, + loc_pos)); +} + + +/** + * Get the position after standard slack after the end of the entry's strings + * in the strings buffer. + * This points to the preferred position of the next entry's strings. + * @param dyn the pointer to the dynamic table structure + * @param loc_pos the number of location position + * @return the position of the end of the entry's strings in the strings buffer + */ +MHD_FN_PURE_ mhd_static_inline dtbl_size_t +dtbl_pos_strs_end_optm (const struct mhd_HpackDTblContext *dyn, + dtbl_idx_ft loc_pos) +{ + return dtbl_entr_strs_end_optm (dtbl_pos_entry_infoc (dyn, + loc_pos)); +} + + +/* ** Entries strings location information based on the pointer to the + entry ** */ + +/** + * Get a pointer to the (inclusive) start of the entry's strings in the + * strings buffer. + * This points to the first byte of the entry's strings. If the entry has + * zero-length strings, the pointer denotes a (possibly zero-sized) area + * that may coincide with the start of the entry's slack (if any) or with + * the next entry's strings start (if present). + * The result is undefined if the entry is not in the table. + * @param dyn the pointer to the dynamic table structure + * @param entr_inf the pointer to the entry information + * @return the pointer of the (inclusive) start of the entry's strings, + * result is undefined if the entry is not in the table + */ +MHD_FN_PURE_ mhd_static_inline char * +dtbl_entr_strs_ptr_start (struct mhd_HpackDTblContext *dyn, + const struct mhd_HpackDTblEntryInfo *entr_inf) +{ + mhd_assert (dtbl_zero_entry_infoc (dyn) >= entr_inf); + mhd_assert (dtbl_edge_entry_infoc (dyn) <= entr_inf); + return dtbl_get_strs_buff (dyn) + dtbl_entr_strs_start (entr_inf); +} + + +/** + * Get const pointer to the (inclusive) start of the entry's strings in the + * strings buffer. + * This points to the first byte of the entry's strings. If the entry has + * zero-length strings, the pointer denotes a (possibly zero-sized) area + * that may coincide with the start of the entry's slack (if any) or with + * the next entry's strings start (if present). + * The result is undefined if the entry is not in the table. + * @param dyn the pointer to the dynamic table structure + * @param entr_inf the pointer to the entry information + * @return const pointer of the (inclusive) start of the entry's strings, + * result is undefined if the entry is not in the table + */ +MHD_FN_PURE_ mhd_static_inline const char * +dtbl_entr_strs_ptr_startc (const struct mhd_HpackDTblContext *dyn, + const struct mhd_HpackDTblEntryInfo *entr_inf) +{ + mhd_assert (dtbl_zero_entry_infoc (dyn) >= entr_inf); + mhd_assert (dtbl_edge_entry_infoc (dyn) <= entr_inf); + return dtbl_get_strs_buffc (dyn) + dtbl_entr_strs_start (entr_inf); +} + + +/** + * Get a pointer to the (exclusive) end of the entry's strings in the + * strings buffer. + * This points to the next char (byte) after the strings of the entry. + * The result is undefined if the entry is not in the table. + * @param dyn the pointer to the dynamic table structure + * @param entr_inf the pointer to the entry information + * @return the pointer to the (exclusive) end of the entry's strings, + * result is undefined if the entry is not in the table + */ +MHD_FN_PURE_ mhd_static_inline char * +dtbl_entr_strs_ptr_end (struct mhd_HpackDTblContext *dyn, + const struct mhd_HpackDTblEntryInfo *entr_inf) +{ + mhd_assert (dtbl_zero_entry_infoc (dyn) >= entr_inf); + mhd_assert (dtbl_edge_entry_infoc (dyn) <= entr_inf); + return dtbl_get_strs_buff (dyn) + dtbl_entr_strs_end_min (entr_inf); +} + + +/** + * Get a const pointer to the (exclusive) end of the entry's strings in the + * strings buffer. + * This points to the next char (byte) after the strings of the entry. + * The result is undefined if the entry is not in the table. + * @param dyn const pointer to the dynamic table structure + * @param entr_inf const pointer to the entry information + * @return const pointer to the (exclusive) end of the entry's strings, + * result is undefined if the entry is not in the table + */ +MHD_FN_PURE_ mhd_static_inline const char * +dtbl_entr_strs_ptr_endc (const struct mhd_HpackDTblContext *dyn, + const struct mhd_HpackDTblEntryInfo *entr_inf) +{ + mhd_assert (dtbl_zero_entry_infoc (dyn) >= entr_inf); + mhd_assert (dtbl_edge_entry_infoc (dyn) <= entr_inf); + return dtbl_get_strs_buffc (dyn) + dtbl_entr_strs_end_min (entr_inf); +} + + +/** + * Get a const pointer to the (exclusive) end of the entry's standard slack + * after the entry's strings in the strings buffer. + * This points to the preferred location of the next entry's strings. + * The result is undefined if the entry is not in the table. + * @param dyn const pointer to the dynamic table structure + * @param entr_inf const pointer to the entry information + * @return const pointer to the (exclusive) end of the entry's standard slack, + * result is undefined if the entry is not in the table + */ +MHD_FN_PURE_ mhd_static_inline const char * +dtbl_entr_strs_ptr_end_slackc (const struct mhd_HpackDTblContext *dyn, + const struct mhd_HpackDTblEntryInfo *entr_inf) +{ + mhd_assert (dtbl_zero_entry_infoc (dyn) >= entr_inf); + mhd_assert (dtbl_edge_entry_infoc (dyn) <= entr_inf); + return dtbl_get_strs_buffc (dyn) + dtbl_entr_strs_end_optm (entr_inf); +} + + +/** + * Get const pointer to the entry's name. + * This points to the first byte of the entry's name. If the entry has + * zero-length name, the pointer denotes a zero-sized area. + * The result is undefined if the entry is not in the table. + * @param dyn the pointer to the dynamic table structure + * @param entr_inf the pointer to the entry information + * @return const pointer to the entry's name, + * result is undefined if the entry is not in the table + */ +MHD_FN_PURE_ mhd_static_inline const char * +dtbl_entr_strs_ptr_namec (const struct mhd_HpackDTblContext *dyn, + const struct mhd_HpackDTblEntryInfo *entr_inf) +{ + return dtbl_entr_strs_ptr_startc (dyn, + entr_inf); +} + + +/** + * Get const pointer to the entry's value. + * This points to the first byte of the entry's value. If the entry has + * zero-length value, the pointer denotes a zero-sized area. + * The result is undefined if the entry is not in the table. + * @param dyn the pointer to the dynamic table structure + * @param entr_inf the pointer to the entry information + * @return const pointer to the entry's value, + * result is undefined if the entry is not in the table + */ +MHD_FN_PURE_ mhd_static_inline const char * +dtbl_entr_strs_ptr_valuec (const struct mhd_HpackDTblContext *dyn, + const struct mhd_HpackDTblEntryInfo *entr_inf) +{ + return dtbl_entr_strs_ptr_startc (dyn, + entr_inf) + entr_inf->name_len; +} + + +/* ** Information about the entry in the table based on the pointer to + the entry ** */ + +/** + * Get the size of the space between entry's strings and entry information data + * as if the provided entry were an edge entry. + * The gap could be zero in some conditions. + * The result is undefined if the entry is not in the table. + * @param dyn const pointer to the dynamic table structure + * @param entr_inf const pointer to the entry information + * @return the size of the space between entry's strings and information, + * result is undefined if the entry is not in the table + */ +MHD_FN_PURE_ mhd_static_inline dtbl_size_t +dtbl_entr_as_edge_get_gap (const struct mhd_HpackDTblContext *dyn, + const struct mhd_HpackDTblEntryInfo *entr_inf) +{ + const char *upper_ptr = (const char *) entr_inf; + const char *lower_ptr = dtbl_entr_strs_ptr_endc (dyn, entr_inf); + const dtbl_size_ft gap = (dtbl_size_ft) (upper_ptr - lower_ptr); + + mhd_assert (dtbl_zero_entry_infoc (dyn) >= entr_inf); + mhd_assert (dtbl_edge_entry_infoc (dyn) <= entr_inf); + mhd_assert (lower_ptr <= upper_ptr); + mhd_assert (mhd_DTBL_VALUE_FITS (gap)); + mhd_assert (gap < dyn->buf_alloc_size); + + return (dtbl_size_t) gap; +} + + +/* ** Entries strings location information based on entry position in + the table ** */ + +/** + * Get a pointer to the (inclusive) start of the entry's strings in the + * strings buffer. + * This points to the first char (byte) of the entry's strings. If the entry + * has zero-length strings then this points to the first byte of entry slack + * (if any) or the first char of the next entry's strings (if any). + * The result is undefined if the location number is equal to or greater than + * the number of entries in the table. + * @param dyn the pointer to the dynamic table structure + * @param loc_pos the number of location position + * @return the pointer to the (inclusive) start of the entry's strings + */ +MHD_FN_PURE_ mhd_static_inline char * +dtbl_pos_strs_ptr_start (struct mhd_HpackDTblContext *dyn, + dtbl_idx_ft loc_pos) +{ + return dtbl_entr_strs_ptr_start (dyn, + dtbl_pos_entry_info (dyn, + loc_pos)); +} + + +/** + * Get a const pointer to the (inclusive) start of the entry's strings in the + * strings buffer. + * This points to the first char (byte) of the entry's strings. If the entry + * has zero-length strings then this points to the first byte of entry slack + * (if any) or the first char of the next entry's strings (if any). + * The result is undefined if the location number is equal to or greater than + * the number of entries in the table. + * @param dyn const pointer to the dynamic table structure + * @param loc_pos the number of location position + * @return const pointer to the (inclusive) start of the entry's strings, + * result is undefined if the entry is not in the table + */ +MHD_FN_PURE_ mhd_static_inline const char * +dtbl_pos_strs_ptr_startc (const struct mhd_HpackDTblContext *dyn, + dtbl_idx_ft loc_pos) +{ + return dtbl_entr_strs_ptr_startc (dyn, + dtbl_pos_entry_infoc (dyn, + loc_pos)); +} + + +/** + * Get a pointer to the (exclusive) end of the entry's strings in the + * strings buffer. + * This points to the next char (byte) after the strings of the entry. + * The result is undefined if the location number is equal or greater than the + * number of entries in the table. + * @param dyn the pointer to the dynamic table structure + * @param loc_pos the number of location position + * @return the pointer to the (exclusive) end of the entry's strings + */ +MHD_FN_PURE_ mhd_static_inline char * +dtbl_pos_strs_ptr_end (struct mhd_HpackDTblContext *dyn, + dtbl_idx_ft loc_pos) +{ + return dtbl_entr_strs_ptr_end (dyn, + dtbl_pos_entry_info (dyn, + loc_pos)); +} + + +/** + * Get a const pointer to the (exclusive) end of the entry's strings in the + * strings buffer. + * This points to the next char (byte) after the strings of the entry. + * The result is undefined if the location number is equal or greater than the + * number of entries in the table. + * @param dyn const pointer to the dynamic table structure + * @param loc_pos the number of location position + * @return const pointer to the (exclusive) end of the entry's strings + */ +MHD_FN_PURE_ mhd_static_inline const char * +dtbl_pos_strs_ptr_endc (const struct mhd_HpackDTblContext *dyn, + dtbl_idx_ft loc_pos) +{ + return dtbl_entr_strs_ptr_endc (dyn, + dtbl_pos_entry_infoc (dyn, + loc_pos)); +} + + +/** + * Get a const pointer to the (exclusive) end of the entry's standard slack + * after the entry's strings in the strings buffer. + * This points to the preferred location of the next entry's strings. + * The result is undefined if the location number is equal or greater than the + * number of entries in the table. + * @param dyn const pointer to the dynamic table structure + * @param loc_pos the number of location position + * @return const pointer to the (exclusive) end of the entry's standard slack + */ +MHD_FN_PURE_ mhd_static_inline const char * +dtbl_pos_strs_ptr_end_slackc (const struct mhd_HpackDTblContext *dyn, + dtbl_idx_ft loc_pos) +{ + return dtbl_entr_strs_ptr_end_slackc (dyn, + dtbl_pos_entry_infoc (dyn, + loc_pos)); +} + + +/* ** Information about the entry in the table based on entry position in + the table ** */ + +/** + * Get the size of the space between entry's strings and entry information data + * as if the provided entry were an edge entry. + * The gap could be zero in some conditions. + * The result is undefined if the location number is equal or greater than the + * number of entries in the table. + * @param dyn const pointer to the dynamic table structure + * @param loc_pos the number of location position + * @return the size of the space between entry's strings and information + */ +MHD_FN_PURE_ mhd_static_inline dtbl_size_t +dtbl_pos_as_edge_get_gap (const struct mhd_HpackDTblContext *dyn, + dtbl_idx_ft loc_pos) +{ + return dtbl_entr_as_edge_get_gap (dyn, + dtbl_pos_entry_infoc (dyn, + loc_pos)); +} + + +/* ** Additional means of access to the entries information ** */ + +/** + * Get table's entries information as a pointer to an array. + * + * The returned array has #dtbl_get_num_entries() elements. + * The returned pointer becomes invalid if any entry is added or evicted + * from the table. + * + * Behaviour is undefined if table is empty. + * @param dyn the pointer to the dynamic table structure + * @return table's entries information as a pointer to an array + */ +MHD_FN_PURE_ mhd_static_inline struct mhd_HpackDTblEntryInfo * +dtbl_get_infos_as_array (struct mhd_HpackDTblContext *dyn) +{ + return dtbl_edge_entry_info (dyn); +} + + +/** + * Get table's entries information as a pointer to a const array. + * + * The returned array has #dtbl_get_num_entries() elements. + * + * The first (zero index) item in the array is the edge entry, the last item + * in the array is zero position entry. + * + * The returned pointer becomes invalid if any entry is added or evicted + * from the table. + * + * Behaviour is undefined if table is empty. + * @param dyn the pointer to the dynamic table structure + * @return table's entries information as a pointer to a const array + */ +MHD_FN_PURE_ mhd_static_inline const struct mhd_HpackDTblEntryInfo * +dtbl_get_infos_as_arrayc (const struct mhd_HpackDTblContext *dyn) +{ + return dtbl_edge_entry_infoc (dyn); +} + + +/* ** Additional information about the table ** */ + +/** + * Get the size of the free space available for new entries (including + * entry's strings, entry info data, and per-entry slack) between the + * string region and the entry-info region in the shared buffer. + * + * The gap could be zero in some conditions. + + * Unlike #dtbl_bottom_gap(), this space is used for both strings data and + * entries info data when adding new entries. + * + * This is not the formal HPACK free size. + * @param dyn const pointer to the dynamic table structure + * @return the size of the space available at the edge of the table + */ +MHD_FN_PURE_ mhd_static_inline dtbl_size_t +dtbl_edge_gap (const struct mhd_HpackDTblContext *dyn) +{ + if (dtbl_is_empty (dyn)) + return dyn->buf_alloc_size; + + return dtbl_entr_as_edge_get_gap (dyn, + dtbl_edge_entry_infoc (dyn)); +} + + +/** + * Get the size of the free space available for strings at the bottom of + * the shared buffer. + * + * Unlike #dtbl_edge_gap(), if table is not empty, this space can be used only + * for the strings data for an entry added at zero position. + * + * @param dyn const pointer to the dynamic table structure + * @return the size of the space available at the bottom of the table + */ +MHD_FN_PURE_ mhd_static_inline dtbl_size_t +dtbl_bottom_gap (const struct mhd_HpackDTblContext *dyn) +{ + if (dtbl_is_empty (dyn)) + return dyn->buf_alloc_size; + + return dtbl_pos_strs_start (dyn, 0u); +} + + +/* ** Manipulating strings in the dynamic table ** */ + +/** + * Choose the offset of the strings in the strings buffer for a new entry + * following another entry (non-zero position). + * + * If enough space is available, up to the standard slack bytes are left + * between entries' strings. + * + * Result is undefined if @a size_of_space is less than @a entry_strs_size. + * @param space_start the offset of the start (inclusive) of free space in + * the buffer + * @param size_of_space the amount of free space at the @a space_start offset + * @param entry_strs_size the size of new entries strings + * @return the offset to put entries strings in the buffer + */ +MHD_FN_CONST_ mhd_static_inline dtbl_size_t +dtbl_choose_strs_offset_for_size (dtbl_size_ft space_start, + dtbl_size_ft size_of_space, + dtbl_size_ft entry_strs_size) +{ + const dtbl_size_ft extra_space = size_of_space - entry_strs_size; + + mhd_assert (size_of_space >= entry_strs_size); + mhd_assert (mhd_DTBL_VALUE_FITS (space_start)); + mhd_assert (mhd_DTBL_VALUE_FITS (size_of_space)); + mhd_assert (mhd_DTBL_VALUE_FITS (entry_strs_size)); + + if (mhd_dtbl_entry_slack <= extra_space) + return (dtbl_size_t) (space_start + mhd_dtbl_entry_slack); + + return (dtbl_size_t) (space_start + extra_space); +} + + +/** + * Completely reset dynamic table data. + * This fully removes all entries from the table, leaving the size of the table + * and the table allocation the same. + * @param dyn the pointer to the dynamic table structure + */ +mhd_static_inline void +dtbl_reset (struct mhd_HpackDTblContext *dyn) +{ + dyn->num_entries = 0u; + dyn->newest_pos = 0u; + dyn->cur_size = 0u; +} + + +/** + * Move selected entries' strings in the strings buffer down (to the start of + * the buffer). + * The strings are moved for all entries from @a from_pos up to the + * edge (highest number) entry. + * @param dyn the pointer to the dynamic table structure + * @param from_pos the first entry position to move strings + * @param shift_down_size the amount of bytes to shift + */ +static void +dtbl_move_strs_down (struct mhd_HpackDTblContext *dyn, + dtbl_idx_ft from_pos, + dtbl_size_ft shift_down_size) +{ + char *move_area_src = dtbl_pos_strs_ptr_start (dyn, + from_pos); + size_t move_area_size = + (size_t) + (dtbl_pos_strs_ptr_endc (dyn, + dtbl_get_pos_edge (dyn)) - move_area_src); + dtbl_idx_ft i; + + mhd_assert (mhd_DTBL_VALUE_FITS (from_pos)); + mhd_assert (mhd_DTBL_VALUE_FITS (shift_down_size)); + mhd_assert (0u != shift_down_size); + mhd_assert (dtbl_get_pos_edge (dyn) >= from_pos); + mhd_assert (shift_down_size <= dtbl_pos_strs_start (dyn, from_pos)); + mhd_assert ((0u == from_pos) || \ + (dtbl_pos_strs_end_min (dyn, from_pos - 1u) <= \ + dtbl_pos_strs_start (dyn, from_pos) - shift_down_size)); + mhd_assert ((0u != from_pos) || \ + (dtbl_bottom_gap (dyn) >= shift_down_size)); + mhd_assert (dtbl_edge_gap (dyn) < dyn->buf_alloc_size); + mhd_assert (dyn->buf_alloc_size > move_area_size); + + /* Optimisation ideas: instead of shifting all entries uniformly, they + * can be "compressed" by eliminating the slack between some of the top + * entries. This will require more processing, more movements on the next + * rounds, but saves a lot if the dynamic table is large. */ + + /* Move all strings in the buffer for selected entries */ + memmove (move_area_src - shift_down_size, + move_area_src, + move_area_size); + +#ifndef NDEBUG + /* Zero-out standard string slack of the last entry strings */ + if (mhd_dtbl_entry_slack <= shift_down_size) + memset (move_area_src - shift_down_size + move_area_size, + 0, + mhd_dtbl_entry_slack); + else + memset (move_area_src - shift_down_size + move_area_size, + 0, + shift_down_size); +#endif /* ! NDEBUG */ + + for (i = from_pos; dtbl_get_pos_edge (dyn) >= i; ++i) + dtbl_pos_entry_info (dyn, + i)->offset -= (dtbl_size_t) shift_down_size; +} + + +/** + * Move selected entries' strings in the strings buffer up (to the entries + * information data). + * The strings are moved for all entries from @a from_entry up to the + * edge (highest number) entry. + * @param dyn the pointer to the dynamic table structure + * @param from_pos the first entry position to move strings + * @param shift_up_size the amount of bytes to shift + */ +static void +dtbl_move_strs_up (struct mhd_HpackDTblContext *dyn, + dtbl_idx_ft from_pos, + dtbl_size_ft shift_up_size) +{ + char *move_area_src = dtbl_pos_strs_ptr_start (dyn, + from_pos); + size_t move_area_size = + (size_t) + (dtbl_pos_strs_ptr_endc (dyn, + dtbl_get_pos_edge (dyn)) - move_area_src); + dtbl_idx_ft i; + + mhd_assert (mhd_DTBL_VALUE_FITS (shift_up_size)); + mhd_assert (0u != shift_up_size); + mhd_assert (dtbl_get_pos_edge (dyn) >= from_pos); + mhd_assert (shift_up_size < (dyn->buf_alloc_size)); + mhd_assert (dtbl_edge_gap (dyn) >= shift_up_size); + mhd_assert (dyn->buf_alloc_size > move_area_size); + + /* Optimisation ideas: instead of shifting all entries uniformly, they + * can be "compacted" by eliminating the slack between some of the bottom + * entries. This will require more processing and probably more movements on + * the next rounds, but saves a lot if the dynamic table is large. */ + +#ifndef NDEBUG + /* Zero-out standard string slack of the last entry strings AFTER the moved + data if space is available */ + if (1) + { + const dtbl_size_ft top_gap_final = dtbl_edge_gap (dyn) - shift_up_size; + + if (mhd_dtbl_entry_slack <= top_gap_final) + memset (move_area_src + shift_up_size + move_area_size, + 0, + mhd_dtbl_entry_slack); + else if (0u != top_gap_final) + memset (move_area_src + shift_up_size + move_area_size, + 0, + top_gap_final); + } +#endif /* ! NDEBUG */ + + /* Move all strings in the buffer for selected entries */ + memmove (move_area_src + shift_up_size, + move_area_src, + move_area_size); + + for (i = from_pos; dtbl_get_pos_edge (dyn) >= i; ++i) + dtbl_pos_entry_info (dyn, + i)->offset += (dtbl_size_t) shift_up_size; +} + + +/** + * Compact strings in the shared buffer so that all currently unused space + * is located at the edge (between the strings region and entry information + * data region). + * + * If the newest entry is not the edge entry, the function removes any extra + * gap between the newest and the oldest entries, keeping only the standard + * slack. Otherwise, the extra gap at the bottom of the buffer is eliminated. + * + * The function does not change the number of entries or their formal sizes. + * Behaviour is undefined if table's internal data is not consistent. + * @param dyn the pointer to the dynamic table structure + */ +static void +dtbl_compact_strs (struct mhd_HpackDTblContext *dyn) +{ + if (dtbl_get_pos_edge (dyn) != dtbl_get_pos_newest (dyn)) + { + /* Remove extra space between the newest and the oldest, + leave the standard slack only. */ + const dtbl_size_t strs_start_optimal = + dtbl_pos_strs_end_optm (dyn, + dtbl_get_pos_newest (dyn)); + const dtbl_size_t strs_start_current = + dtbl_pos_strs_start (dyn, + dtbl_get_pos_oldest (dyn)); + if (strs_start_current > strs_start_optimal) + { + /* There is an extra slack */ + const dtbl_size_t shift_size = strs_start_current - strs_start_optimal; + dtbl_move_strs_down (dyn, + dtbl_get_pos_oldest (dyn), + shift_size); + } + } + else + { + /* Remove extra space at the bottom of the strings */ + const dtbl_size_t shift_size = dtbl_pos_strs_start (dyn, + 0u); + + /* If there is an extra space - remove it */ + if (0u != shift_size) + dtbl_move_strs_down (dyn, + 0u, + shift_size); + } + /* All the free space must be at the edge of the buffer. + The buffer allocation is larger than the formal table size. */ + mhd_assert (dtbl_edge_gap (dyn) > dtbl_get_free_formal (dyn)); +} + + +/** + * Choose the offset of the strings in the strings buffer for a new entry + * following another entry (non-zero position). + * + * If enough space is available, leave up to the standard slack between + * entries' strings. + * + * Result is undefined if @a space_end is less than @a space_start. + * Result is undefined if not enough space for @a entry_strs_size is in + * between @a space_start and @a space_end. + * @param space_start the offset of the start (inclusive) of free space in + * the buffer + * @param space_end the offset of the end (exclusive) of free space in + * the buffer + * @param entry_strs_size the size of new entries strings + * @return the offset to put entries strings in the buffer + */ +MHD_FN_CONST_ mhd_static_inline dtbl_size_t +dtbl_choose_strs_offset (dtbl_size_ft space_start, + dtbl_size_ft space_end, + dtbl_size_ft entry_strs_size) +{ + const dtbl_size_ft space_size = space_end - space_start; + + mhd_assert (space_start <= space_end); + mhd_assert (mhd_DTBL_VALUE_FITS (space_start)); + mhd_assert (mhd_DTBL_VALUE_FITS (space_end)); + mhd_assert (mhd_DTBL_VALUE_FITS (space_size)); + mhd_assert (space_end >= space_size); + + return (dtbl_size_t) dtbl_choose_strs_offset_for_size (space_start, + space_size, + entry_strs_size); +} + + +#ifndef NDEBUG + +/** + * Zero-out end up to mhd_dtbl_entry_slack at the end of the strings of some + * entry. + * The input data is a pointer to the end of strings and available space. + * @param entr_strs_end_ptr the pointer to the end of the strings + * @param space_available amount of space before next used memory area + */ +mhd_static_inline void +dtbl_zeroout_strs_slack_ptr_space (char *entr_strs_end_ptr, + dtbl_size_ft space_available) +{ + const dtbl_size_ft zero_out_size = + (mhd_dtbl_entry_slack <= space_available) ? + mhd_dtbl_entry_slack : space_available; + mhd_assert (mhd_DTBL_VALUE_FITS (space_available)); + + if (0u != space_available) + memset (entr_strs_end_ptr, + 0, + zero_out_size); +} + + +/** + * Zero-out end up to mhd_dtbl_entry_slack at the end of the strings of some + * entry. + * The input data is an offset of the end of strings and available space. + * @param dyn pointer to the dynamic table structure + * @param entr_strs_end_offset the offset of the end of the strings + * @param space_available amount of space before next used memory area + */ +mhd_static_inline void +dtbl_zeroout_strs_slack_offset_space (struct mhd_HpackDTblContext *dyn, + dtbl_size_ft entr_strs_end_offset, + dtbl_size_ft space_available) +{ + mhd_assert (dyn->buf_alloc_size >= entr_strs_end_offset); + mhd_assert (dyn->buf_alloc_size >= space_available); + mhd_assert (dyn->buf_alloc_size >= (entr_strs_end_offset + space_available)); + dtbl_zeroout_strs_slack_ptr_space (dtbl_get_strs_buff (dyn) + + entr_strs_end_offset, + space_available); +} + + +/** + * Zero-out end up to mhd_dtbl_entry_slack at the end of the strings of some + * entry. + * The input data is dynamic table struct, an offset of the end of strings and + * offset of the next data in the buffer. + * @param dyn pointer to the dynamic table structure + * @param entr_strs_end_offset the offset of the end of the strings + * @param next_data_offset the offset of the next used memory area in the + * buffer + */ +mhd_static_inline void +dtbl_zeroout_strs_slack_offset_next (struct mhd_HpackDTblContext *dyn, + dtbl_size_ft entr_strs_end_offset, + dtbl_size_ft next_data_offset) +{ + mhd_assert (dyn->buf_alloc_size >= entr_strs_end_offset); + mhd_assert (dyn->buf_alloc_size >= next_data_offset); + mhd_assert (next_data_offset >= entr_strs_end_offset); + dtbl_zeroout_strs_slack_offset_space (dyn, + entr_strs_end_offset, + next_data_offset + - entr_strs_end_offset); +} + + +/** + * Zero-out end up to mhd_dtbl_entry_slack at the end of the strings of some + * entry. + * @param dyn pointer to the dynamic table structure + * @param entry the pointer to the entry information data + * @param space_available amount of space before next used memory area + */ +mhd_static_inline void +dtbl_zeroout_strs_slack_entry_space (struct mhd_HpackDTblContext *dyn, + const struct mhd_HpackDTblEntryInfo *entry, + dtbl_size_ft space_available) +{ + mhd_assert (dyn->buf_alloc_size >= space_available); + dtbl_zeroout_strs_slack_ptr_space (dtbl_entr_strs_ptr_end (dyn, entry), + space_available); +} + + +/** + * Zero-out end up to mhd_dtbl_entry_slack at the end of the strings of some + * entry. + * @param dyn pointer to the dynamic table structure + * @param entry the pointer to the entry information data + * @param next_data_offset the offset of the next used memory area in the + * buffer + */ +mhd_static_inline void +dtbl_zeroout_strs_slack_entry_next (struct mhd_HpackDTblContext *dyn, + const struct mhd_HpackDTblEntryInfo *entry, + dtbl_size_ft next_data_offset) +{ + mhd_assert (dyn->buf_alloc_size >= next_data_offset); + dtbl_zeroout_strs_slack_offset_next (dyn, + dtbl_entr_strs_end_min (entry), + next_data_offset); +} + + +/** + * Zero-out end up to mhd_dtbl_entry_slack at the end of the strings of some + * entry. + * @param dyn pointer to the dynamic table structure + * @param loc_pos the number of location position + */ +mhd_static_inline void +dtbl_zeroout_strs_slack_pos (struct mhd_HpackDTblContext *dyn, + dtbl_idx_ft loc_pos) +{ + if (dtbl_get_pos_edge (dyn) == loc_pos) + dtbl_zeroout_strs_slack_entry_space (dyn, + dtbl_pos_entry_infoc (dyn, + loc_pos), + dtbl_edge_gap (dyn)); + else + dtbl_zeroout_strs_slack_offset_next (dyn, + dtbl_pos_strs_end_min (dyn, + loc_pos), + dtbl_pos_strs_start (dyn, + loc_pos + 1u)); +} + + +#else /* NDEBUG */ + +/** + * No-op macro in non-debug builds. + */ +#define dtbl_zeroout_strs_slack_ptr_space(ptr,space) ((void) 0) + +/** + * No-op macro in non-debug builds. + */ +#define dtbl_zeroout_strs_slack_offset_space(dyn,offset,space) ((void) 0) + +/** + * No-op macro in non-debug builds. + */ +#define dtbl_zeroout_strs_slack_offset_next(dyn,offset,next_offset) ((void) 0) + +/** + * No-op macro in non-debug builds. + */ +#define dtbl_zeroout_strs_slack_entry_space(dyn,entry,space) ((void) 0) + +/** + * No-op macro in non-debug builds. + */ +#define dtbl_zeroout_strs_slack_entry_next(dyn,entry,next_offset) ((void) 0) + +/** + * No-op macro in non-debug builds. + */ +#define dtbl_zeroout_strs_slack_pos(dyn,loc_pos) ((void) 0) + +#endif /* NDEBUG */ + +/** + * Copy strings to the strings buffer for a potential new entry. + * + * This function ONLY copies strings to the strings buffer. + * It does not create a new entry, nor update any numbers or limits. + * + * The caller may create a new entry pointing to copied strings and update + * related data in the dynamic table structure following the call of this + * function. + * + * The table data must be in consistent and valid state. + * + * In debug builds the function checks whether the copied data does not + * overwrite any other used data in the buffer. + * + * @param dyn pointer to the dynamic table structure + * @param name the name of the header, does NOT need to be zero-terminated + * @param val the value of the header, does NOT need to be zero terminated + * @param new_entry the pointer to the newly created entry; this entry must not + * be in the table; must contain the lengths of the name + * and the value corresponding to the strings pointed to by + * @a name and @a val respectively. + */ +static void +dtbl_new_entry_copy_entr_strs ( + struct mhd_HpackDTblContext *restrict dyn, + const char *restrict name, + const char *restrict val, + const struct mhd_HpackDTblEntryInfo *restrict new_entry) +{ + char *const strs_buff = dtbl_get_strs_buff (dyn); + +#ifndef __SANITIZE_ADDRESS__ +# ifdef HAVE_UINTPTR_T + /* The new entry must not be in the table */ + mhd_assert (dtbl_is_empty (dyn) || + (((uintptr_t) (const void*) dtbl_zero_entry_infoc (dyn)) < \ + (uintptr_t) (const void*) new_entry) || \ + (((uintptr_t) (const void*) dtbl_zero_entry_infoc (dyn)) > \ + (uintptr_t) (const void*) new_entry)); +# endif /* HAVE_UINTPTR_T */ +#endif /* ! __SANITIZE_ADDRESS__*/ + +#ifndef NDEBUG + if (1) + { + /* Find position of the entry which string is located after the new copied + strings. */ + dtbl_idx_ft i; + dtbl_size_ft next_data_offset = 0u; + for (i = 0u; dyn->num_entries > i; ++i) + { + /* Check whether the buffer area referenced in the new entry is not used + by other entries */ + mhd_assert ((0u == dtbl_pos_strs_size_min (dyn, i)) || \ + (dtbl_pos_strs_end_min (dyn, i) <= \ + dtbl_entr_strs_start (new_entry)) || \ + (dtbl_entr_strs_end_min (new_entry) <= \ + dtbl_pos_strs_start (dyn, i))); + + if (dtbl_entr_strs_end_min (new_entry) <= \ + dtbl_pos_strs_start (dyn, i)) + { + next_data_offset = dtbl_pos_strs_start (dyn, i); + break; + } + } + if (dyn->num_entries == i) + { + /* Adding strings are at the edge of the strings buffer */ + mhd_assert (0u == next_data_offset); + mhd_assert (dtbl_entr_strs_end_min (new_entry) <= \ + dtbl_get_strs_ceiling (dyn)); + next_data_offset = dtbl_get_strs_ceiling (dyn); + } + mhd_assert (dtbl_entr_strs_end_min (new_entry) <= next_data_offset); + dtbl_zeroout_strs_slack_entry_next (dyn, + new_entry, + next_data_offset); + } +#endif + + /* Do not use dtbl_entr_strs_ptr_start() here as it does not work with + entries outside the table. */ + if (0u != new_entry->name_len) + memcpy (strs_buff + dtbl_entr_strs_start (new_entry), + name, + new_entry->name_len); + if (0u != new_entry->val_len) + memcpy (strs_buff + dtbl_entr_strs_start (new_entry) + new_entry->name_len, + val, + new_entry->val_len); +} + + +/** + * Return a pointer to the slot for the next entry info. + * The new slot is assumed to be located at the next edge location (below + * the current edge entry location). + * This function neither modifies the table nor reserves memory. + * The returned pointer refers to writable but not yet initialised space + * inside the table buffer; the caller must fill it and then increment + * dyn->num_entries. + * The result is undefined if there is no space in the buffer for the + * additional entry info. + * @param dyn pointer to the dynamic table structure + * @return pointer to writable memory for the next entry info + */ +MHD_FN_PURE_ mhd_static_inline struct mhd_HpackDTblEntryInfo * +dtbl_new_edge_peek_slot (struct mhd_HpackDTblContext *dyn) +{ + mhd_assert (mhd_DTBL_ENTRY_INFO_SIZE <= dtbl_edge_gap (dyn)); + /* Do not call dtbl_pos_entry_info() as it works only with valid position + * numbers, while the new position number is not valid yet. */ + return dtbl_get_infos (dyn) - dyn->num_entries; +} + + +/* ** Intrusive dangerous functions ** */ + +/** + * Shift entries info data toward higher location positions by one location + * position, starting at the specified location position and INCLUDING the + * edge entry (i.e., the block [insert_pos_loc .. edge] is moved to + * [insert_pos_loc + 1 .. edge + 1]). The entry information at + * @a insert_pos_loc becomes uninitialised. + * + * Only entries information data are moved; strings in the buffer are not + * modified. + * + * Note: this function internally moves data downward as higher location + * numbers correspond to lower entry info addresses. + * + * This function does not update any table's data. The caller is responsible + * for setting a valid entry data at the @a insert_pos_loc position, updating + * the number of entries in the table, correcting the total size of the data + * in the table and probably updating the position of the newest entry. + * + * Behaviour is undefined if @a insert_pos_loc is not a valid position in the + * table or if the location of the next edge position is already used by the + * strings in the buffer. + * + * @warning This function leaves table's data in an inconsistent state, the + * caller should update the table's data properly. Until the data is fixed, + * many dynamic table helper functions will work incorrectly. + * + * @param dyn pointer to the dynamic table structure + * @param insert_pos_loc the location position of the first entry data to move + */ +mhd_static_inline void +dtbl_move_infos_up (struct mhd_HpackDTblContext *dyn, + const dtbl_idx_ft insert_pos_loc) +{ + mhd_assert (dtbl_get_pos_edge (dyn) >= insert_pos_loc); + mhd_assert (dtbl_edge_gap (dyn) >= mhd_DTBL_ENTRY_INFO_SIZE); + memmove (dtbl_new_edge_peek_slot (dyn), + dtbl_edge_entry_infoc (dyn), + (size_t) + ((dtbl_get_pos_edge (dyn) - insert_pos_loc + 1u) + * mhd_DTBL_ENTRY_INFO_SIZE)); +} + + +/** + * Shift entries info data for a contiguous range of locations toward lower + * location positions to the specified location position. + * The block [first .. last] is moved to [final .. final + last - first]. + * Depending on direction of the move, the entry-info slots in the range + * (final + last - first .. last] or in the range [first .. final) become + * uninitialised. + * + * Only entries information data are moved; strings in the buffer are not + * modified. + * + * This function does not update any table's data. The caller is responsible + * for updating the number of entries in the table, correcting the total size + * of the data in the table and probably updating the position of the newest + * entry. + * + * Behaviour is undefined if the specified positions are not valid for the + * table. + * + * @warning This function leaves table's data in inconsistent state, the caller + * should update the table's data properly. Until the data is fixed, many + * dynamic table helper functions will work incorrectly. + * + * @param dyn pointer to the dynamic table structure + * @param range_first_loc the first inclusive (lowest-numbered) entry position + * to move + * @param range_last_loc the last inclusive (higher number) entry position to + * move, could be equal to @a range_first_loc + * @param final_first_loc the final position location number of the first entry + */ +mhd_static_inline void +dtbl_move_infos_pos (struct mhd_HpackDTblContext *dyn, + const dtbl_idx_ft range_first_loc, + const dtbl_idx_ft range_last_loc, + const dtbl_idx_ft final_first_loc) +{ + /** Number of elements to move, including both the last and the first */ + const dtbl_idx_ft num_elements = range_last_loc - range_first_loc + 1u; + /** The final position location number of the last entry */ + const dtbl_idx_ft final_last_loc = final_first_loc + num_elements - 1u; + /* Do not use dtbl_pos_entry_info() here to avoid triggering asserts as + the table data can be inconsistent */ + struct mhd_HpackDTblEntryInfo *const zero_info_pos = dtbl_get_infos (dyn); + const struct mhd_HpackDTblEntryInfo *const src = + zero_info_pos - range_last_loc; + struct mhd_HpackDTblEntryInfo *const dst = zero_info_pos - final_last_loc; + mhd_assert ((dyn->buf_alloc_size / mhd_DTBL_ENTRY_INFO_SIZE) \ + >= range_first_loc); + mhd_assert ((dyn->buf_alloc_size / mhd_DTBL_ENTRY_INFO_SIZE) \ + >= range_last_loc); + mhd_assert ((dyn->buf_alloc_size / mhd_DTBL_ENTRY_INFO_SIZE) \ + >= final_first_loc); + mhd_assert ((dyn->buf_alloc_size / mhd_DTBL_ENTRY_INFO_SIZE) \ + >= final_last_loc); + mhd_assert (range_first_loc <= range_last_loc); + + if (range_first_loc == final_first_loc) + return; + + memmove (dst, + src, + (size_t) (num_elements * mhd_DTBL_ENTRY_INFO_SIZE)); +} + + +/* ** Manipulating functions ** */ + +#ifndef NDEBUG +/** + * Check internal consistency of the dynamic table internal data. + * @param dyn the pointer to the dynamic table structure to check + */ +static void +dtbl_check_internals (const struct mhd_HpackDTblContext *dyn) +{ + mhd_assert (0u != dyn->buf_alloc_size); + mhd_assert (dyn->buf_alloc_size > dyn->size_limit); + mhd_assert (dyn->cur_size <= dyn->size_limit); + mhd_assert (dyn->buf_alloc_size >= \ + (dyn->num_entries * mhd_DTBL_ENTRY_INFO_SIZE)); + mhd_assert (dyn->newest_pos <= dyn->num_entries); + if (dtbl_is_empty (dyn)) + { + mhd_assert (0u == dyn->cur_size); + mhd_assert (0u == dyn->newest_pos); + } + else + { + const struct mhd_HpackDTblEntryInfo *const zero_entry = + dtbl_zero_entry_infoc (dyn); + dtbl_size_ft counted_size = 0u; + dtbl_idx_ft i; + + mhd_assert (dyn->newest_pos < dyn->num_entries); + mhd_assert ((0u != dyn->cur_size) && \ + "Each entry has minimal size, even with zero-length strings"); + mhd_assert (dyn->cur_size >= \ + (dyn->num_entries * mhd_dtbl_entry_overhead)); + mhd_assert (dtbl_edge_gap (dyn) <= dyn->buf_alloc_size); + + /* Check zero entry individually */ + /* If the newest entry is the edge entry, zero entry may have gap + at the start of the buffer. */ + if (0u != dtbl_get_pos_oldest (dyn)) + { + mhd_assert ((0u == zero_entry->offset) && \ + "The extra gap between entries' strings is allowed only " \ + "between the newest and the oldest entries"); + } + mhd_assert (zero_entry->offset < dyn->buf_alloc_size); + mhd_assert (zero_entry->name_len < dyn->buf_alloc_size); + mhd_assert (zero_entry->val_len < dyn->buf_alloc_size); + mhd_assert (dtbl_entr_strs_end_min (zero_entry) < dyn->buf_alloc_size); + mhd_assert (dtbl_entr_strs_ptr_endc (dyn, zero_entry) <= \ + (const char*) dtbl_edge_entry_infoc (dyn)); + counted_size += dtbl_entr_size_formal (zero_entry); + mhd_assert (counted_size <= dyn->cur_size); + + for (i = 1u; i <= dtbl_get_pos_edge (dyn); ++i) + { + const struct mhd_HpackDTblEntryInfo *const check_entry = + dtbl_pos_entry_infoc (dyn, + i); + + mhd_assert ((dtbl_pos_strs_end_min (dyn, i - 1u) <= \ + dtbl_pos_strs_start (dyn, i)) && \ + "Strings data cannot overlap between entries"); + + if (dtbl_get_pos_oldest (dyn) != i) + mhd_assert ((dtbl_pos_strs_end_optm (dyn, i - 1u) >= \ + dtbl_pos_strs_start (dyn, i)) && \ + "The extra gap between entries' strings is allowed only " \ + "between the newest and the oldest entries"); + + mhd_assert (dtbl_pos_strs_start (dyn, i) < dyn->buf_alloc_size); + mhd_assert (check_entry->name_len < dyn->buf_alloc_size); + mhd_assert (check_entry->val_len < dyn->buf_alloc_size); + mhd_assert (dtbl_entr_strs_end_min (check_entry) < dyn->buf_alloc_size); + mhd_assert (dtbl_entr_strs_ptr_endc (dyn, check_entry) <= \ + (const char*) dtbl_edge_entry_infoc (dyn)); + if (dtbl_get_pos_edge (dyn) != i) + mhd_assert (0u != dtbl_pos_as_edge_get_gap (dyn, i)); + + counted_size += dtbl_entr_size_formal (check_entry); + mhd_assert (counted_size <= dyn->cur_size); + } + + mhd_assert (dyn->cur_size == counted_size); + } +} + + +#else /* NDEBUG */ +/* No-op in non-debug builds */ +#define dtbl_check_internals(dyn) ((void) 0) +#endif /* NDEBUG */ + +/** + * Add the first entry to the table + * + * The table must be empty otherwise the behaviour is undefined. + * The table must have enough space for the new entry. + * + * @param dyn the pointer to the dynamic table structure + * @param name_len the length of the @a name + * @param name the name of the header, does NOT need to be zero-terminated + * @param val_len the length of the @a val + * @param val the value of the header, does NOT need to be zero terminated + */ +static MHD_FN_PAR_IN_SIZE_ (3,2) MHD_FN_PAR_IN_SIZE_ (5,4) void +dtbl_add_first_entry (struct mhd_HpackDTblContext *restrict dyn, + const dtbl_size_ft name_len, + const char *restrict name, + const dtbl_size_ft val_len, + const char *restrict val) +{ + const dtbl_size_ft entry_strs_size = name_len + val_len; + struct mhd_HpackDTblEntryInfo new_entry; + + /* Check parameters */ + mhd_assert (mhd_DTBL_VALUE_FITS (name_len)); + mhd_assert (mhd_DTBL_VALUE_FITS (val_len)); + mhd_assert (mhd_DTBL_VALUE_FITS (entry_strs_size)); + mhd_assert (entry_strs_size >= name_len); + mhd_assert (entry_strs_size >= val_len); + + /* Check conditions */ + mhd_assert (dtbl_is_empty (dyn)); + + dtbl_check_internals (dyn); + + new_entry.name_len = (dtbl_size_t) name_len; + new_entry.val_len = (dtbl_size_t) val_len; + new_entry.offset = 0u; + + mhd_assert (dtbl_get_free_formal (dyn) >= \ + dtbl_entr_size_formal (&new_entry)); + mhd_assert (dtbl_edge_gap (dyn) == dtbl_get_strs_ceiling (dyn)); + mhd_assert (dtbl_get_strs_ceiling (dyn) >= \ + (dtbl_entr_strs_size_min (&new_entry) \ + + mhd_DTBL_ENTRY_INFO_SIZE)); + + dtbl_new_entry_copy_entr_strs (dyn, + name, + val, + &new_entry); + + *(dtbl_new_edge_peek_slot (dyn)) = new_entry; + dyn->num_entries = 1u; + dyn->cur_size = dtbl_entr_size_formal (&new_entry); + mhd_assert (0u == dtbl_get_pos_newest (dyn)); +} + + +/** + * Add new entry into the table at the new edge position + * + * This function adds a new entry after the existing edge-position entry, + * updates all internal table data. + * The function does NOT move strings in the strings buffer. The table's + * buffer must have enough space for the new entry's strings and the new + * entry data. + * + * The newest entry must be the edge entry. + * The table must have enough space for the new entry. + * The table must not be empty otherwise behaviour is undefined. + * + * @param dyn the pointer to the dynamic table structure + * @param name_len the length of the @a name + * @param name the name of the header, does NOT need to be zero-terminated + * @param val_len the length of the @a val + * @param val the value of the header, does NOT need to be zero terminated + */ +static MHD_FN_PAR_IN_SIZE_ (3,2) MHD_FN_PAR_IN_SIZE_ (5,4) void +dtbl_add_new_entry_at_new_edge (struct mhd_HpackDTblContext *restrict dyn, + const dtbl_size_ft name_len, + const char *restrict name, + const dtbl_size_ft val_len, + const char *restrict val) +{ + /** The total size of the strings of the new entry */ + const dtbl_size_ft entry_strs_size = name_len + val_len; + struct mhd_HpackDTblEntryInfo new_entry; + + /* Check parameters */ + mhd_assert (mhd_DTBL_VALUE_FITS (name_len)); + mhd_assert (mhd_DTBL_VALUE_FITS (val_len)); + mhd_assert (mhd_DTBL_VALUE_FITS (entry_strs_size)); + mhd_assert (entry_strs_size >= name_len); + mhd_assert (entry_strs_size >= val_len); + mhd_assert (dtbl_get_free_formal (dyn) >= \ + dtbl_new_entry_size_formal (name_len, val_len)); + + /* Check conditions */ + mhd_assert (! dtbl_is_empty (dyn)); + mhd_assert (dtbl_get_pos_edge (dyn) == dtbl_get_pos_newest (dyn)); + mhd_assert (dtbl_edge_gap (dyn) >= \ + entry_strs_size + mhd_DTBL_ENTRY_INFO_SIZE); + + dtbl_check_internals (dyn); + + /* Inserting at the edge */ + /* The simple case: just add new data at the edge. The previous entry + * exists. */ + /* Both strings and the entry info data must be stored in this memory + area (edge gap). */ + + new_entry.name_len = (dtbl_size_t) name_len; + new_entry.val_len = (dtbl_size_t) val_len; + new_entry.offset = + dtbl_choose_strs_offset (dtbl_pos_strs_end_min (dyn, + dtbl_get_pos_edge (dyn)), + dtbl_get_strs_ceiling (dyn) + - mhd_DTBL_ENTRY_INFO_SIZE, + entry_strs_size); + + mhd_assert (dtbl_edge_gap (dyn) >= \ + dtbl_entr_strs_size_min (&new_entry) + mhd_DTBL_ENTRY_INFO_SIZE); + + dtbl_new_entry_copy_entr_strs (dyn, + name, + val, + &new_entry); + + *(dtbl_new_edge_peek_slot (dyn)) = new_entry; + dyn->newest_pos = dyn->num_entries; + ++(dyn->num_entries); + dyn->cur_size += dtbl_entr_size_formal (&new_entry); + + mhd_assert (dyn->cur_size > dtbl_entr_size_formal (&new_entry)); + mhd_assert (! dtbl_is_empty (dyn)); + mhd_assert (0u != dyn->newest_pos); + mhd_assert (dyn->size_limit >= dyn->cur_size); + /* The next assert evaluates dtbl_edge_gap(), which also checks the + strings/infos do not overlap. */ + mhd_assert (dyn->buf_alloc_size > dtbl_edge_gap (dyn)); +} + + +/** + * Insert new entry into the table after the current newest (latest added) + * entry. If the latest entry is at the edge, then the new entry is inserted + * at zero position. + * + * This function inserts a new entry, moving entries information data as + * necessary, updates all internal table data. + * The function does NOT move strings in the strings buffer. The strings + * buffer after the latest entry must have enough space for the new entry + * strings. + * + * This function never inserts an entry at the edge (zero position is used + * instead). + * The table must have enough space for the new entry. + * The table must not be empty otherwise behaviour is undefined. + * + * @param dyn the pointer to the dynamic table structure + * @param name_len the length of the @a name + * @param name the name of the header, does NOT need to be zero-terminated + * @param val_len the length of the @a val + * @param val the value of the header, does NOT need to be zero terminated + */ +static void +dtbl_insert_next_new_entry (struct mhd_HpackDTblContext *restrict dyn, + const dtbl_size_ft name_len, + const char *restrict name, + const dtbl_size_ft val_len, + const char *restrict val) +{ + /** The total size of the strings of the new entry */ + const dtbl_size_ft entry_strs_size = name_len + val_len; + const dtbl_idx_ft loc_pos = dtbl_get_pos_oldest (dyn); + const bool insert_at_zero = (0u == loc_pos); + /** The pointer to the insert entry. + The entry information data will be moved (together with higher numbered + entries) and new entry will be inserted to this location. */ + struct mhd_HpackDTblEntryInfo *const insert_entry_ptr = + dtbl_oldest_entry_info (dyn); + /** The offset of the start of the available space */ + const dtbl_size_ft avail_space_start = + insert_at_zero ? 0u : dtbl_entr_strs_end_min (dtbl_newest_entry_info (dyn)); + /** The offset of the end of the available space */ + const dtbl_size_ft avail_space_end = + dtbl_entr_strs_start (dtbl_oldest_entry_infoc (dyn)); + struct mhd_HpackDTblEntryInfo new_entry; + + /* Check parameters */ + mhd_assert (mhd_DTBL_VALUE_FITS (name_len)); + mhd_assert (mhd_DTBL_VALUE_FITS (val_len)); + mhd_assert (mhd_DTBL_VALUE_FITS (entry_strs_size)); + mhd_assert (entry_strs_size >= name_len); + mhd_assert (entry_strs_size >= val_len); + mhd_assert (dtbl_get_pos_prev (dyn, loc_pos) == dtbl_get_pos_newest (dyn)); + mhd_assert (dtbl_get_pos_edge (dyn) >= loc_pos); + /* Insertion as zero position is possible only if the newest entry + is the edge entry (and the insertion wraps to the other side of + the buffer). */ + mhd_assert (insert_at_zero == + (dtbl_get_pos_newest (dyn) == dtbl_get_pos_edge (dyn))); + + /* Check conditions */ + mhd_assert (! dtbl_is_empty (dyn)); + + dtbl_check_internals (dyn); + + /* The new entry must be inserted either between two entries or at zero + location position. The inserted entry is not at the edge (is followed by + another entry). */ + mhd_assert (avail_space_end >= avail_space_start); + + new_entry.name_len = (dtbl_size_t) name_len; + new_entry.val_len = (dtbl_size_t) val_len; + new_entry.offset = + insert_at_zero ? 0u : dtbl_choose_strs_offset (avail_space_start, + avail_space_end, + entry_strs_size); + + mhd_assert (avail_space_start <= new_entry.offset); + mhd_assert (avail_space_end >= new_entry.offset); + mhd_assert (avail_space_end >= new_entry.offset + entry_strs_size); + mhd_assert (avail_space_end >= dtbl_entr_strs_end_min (&new_entry)); + mhd_assert (dtbl_get_free_formal (dyn) >= \ + dtbl_entr_size_formal (&new_entry)); + + dtbl_new_entry_copy_entr_strs (dyn, + name, + val, + &new_entry); + + /* Move entries info data as the new entry data must be inserted */ + dtbl_move_infos_up (dyn, + loc_pos); + + *insert_entry_ptr = new_entry; + ++(dyn->num_entries); + dyn->newest_pos = (dtbl_idx_t) loc_pos; + dyn->cur_size += dtbl_entr_size_formal (&new_entry); + + mhd_assert (dyn->cur_size > dtbl_entr_size_formal (&new_entry)); + mhd_assert (dtbl_get_pos_edge (dyn) > dtbl_get_pos_newest (dyn)); + mhd_assert (! dtbl_is_empty (dyn)); + mhd_assert (dyn->size_limit >= dyn->cur_size); + /* The next assert calls dtbl_edge_gap() which force checking non-overlap + of entries and strings. */ + mhd_assert (dyn->buf_alloc_size > dtbl_edge_gap (dyn)); +} + + +/** + * Extend the table by inserting a new entry without prior eviction. + * + * The table must have enough formal free space for the new entry. + * Behaviour is undefined if table's internal data is not consistent. + * @param dyn the pointer to the dynamic table structure + * @param name_len the length of the @a name + * @param name the name of the header, does NOT need to be zero-terminated + * @param val_len the length of the @a val + * @param val the value of the header, does NOT need to be zero terminated + */ +static void +dtbl_extend_with_entry (struct mhd_HpackDTblContext *restrict dyn, + const dtbl_size_ft name_len, + const char *restrict name, + const dtbl_size_ft val_len, + const char *restrict val) +{ + const dtbl_size_ft entry_strs_size = name_len + val_len; + + mhd_assert (mhd_DTBL_VALUE_FITS (name_len)); + mhd_assert (mhd_DTBL_VALUE_FITS (val_len)); + mhd_assert (mhd_DTBL_VALUE_FITS (entry_strs_size)); + mhd_assert (entry_strs_size >= name_len); + mhd_assert (entry_strs_size >= val_len); + mhd_assert (dtbl_get_free_formal (dyn) >= \ + dtbl_new_entry_size_formal (name_len, val_len)); + + dtbl_check_internals (dyn); + + if (dtbl_is_empty (dyn)) + { + /* Empty table */ + dtbl_add_first_entry (dyn, + name_len, + name, + val_len, + val); + + return; /* Inserted at zero position */ + } + else if (dtbl_get_pos_newest (dyn) == dtbl_get_pos_edge (dyn)) + { + /* Current insert position is at the edge */ + + /* This section selects where to add a new entry. There are two options: + + insert at the edge; + + insert at the bottom (position wrap). */ + + /** The space left on the top for strings and the new entry info */ + const dtbl_size_ft top_gap = dtbl_edge_gap (dyn); + /** The space left on the bottom for strings */ + const dtbl_size_ft bottom_gap = dtbl_bottom_gap (dyn); + /* 'true' to insert at the edge, 'false' to insert at the bottom */ + bool insert_at_the_edge; + mhd_assert (! dtbl_is_empty (dyn)); + mhd_assert (0u != dyn->cur_size); + + if (mhd_DTBL_ENTRY_INFO_SIZE > top_gap) + { + /* Not enough space to add new entry info data */ + mhd_assert (0u != bottom_gap); + mhd_assert (top_gap + bottom_gap >= \ + mhd_DTBL_ENTRY_INFO_SIZE + entry_strs_size); + dtbl_move_strs_down (dyn, + 0u, + bottom_gap); + mhd_assert (0u == dtbl_bottom_gap (dyn)); + insert_at_the_edge = true; + } + else if (entry_strs_size + mhd_dtbl_entry_slack <= bottom_gap) + { + /* The new strings and the standard slack fully fit the bottom space + * in the buffer, the top space is enough for the new entry info. */ + insert_at_the_edge = false; + } + else if (entry_strs_size + mhd_dtbl_entry_slack + + mhd_DTBL_ENTRY_INFO_SIZE <= top_gap) + { + /* The new strings, the new entry info and the standard slack fully fit + * the top space in the buffer. */ + insert_at_the_edge = true; + } + else if (entry_strs_size <= bottom_gap) + { + /* The new strings without the standard slack fully fit the bottom space + * in the buffer, the top space is enough for the new entry info. */ + insert_at_the_edge = false; + } + else if (entry_strs_size + mhd_DTBL_ENTRY_INFO_SIZE <= top_gap) + { + /* The new strings without the standard slack and the new entry info + * fully fit the top space in the buffer. */ + insert_at_the_edge = true; + } + else + { + /* Neither top nor bottom of the buffer is enough for the new entry. + * The buffer needs to be moved. */ + /* Strings could be moved either down or up */ + /* As the strings must be moved in any case, move strings to the bottom + to insert the new entry at the edge and thus avoid moving entries + info data in memory. */ + mhd_assert (top_gap < entry_strs_size \ + + mhd_dtbl_entry_slack + mhd_DTBL_ENTRY_INFO_SIZE); + mhd_assert ((top_gap + bottom_gap >= \ + mhd_dtbl_entry_slack \ + + entry_strs_size \ + + mhd_dtbl_entry_slack + mhd_DTBL_ENTRY_INFO_SIZE) && \ + "The total allocation size of the buffer is larger than " \ + "required for strict HPACK. All extra size should be now " \ + "on the top and on the bottom, as all other strings " \ + "should now be place optimally or denser. The total free " \ + "space must be enough for the previous entry slack and " \ + "for complete new entry, including slack and info data."); + + dtbl_move_strs_down (dyn, + 0u, + bottom_gap); + + mhd_assert (dtbl_edge_gap (dyn) >= \ + entry_strs_size + mhd_DTBL_ENTRY_INFO_SIZE); + mhd_assert ((dtbl_edge_gap (dyn) >= \ + mhd_dtbl_entry_slack \ + + entry_strs_size \ + + mhd_dtbl_entry_slack + mhd_DTBL_ENTRY_INFO_SIZE) && \ + "Strings have been compacted up to optimal space or " + "denser. The free space should be enough for optimal " + "placement."); + insert_at_the_edge = true; + } + + if (insert_at_the_edge) + { + dtbl_add_new_entry_at_new_edge (dyn, + name_len, + name, + val_len, + val); + + return; /* Inserted at new edge position */ + } + } + else + { + /* Current insert position is in between two entries */ + + /** The end of the strings of the newest entry */ + const dtbl_size_ft newest_entry_end = + dtbl_pos_strs_end_min (dyn, + dtbl_get_pos_newest (dyn)); + /** The gap between the newest entry and the oldest entry */ + /** The start of the strings of the newest entry */ + const dtbl_size_ft oldest_entry_start = + dtbl_pos_strs_start (dyn, + dtbl_get_pos_oldest (dyn)); + const dtbl_size_ft inbetween_gap = + oldest_entry_start - newest_entry_end; + /** The space left on the top for the new entry info data */ + const dtbl_size_ft top_gap = dtbl_edge_gap (dyn); + /** The optimal space to place a new entry. + The size consist of standard slack for previous entry string, + the new entry strings and the standard slack for this entry. */ + const dtbl_size_ft optimal_inbetween_size = + mhd_dtbl_entry_slack + entry_strs_size + mhd_dtbl_entry_slack; + + mhd_assert (dtbl_get_pos_edge (dyn) != dtbl_get_pos_newest (dyn)); + mhd_assert (dtbl_get_pos_oldest (dyn) > dtbl_get_pos_newest (dyn)); + mhd_assert (oldest_entry_start >= newest_entry_end); + mhd_assert (0u != dtbl_get_pos_oldest (dyn)); + mhd_assert (0u != dyn->cur_size); + + mhd_assert (top_gap + inbetween_gap >= \ + entry_strs_size + mhd_DTBL_ENTRY_INFO_SIZE); + mhd_assert ((top_gap + inbetween_gap >= \ + optimal_inbetween_size + mhd_DTBL_ENTRY_INFO_SIZE) && \ + "This is not required for the insertion of the entry " \ + "but this is guaranteed by the checking the overall size " \ + "of the buffer before the insertion, so this is a check " \ + "for the overall handling logic."); + + if (mhd_DTBL_ENTRY_INFO_SIZE > top_gap) + { + /* Not enough space to add new entry info data */ + /* Shrink in-between space to the optimal entry strings size */ + const dtbl_size_ft shift_size = inbetween_gap - optimal_inbetween_size; + + mhd_assert (inbetween_gap > optimal_inbetween_size); + mhd_assert (top_gap + shift_size >= mhd_DTBL_ENTRY_INFO_SIZE); + + dtbl_move_strs_down (dyn, + dtbl_get_pos_oldest (dyn), + shift_size); + } + else if (inbetween_gap < entry_strs_size) + { + /* Not enough space to add new entry strings */ + /* Grow in-between space to the standard step */ + const dtbl_size_ft shift_size = optimal_inbetween_size - inbetween_gap; + + mhd_assert (inbetween_gap < optimal_inbetween_size); + mhd_assert (top_gap - shift_size >= mhd_DTBL_ENTRY_INFO_SIZE); + + dtbl_move_strs_up (dyn, + dtbl_get_pos_oldest (dyn), + shift_size); + } + } + + /* The new entry must be inserted either between two entries or at zero + location position. The inserted entry is not at the edge (is followed by + another entry). */ + /* Insertion to the empty table and insertion at the edge are handled + earlier. */ + dtbl_insert_next_new_entry (dyn, + name_len, + name, + val_len, + val); +} + + +/** + * Evict the oldest entries as needed and add a new entry. + * + * The formal size of the new entry must be less than or equal to the table + * maximum formal size. + * The table must NOT have enough free space to add a new entry without + * eviction. + * + * Behaviour is undefined if table's internal data is not consistent. + * @param dyn the pointer to the dynamic table structure + * @param name_len the length of the @a name + * @param name the name of the header, does NOT need to be zero-terminated + * @param val_len the length of the @a val + * @param val the value of the header, does NOT need to be zero terminated + */ +static MHD_FN_PAR_IN_SIZE_ (3,2) MHD_FN_PAR_IN_SIZE_ (5,4) void +dtbl_evict_add_entry (struct mhd_HpackDTblContext *restrict dyn, + const dtbl_size_ft name_len, + const char *restrict name, + const dtbl_size_ft val_len, + const char *restrict val) +{ + /** The total size of the strings of the new entry */ + const dtbl_size_ft entry_strs_size = name_len + val_len; + /** The starting eviction position */ + const dtbl_idx_ft eviction_start = + dtbl_get_pos_oldest (dyn); + /** The final (inclusive) eviction entry */ + dtbl_idx_ft eviction_end; + const dtbl_size_ft needed_evict_min = + dtbl_new_entry_size_formal (name_len, val_len) - dtbl_get_free_formal (dyn); + dtbl_size_ft evicted_size; + /** The total number of entries to evict */ + dtbl_idx_ft num_to_evict; + + mhd_assert (mhd_DTBL_VALUE_FITS (name_len)); + mhd_assert (mhd_DTBL_VALUE_FITS (val_len)); + mhd_assert (0u != dyn->cur_size); + mhd_assert (! dtbl_is_empty (dyn)); + mhd_assert (mhd_DTBL_VALUE_FITS (entry_strs_size)); + mhd_assert (entry_strs_size >= name_len); + mhd_assert (entry_strs_size >= val_len); + mhd_assert (dtbl_get_free_formal (dyn) < \ + dtbl_new_entry_size_formal (name_len, val_len)); + mhd_assert (dtbl_get_size_max_formal (dyn) >= \ + dtbl_new_entry_size_formal (name_len, val_len)); + mhd_assert (0u != needed_evict_min); + mhd_assert (needed_evict_min <= dyn->cur_size); + dtbl_check_internals (dyn); + + eviction_end = eviction_start; + evicted_size = dtbl_pos_size_formal (dyn, + eviction_end); + + while (needed_evict_min > evicted_size) + { + eviction_end = dtbl_get_pos_next (dyn, + eviction_end); + + mhd_assert (eviction_start != eviction_end); + + evicted_size += dtbl_pos_size_formal (dyn, + eviction_end); + } + mhd_assert (needed_evict_min <= evicted_size); +#ifdef MHD_USE_CODE_HARDENING + if (eviction_start > eviction_end) + num_to_evict = + eviction_end + dtbl_get_num_entries (dyn) - eviction_start + 1u; + else + num_to_evict = eviction_end - eviction_start + 1u; +#else /* ! MHD_USE_CODE_HARDENING */ + num_to_evict = + ((dtbl_get_num_entries (dyn) + eviction_end + - eviction_start) % dtbl_get_num_entries (dyn)) + 1u; +#endif /* ! MHD_USE_CODE_HARDENING */ + mhd_assert (0u != num_to_evict); + mhd_assert (dtbl_get_num_entries (dyn) >= num_to_evict); + + if (mhd_COND_ALMOST_NEVER (dtbl_get_num_entries (dyn) == num_to_evict)) + { + /* Simplest situation: evicted all existing entries completely */ + /* Processing: + + reset the table, + + add the new first entry */ + dtbl_reset (dyn); + dtbl_add_first_entry (dyn, + name_len, + name, + val_len, + val); + return; + } + else if (dtbl_get_pos_edge (dyn) == eviction_end) + { + /* Eviction area ends at the edge, at least one entry is not evicted. */ + /* Processing: + + reduce the number of entries in the table (evicted entries become + ignored), + + reduce the official size of the table, + + add the new entry at the edge. + No need to move the data in the table's buffer. */ + mhd_assert (eviction_end >= eviction_start); + mhd_assert ((0u == dtbl_pos_strs_start (dyn, 0u)) && \ + "An extra gap is allowed only between the newest and the " \ + "oldest entries. The newest entry was not the edge entry " \ + "before the eviction."); + mhd_assert (dtbl_get_pos_newest (dyn) == (eviction_start - 1u)); + + dyn->cur_size -= (dtbl_size_t) evicted_size; + dyn->num_entries = (dtbl_idx_t) eviction_start; + + dtbl_add_new_entry_at_new_edge (dyn, + name_len, + name, + val_len, + val); + return; + } + else if ((0u != eviction_start) && + (eviction_end >= eviction_start)) + { + /* Entries are evicted in between other entries, at least two entries + are not evicted (at the start and at the edge). */ + /* Processing: + + set strings size of the first evicted entry to zero (will be replaced + with new entry strings), + + remove other evicted entries information data (if any) by moving + higher numbered entries, + + reduce the official size of the table, + + move strings data in the buffer (if needed), + + replace the first evicted entry with the new entry. */ + struct mhd_HpackDTblEntryInfo *replace_entry_ptr = + dtbl_pos_entry_info (dyn, + eviction_start); + /** The last entry to keep before the evicted entries */ + dtbl_idx_ft last_entry_keep = dtbl_get_pos_prev (dyn, + eviction_start); + /** The first entry to keep after the evicted entries */ + dtbl_idx_ft first_entry_keep = dtbl_get_pos_next (dyn, + eviction_end); + /** Number of entries to keep at the edge (after evicted entries) */ + dtbl_idx_ft num_keep_at_edge = + dtbl_get_pos_edge (dyn) - first_entry_keep + 1u; + /** The position of the start of the space for the new entry strings */ + const dtbl_size_ft space_start = dtbl_pos_strs_end_min (dyn, + last_entry_keep); + /** The position of the end of the space for the new entry strings */ + dtbl_size_ft space_end = dtbl_pos_strs_start (dyn, + first_entry_keep); + dtbl_size_ft space_size = space_end - space_start; + struct mhd_HpackDTblEntryInfo new_entry; + + mhd_assert (first_entry_keep > last_entry_keep); + mhd_assert (dtbl_get_num_entries (dyn) - num_to_evict >= 2u); + mhd_assert (dtbl_get_pos_edge (dyn) >= first_entry_keep); + mhd_assert (0u != num_keep_at_edge); + mhd_assert (dtbl_get_num_entries (dyn) > num_keep_at_edge); + mhd_assert (space_end >= space_start); + mhd_assert (dyn->buf_alloc_size > space_size); + + replace_entry_ptr->name_len = 0u; + replace_entry_ptr->val_len = 0u; + /* Keep the entry to be replaced and move not evicted entries at the edge */ + dtbl_move_infos_pos (dyn, + first_entry_keep, + dtbl_get_pos_edge (dyn), + eviction_start + 1u); + /* Keep the standard overhead of the entry being replaced */ + dyn->cur_size -= (dtbl_size_t) (evicted_size - mhd_dtbl_entry_overhead); + dyn->num_entries -= (dtbl_idx_t) (num_to_evict - 1u); + + if (space_size < entry_strs_size) + { + /* No space to put the new entry strings. + * Need to move strings in the buffer. */ + const dtbl_size_ft shift_size = + mhd_dtbl_entry_slack + entry_strs_size + mhd_dtbl_entry_slack + - space_size; + mhd_assert (dtbl_edge_gap (dyn) > shift_size); + dtbl_move_strs_up (dyn, + eviction_start + 1u, + shift_size); + space_size = + mhd_dtbl_entry_slack + entry_strs_size + mhd_dtbl_entry_slack; + } + + mhd_assert (space_size >= entry_strs_size); + + new_entry.name_len = (dtbl_size_t) name_len; + new_entry.val_len = (dtbl_size_t) val_len; + new_entry.offset = dtbl_choose_strs_offset_for_size (space_start, + space_size, + entry_strs_size); + + mhd_assert (dtbl_get_num_entries (dyn) > (eviction_start + 1u)); + mhd_assert (dtbl_entr_strs_end_min (&new_entry) <= \ + dtbl_pos_strs_start (dyn, eviction_start + 1u)); + + dtbl_new_entry_copy_entr_strs (dyn, + name, + val, + &new_entry); + *replace_entry_ptr = new_entry; + /* Keep the standard overhead of the entry being replaced */ + dyn->cur_size += (dtbl_size_t) entry_strs_size; + mhd_assert ((dyn->newest_pos + 1u) == eviction_start); + dyn->newest_pos = (dtbl_idx_t) eviction_start; + + return; + } + else + { + /* Eviction area includes zero position entry, at least one entry is not + evicted. + The most complex case: some free space is at the bottom of the + buffer and some free space can be at the edge of the buffer. + The code should choose where to insert a new entry: at the bottom or + at the edge. */ + /* Processing: + + if bottom area is large enough insert at the bottom (no need to move + strings (typically large), only entries info data may need to be + moved (fast as it is typically smaller and is always aligned), + + otherwise remove evicted info data with low numbers, move strings + in the buffer (if needed) and add the new entry at the edge. */ + /** The first entry to keep */ + dtbl_idx_ft first_entry_keep = dtbl_get_pos_next (dyn, + eviction_end); + /** The last entry to keep */ + dtbl_idx_ft last_entry_keep = dtbl_get_pos_prev (dyn, + eviction_start); + dtbl_idx_ft num_to_keep = + ((dtbl_idx_ft) (last_entry_keep - first_entry_keep) + 1u); + /** The available space at the bottom of the strings buffer after + eviction of the entries */ + dtbl_size_ft new_bottom_gap = dtbl_pos_strs_start (dyn, + first_entry_keep); + + mhd_assert (dtbl_get_pos_edge (dyn) != eviction_end); + mhd_assert (last_entry_keep >= first_entry_keep); + mhd_assert (0u != num_to_keep); + mhd_assert (dtbl_get_num_entries (dyn) > num_to_keep); + mhd_assert (num_to_keep + num_to_evict == dtbl_get_num_entries (dyn)); + + if (new_bottom_gap >= entry_strs_size) + { + /* Enough space at the bottom to put the new entry strings */ + /* No need to check the space for the entries information data as + new entry replaces evicted zero position entry. */ + struct mhd_HpackDTblEntryInfo *replace_entry_ptr = + dtbl_zero_entry_info (dyn); + struct mhd_HpackDTblEntryInfo new_entry; + + /* Keep data correct and asserts quite */ + replace_entry_ptr->name_len = 0u; + replace_entry_ptr->val_len = 0u; + /* Move entries information data if needed, + the zero position entry information will be overwritten with + a new data. */ + dtbl_move_infos_pos (dyn, + first_entry_keep, + last_entry_keep, + 1u); + /* Keep the standard overhead of the entry being replaced */ + dyn->cur_size -= (dtbl_size_t) (evicted_size - mhd_dtbl_entry_overhead); + dyn->num_entries = (dtbl_idx_t) num_to_keep + 1u; /* Plus replaced zero position */ + + new_entry.name_len = (dtbl_size_t) name_len; + new_entry.val_len = (dtbl_size_t) val_len; + new_entry.offset = 0u; + + mhd_assert (dtbl_entr_strs_end_min (&new_entry) <= \ + dtbl_pos_strs_start (dyn, 1u)); + + dtbl_new_entry_copy_entr_strs (dyn, + name, + val, + &new_entry); + *replace_entry_ptr = new_entry; + /* Keep the standard overhead of the entry being replaced */ + dyn->cur_size += (dtbl_size_t) entry_strs_size; + dyn->newest_pos = 0u; + + dtbl_zeroout_strs_slack_pos (dyn, + dtbl_get_pos_newest (dyn)); + + return; + } + else + { + /* Not enough space at zero position in the buffer */ + /* The new entry will be added at the edge of the buffer after + eviction */ + /** The available space at the top of the strings buffer after moving + entries information data */ + const dtbl_size_ft new_top_gap = + dtbl_pos_as_edge_get_gap (dyn, + last_entry_keep) /* The gap after the last kept entry */ + + (first_entry_keep * mhd_DTBL_ENTRY_INFO_SIZE); /* 'first_entry_keep' will be evicted at zero position */ + + mhd_assert (1u <= first_entry_keep); + mhd_assert (new_top_gap + new_bottom_gap >= \ + entry_strs_size + mhd_DTBL_ENTRY_INFO_SIZE); + mhd_assert ((new_top_gap + new_bottom_gap >= \ + mhd_dtbl_entry_slack + entry_strs_size + + mhd_dtbl_entry_slack + mhd_DTBL_ENTRY_INFO_SIZE) && \ + "This is not required for the insertion of the entry " \ + "but this is guaranteed by the checking the overall size " \ + "of the buffer before the insertion, so this is a check " \ + "for the overall handling logic."); + + /* Move entries information data first to free some space */ + /* No slot kept in evicted entries as the new entry will be added + at the edge */ + dtbl_move_infos_pos (dyn, + first_entry_keep, + last_entry_keep, + 0u); + /* Keep the table internal data correct */ + dyn->num_entries = (dtbl_idx_t) num_to_keep; + dyn->newest_pos = (dtbl_idx_t) (num_to_keep - 1u); + dyn->cur_size -= (dtbl_size_t) evicted_size; + + mhd_assert (new_top_gap == dtbl_edge_gap (dyn)); + + if (new_top_gap < (entry_strs_size + mhd_DTBL_ENTRY_INFO_SIZE)) + { + /* Not enough space on the top of the buffer (checked earlier), + not enough space at the bottom of the buffer. + The strings in the buffer need to be moved. + Eliminate all space at the bottom. */ + const dtbl_size_ft shift_size = new_bottom_gap; + mhd_assert (0u != new_bottom_gap); + mhd_assert (new_bottom_gap == dtbl_bottom_gap (dyn)); + + dtbl_move_strs_down (dyn, + 0u, + shift_size); + mhd_assert (0u == dtbl_bottom_gap (dyn)); + mhd_assert (new_top_gap + shift_size == dtbl_edge_gap (dyn)); + mhd_assert (dtbl_edge_gap (dyn) >= \ + mhd_dtbl_entry_slack \ + + dtbl_new_entry_strs_size_formal (entry_strs_size) && \ + "All strings have been compacted, the free space must " \ + "be enough for the previous entry slack and for " \ + "a complete new entry, including slack and info data."); + } + + /* The entries have been evicted. + The edge of the buffer (top of the strings buffer) has enough space + for the new strings and the new entry info */ + dtbl_add_new_entry_at_new_edge (dyn, + name_len, + name, + val_len, + val); + + return; + } + } +} + + +/** + * Evict entries to reach the specified final formal table size. + * + * The function evicts the oldest entries until the formal used size is less + * than or equal to @a final_formal_size. + * + * The table must not be empty. + * Behaviour is undefined if @a final_formal_size is not less than the current + * formal used size. + * @param dyn the pointer to the dynamic table structure + * @param max_used_final the target formal size of data in the table + */ +static void +dtbl_evict_to_size (struct mhd_HpackDTblContext *restrict dyn, + dtbl_size_ft max_used_final) +{ + const dtbl_size_ft needed_evict_min = + dtbl_get_used_formal (dyn) - max_used_final; + /** The starting eviction position */ + const dtbl_idx_ft eviction_start = + dtbl_get_pos_oldest (dyn); + /** The final (inclusive) eviction entry */ + dtbl_idx_ft eviction_end; + + dtbl_size_ft evicted_size; + /** The total number of entries to evict */ + dtbl_idx_ft num_to_evict; + + mhd_assert (dtbl_get_used_formal (dyn) > max_used_final); + mhd_assert (0u != dyn->cur_size); + mhd_assert (! dtbl_is_empty (dyn)); + mhd_assert (0u != needed_evict_min); + mhd_assert (needed_evict_min <= dyn->cur_size); + + eviction_end = eviction_start; + evicted_size = dtbl_pos_size_formal (dyn, + eviction_end); + + while (needed_evict_min > evicted_size) + { + eviction_end = dtbl_get_pos_next (dyn, + eviction_end); + + mhd_assert (eviction_start != eviction_end); + + evicted_size += dtbl_pos_size_formal (dyn, + eviction_end); + } + + mhd_assert (needed_evict_min <= evicted_size); + num_to_evict = + (dtbl_get_num_entries (dyn) + eviction_end + - eviction_start) % dtbl_get_num_entries (dyn) + 1u; + mhd_assert (0u != num_to_evict); + mhd_assert (dtbl_get_num_entries (dyn) >= num_to_evict); + + if (mhd_COND_ALMOST_NEVER (dtbl_get_num_entries (dyn) == num_to_evict)) + { + /* Simplest situation: evicted all existing entries completely */ + dtbl_reset (dyn); + return; + } + else if (dtbl_get_pos_edge (dyn) == eviction_end) + { + /* Eviction area ends at the edge, at least one entry is not evicted. */ + mhd_assert (eviction_end >= eviction_start); + mhd_assert (dtbl_get_pos_newest (dyn) == (eviction_start - 1u)); + + dyn->cur_size -= (dtbl_size_t) evicted_size; + dyn->num_entries = (dtbl_idx_t) eviction_start; + + return; + } + else if ((0u != eviction_start) && + (eviction_end >= eviction_start)) + { + /* Entries are evicted in-between of other entries, at least two entries + are not evicted (at the start and at the edge). */ + /** The last entry to keep before the evicted entries */ + dtbl_idx_ft last_entry_keep = dtbl_get_pos_prev (dyn, + eviction_start); + /** The first entry to keep after the evicted entries */ + dtbl_idx_ft first_entry_keep = dtbl_get_pos_next (dyn, + eviction_end); + + mhd_assert (first_entry_keep > last_entry_keep); + mhd_assert (dtbl_get_num_entries (dyn) - num_to_evict >= 2u); + mhd_assert (dtbl_get_pos_edge (dyn) >= first_entry_keep); + + /* Move not evicted entries at the edge */ + dtbl_move_infos_pos (dyn, + first_entry_keep, + dtbl_get_pos_edge (dyn), + eviction_start); + dyn->cur_size -= (dtbl_size_t) evicted_size; + dyn->num_entries -= (dtbl_idx_t) num_to_evict; + mhd_assert (dtbl_get_pos_edge (dyn) >= dtbl_get_pos_newest (dyn)); + + return; + } + else + { + /* Eviction area includes zero position entry, at least one entry is not + evicted. */ + /** The first entry to keep */ + dtbl_idx_ft first_entry_keep = dtbl_get_pos_next (dyn, + eviction_end); + /** The last entry to keep */ + dtbl_idx_ft last_entry_keep = dtbl_get_pos_prev (dyn, + eviction_start); + dtbl_idx_ft num_to_keep = + ((dtbl_idx_ft) (last_entry_keep - first_entry_keep) + 1u); + + mhd_assert (dtbl_get_pos_edge (dyn) != eviction_end); + mhd_assert (0u != num_to_keep); + mhd_assert (dtbl_get_num_entries (dyn) > num_to_keep); + mhd_assert (num_to_keep + num_to_evict == dtbl_get_num_entries (dyn)); + + dtbl_move_infos_pos (dyn, + first_entry_keep, + last_entry_keep, + 0u); + dyn->cur_size -= (dtbl_size_t) evicted_size; + dyn->num_entries = (dtbl_idx_t) num_to_keep; + dyn->newest_pos = dtbl_get_pos_edge (dyn); + + return; + } +} + + +/** + * Adapt the in-memory layout to a new allocation and/or formal size. + * + * The function updates @a dyn to match @a new_alloc_size and + * @a new_formal_size, moving entries information data as needed. + * + * The @a new_formal_size must be larger than or equal to the current formal + * size of the entries in the table. + * The table must not be empty. + * @param dyn the pointer to the dynamic table structure + * @param new_alloc_size the new size of the shared buffer allocation + * @param new_formal_size the new formal HPACK table size limit + */ +static void +dtbl_perform_resize (struct mhd_HpackDTblContext *restrict dyn, + const dtbl_size_ft new_alloc_size, + const dtbl_size_ft new_formal_size) +{ + /* Obtain the data from the old table state */ + const struct mhd_HpackDTblEntryInfo *const infos_old_ptr = + dtbl_edge_entry_infoc (dyn); + struct mhd_HpackDTblEntryInfo *infos_new_ptr; + const dtbl_size_ft entries_total_size = + dtbl_get_num_entries (dyn) * mhd_DTBL_ENTRY_INFO_SIZE; + + mhd_assert (! dtbl_is_empty (dyn)); + mhd_assert (mhd_DTBL_VALUE_FITS (new_alloc_size)); + mhd_assert (mhd_DTBL_VALUE_FITS (new_formal_size)); + mhd_assert (new_formal_size <= mhd_DTBL_MAX_SIZE); + mhd_assert (new_formal_size < new_alloc_size); + mhd_assert (dtbl_get_used_formal (dyn) <= new_formal_size); + + if (dyn->buf_alloc_size > new_alloc_size) + { + /* Shrinking the buffer */ + mhd_assert (dtbl_get_size_max_formal (dyn) > new_formal_size); + mhd_assert (((dyn->buf_alloc_size - new_alloc_size) \ + % mhd_ALIGNOF (struct mhd_HpackDTblEntryInfo)) == 0); + + if (dtbl_edge_gap (dyn) < (dyn->buf_alloc_size - new_alloc_size)) + dtbl_compact_strs (dyn); + + mhd_assert (dtbl_edge_gap (dyn) >= (dyn->buf_alloc_size - new_alloc_size)); + + } + else if (mhd_COND_ALMOST_NEVER (new_alloc_size == dyn->buf_alloc_size)) + { + dyn->size_limit = (dtbl_size_t) new_formal_size; + return; /* Just update the formal size */ + } + else + { + /* Growing the buffer */ + mhd_assert (dtbl_get_size_max_formal (dyn) < new_formal_size); + mhd_assert (((new_alloc_size - dyn->buf_alloc_size) \ + % mhd_ALIGNOF (struct mhd_HpackDTblEntryInfo)) == 0); + } + + /* Set the new table size */ + dyn->size_limit = (dtbl_size_t) new_formal_size; + dyn->buf_alloc_size = (dtbl_size_t) new_alloc_size; + + /* Get the data location based on the new table size */ + infos_new_ptr = dtbl_edge_entry_info (dyn); + memmove (infos_new_ptr, + infos_old_ptr, + (size_t) entries_total_size); +} + + +/** + * Adapt the in-memory layout to a new allocation and/or formal size. + * + * The function updates @a dyn to match @a new_alloc_size and + * @a new_formal_size, moving entries information data as needed. + * + * The @a new_formal_size must be larger than or equal to the current formal + * size of the entries in the table. + * @param dyn the pointer to the dynamic table structure + * @param new_alloc_size the new size of the shared buffer allocation + * @param new_formal_size the new formal HPACK table size limit + */ +static void +dtbl_adapt_to_new_size (struct mhd_HpackDTblContext *restrict dyn, + const dtbl_size_ft new_alloc_size, + const dtbl_size_ft new_formal_size) +{ + mhd_assert (mhd_DTBL_VALUE_FITS (new_alloc_size)); + mhd_assert (mhd_DTBL_VALUE_FITS (new_formal_size)); + mhd_assert (new_formal_size <= mhd_DTBL_MAX_SIZE); + mhd_assert (new_formal_size < new_alloc_size); + + if (! dtbl_is_empty (dyn)) + { + dtbl_perform_resize (dyn, + new_alloc_size, + new_formal_size); + return; /* Internal structure has been fully updated */ + } + + /* Just set the new table size */ + dyn->size_limit = (dtbl_size_t) new_formal_size; + dyn->buf_alloc_size = (dtbl_size_t) new_alloc_size; + +} + + +/* ** Allocation helpers ** */ + +/** + * Calculate the buffer allocation size from the requested formal table size. + * + * The returned size includes additional slack to reduce the need for frequent + * compaction and is rounded up to alignment suitable for entry information + * data. The size accounts for the alignment difference between the context + * structure and the entry information data. + * + * @param formal_size the requested formal HPACK table size + * @return the allocation size for the strings/infos shared buffer + */ +mhd_static_inline dtbl_size_t +dtbl_calc_alloc_size (dtbl_size_ft formal_size) +{ + dtbl_size_ft dyn_table_alloc_size; + + mhd_assert (mhd_DTBL_VALUE_FITS (formal_size)); + + dyn_table_alloc_size = formal_size; + /* Add some slack to lower the need for the buffer compaction */ + dyn_table_alloc_size += formal_size / 64; + dyn_table_alloc_size += 2 * mhd_DTBL_ENTRY_INFO_SIZE; + /* Round up to alignment of the entry info data, which is placed at the + end of the buffer. */ + dyn_table_alloc_size = + ((dyn_table_alloc_size + mhd_ALIGNOF (struct mhd_HpackDTblEntryInfo) - 1u) + / mhd_ALIGNOF (struct mhd_HpackDTblEntryInfo)) + * mhd_ALIGNOF (struct mhd_HpackDTblEntryInfo); + /* Adjust the size of the allocation in case the alignment of + mhd_HpackDTblEntryInfo is stricter than that of mhd_HpackDTblContext */ + dyn_table_alloc_size += + (mhd_ALIGNOF (struct mhd_HpackDTblEntryInfo) + - (sizeof(struct mhd_HpackDTblContext) + % mhd_ALIGNOF (struct mhd_HpackDTblEntryInfo))) + % mhd_ALIGNOF (struct mhd_HpackDTblEntryInfo); + + mhd_assert (mhd_DTBL_VALUE_FITS (dyn_table_alloc_size)); + + return (dtbl_size_t) dyn_table_alloc_size; +} + + +/* ** Entries finders ** */ + +/** + * Find an entry in the dynamic table that exactly matches the given + * name and value. + * + * The @a name and @a val do not need to be zero-terminated. + * The table must not be empty. + * + * @param dyn const pointer to the dynamic table structure + * @param name_len length of @a name in bytes + * @param name pointer to the header field name + * @param val_len length of @a val in bytes + * @param val pointer to the header field value + * @return the HPACK index (> #mhd_HPACK_STBL_LAST_IDX) of the matching entry, + * or 0 if not found + */ +static MHD_FN_PAR_IN_SIZE_ (3,2) MHD_FN_PAR_IN_SIZE_ (5,4) dtbl_idx_t +dtbl_find_entry (const struct mhd_HpackDTblContext *restrict dyn, + dtbl_size_ft name_len, + const char *restrict name, + dtbl_size_ft val_len, + const char *restrict val) +{ + /* The table must not be empty */ + const struct mhd_HpackDTblEntryInfo *entries = + dtbl_get_infos_as_arrayc (dyn); + dtbl_idx_ft i; + for (i = 0u; i < dtbl_get_num_entries (dyn); ++i) + { + const struct mhd_HpackDTblEntryInfo *const entry = entries + i; + + if (name_len != entry->name_len) + continue; + if (val_len != entry->val_len) + continue; + if (((0u == name_len) || + (0 == memcmp (name, + dtbl_entr_strs_ptr_namec (dyn, + entry), + name_len))) + && + ((0u == val_len) || + (0 == memcmp (val, + dtbl_entr_strs_ptr_valuec (dyn, + entry), + val_len)))) + { /* Found the entry */ + return dtbl_get_hpack_idx_from_pos (dyn, + dtbl_get_pos_edge (dyn) - i); + } + } + return 0u; /* Not found */ +} + + +/** + * Find an entry in the dynamic table whose name exactly matches @a name. + * + * The @a name does not need to be zero-terminated. + * The table must not be empty. + * + * @param dyn const pointer to the dynamic table structure + * @param name_len length of @a name in bytes + * @param name pointer to the header field name + * @return the HPACK index (> #mhd_HPACK_STBL_LAST_IDX) of the matching entry, + * or 0 if not found + */ +static MHD_FN_PAR_IN_SIZE_ (3,2) dtbl_idx_t +dtbl_find_name (const struct mhd_HpackDTblContext *restrict dyn, + dtbl_size_ft name_len, + const char *restrict name) +{ + /* The table must not be empty */ + const struct mhd_HpackDTblEntryInfo *entries = + dtbl_get_infos_as_arrayc (dyn); + dtbl_idx_ft i; + for (i = 0u; i < dtbl_get_num_entries (dyn); ++i) + { + const struct mhd_HpackDTblEntryInfo *const entry = entries + i; + + if (name_len != entry->name_len) + continue; + if ((0u == name_len) || + (0 == memcmp (name, + dtbl_entr_strs_ptr_namec (dyn, + entry), + name_len))) + { /* Found the entry */ + return dtbl_get_hpack_idx_from_pos (dyn, + dtbl_get_pos_edge (dyn) - i); + } + } + return 0u; /* Not found */ +} + + +/* **** ________________ End of dynamic table helpers _________________ **** */ + +/* ****** ------------------- Dynamic table API --------------------- ****** */ + +/* + * The API is designed to be used by one thread only. + * If any thread is modifying the data in the dynamic table, then any access + * in any other thread at the same time is not safe! + */ + + +/** + * Create a dynamic HPACK table context with the specified formal size limit. + * + * The allocation includes the context and a shared buffer. The table is + * initialised to an empty state. The function allocates slightly more than + * @a dyn_table_size due to the internal overhead. + * + * @param dyn_table_size the requested formal HPACK table size limit + * @return pointer to the newly created context on success, + * NULL on allocation failure + */ +static mhd_FN_RET_UNALIASED +struct mhd_HpackDTblContext * +mhd_dtbl_create (size_t dyn_table_size) +{ + struct mhd_HpackDTblContext*dyn; + dtbl_size_ft alloc_size; + mhd_assert (mhd_DTBL_MAX_SIZE >= dyn_table_size); + mhd_assert (mhd_DTBL_VALUE_FITS (dyn_table_size)); + + alloc_size = dtbl_calc_alloc_size ((dtbl_size_ft) dyn_table_size); + + dyn = (struct mhd_HpackDTblContext*) malloc (sizeof(*dyn) + + (size_t) alloc_size); + if (NULL == dyn) + return NULL; /* Failure exit point */ + + dyn->buf_alloc_size = (dtbl_size_t) alloc_size; + dyn->size_limit = (dtbl_size_t) dyn_table_size; + dtbl_reset (dyn); + + dtbl_check_internals (dyn); + + return dyn; +} + + +/** + * Destroy a dynamic HPACK table context and free all associated memory. + * + * @param dyn the pointer to the dynamic table structure to destroy + */ +mhd_static_inline MHD_FN_PAR_NONNULL_ALL_ void +mhd_dtbl_destroy (struct mhd_HpackDTblContext *dyn) +{ + dtbl_check_internals (dyn); + /* Everything is in a single memory allocation, just free it */ + free (dyn); +} + + +/** + * Get the current formal maximum table size (the HPACK size limit). + * @param dyn the pointer to the dynamic table structure + * @return the formal maximum size of the table + */ +static MHD_FN_PURE_ size_t +mhd_dtbl_get_table_max_size (const struct mhd_HpackDTblContext *dyn) +{ + return (size_t) dtbl_get_size_max_formal (dyn); +} + + +/** + * Get the current amount of formal used space in the table. + * @param dyn the pointer to the dynamic table structure + * @return the formal used space in the table + */ +static MHD_FN_PURE_ size_t +mhd_dtbl_get_table_used (const struct mhd_HpackDTblContext *dyn) +{ + return (size_t) dtbl_get_used_formal (dyn); +} + + +/** + * Get the current number of entries in the table. + * @param dyn the pointer to the dynamic table structure + * @return the number of entries in the table + */ +static MHD_FN_PURE_ size_t +mhd_dtbl_get_num_entries (const struct mhd_HpackDTblContext *dyn) +{ + return (size_t) dtbl_get_num_entries (dyn); +} + + +/** + * Evict the oldest dynamic-table entries until the formal (HPACK) used size + * becomes less than or equal to the requested value. + * + * If the table is already within the limit, nothing is changed. + * + * The function does not change the formal maximum table size and does not + * allocate memory. + * + * @param dyn the pointer to the dynamic table structure + * @param max_used_formal the target upper bound (in bytes) for the formal + * used size after eviction + */ +static void +mhd_dtbl_evict_to_size (struct mhd_HpackDTblContext *dyn, + size_t max_used_formal) +{ + if (dtbl_is_empty (dyn)) + return; + else if (0u == max_used_formal) + dtbl_reset (dyn); + else if (dtbl_get_used_formal (dyn) <= max_used_formal) + return; + else + dtbl_evict_to_size (dyn, + (dtbl_size_t) max_used_formal); + + dtbl_check_internals (dyn); +} + + +/** + * Resize the dynamic HPACK table. + * + * On allocation failure when growing, the original table is unchanged. + * The shrinking of the table never fails. + * + * @param dyn_pp the pointer to the variable holding the pointer dynamic + * table structure, the value of the variable could be updated + * @param dyn_table_size the new formal HPACK table size limit + * @return 'true' on success (the variable pointer by @a dyn_pp could be + * updated), + * 'false' if growing failed (the dynamic table remains valid, but + * not resized) + */ +static bool +mhd_dtbl_resize (struct mhd_HpackDTblContext** const dyn_pp, + size_t dyn_table_size) +{ + const dtbl_size_ft old_official_size = dtbl_get_size_max_formal (*dyn_pp); + dtbl_size_ft new_alloc_size; + struct mhd_HpackDTblContext *new_dyn; + mhd_assert (mhd_DTBL_MAX_SIZE >= dyn_table_size); + mhd_assert (mhd_DTBL_VALUE_FITS (dyn_table_size)); + + if (old_official_size == dyn_table_size) + return true; /* Do nothing */ + + new_alloc_size = dtbl_calc_alloc_size ((dtbl_size_ft) dyn_table_size); + + if (old_official_size < dyn_table_size) + { + /* Growing table size */ + /* No need to evict */ + new_dyn = (struct mhd_HpackDTblContext*) + realloc (*dyn_pp, + sizeof(**dyn_pp) + (size_t) new_alloc_size); + if (NULL == new_dyn) + return false; /* No table resize */ + *dyn_pp = new_dyn; + + /* Adapt the table data to the larger size */ + dtbl_adapt_to_new_size (new_dyn, + new_alloc_size, + (dtbl_size_ft) dyn_table_size); + } + else + { + /* Shrinking table size */ + mhd_dtbl_evict_to_size (*dyn_pp, + (dtbl_size_ft) dyn_table_size); + + /* Adapt table data before resizing */ + dtbl_adapt_to_new_size (*dyn_pp, + new_alloc_size, + (dtbl_size_ft) dyn_table_size); + + /* Try to reduce the allocated memory */ + new_dyn = (struct mhd_HpackDTblContext*) + realloc (*dyn_pp, + sizeof(**dyn_pp) + (size_t) new_alloc_size); + + /* If realloc() failed, just use the previous allocation. + The table will use the new (reduced) size anyway, while the allocation + will be kept larger than needed. */ + if (mhd_COND_VIRTUALLY_ALWAYS (NULL != new_dyn)) + *dyn_pp = new_dyn; + } + + dtbl_check_internals (new_dyn); + + return true; +} + + +/** + * Check whether the new entry may fit the dynamic table + * @param dyn the pointer to the dynamic table structure + * @param name_len the length of the name of the new entry + * @param val_len the length of the value of the new entry + * @return 'true' if the new entry may be stored in the @a dyn dynamic table, + * 'false' if the new entry formal size is larger than @a dyn may hold. + */ +static bool +mhd_dtbl_check_entry_fit (struct mhd_HpackDTblContext *restrict dyn, + size_t name_len, + size_t val_len) +{ + size_t entry_size; + /* Carefully check the values, taking into account possible type overflow + when performing calculations */ + entry_size = name_len + val_len; + if (mhd_COND_HARDLY_EVER (entry_size < val_len)) + return false; + entry_size += mhd_dtbl_entry_overhead; + if (mhd_COND_HARDLY_EVER (entry_size < mhd_dtbl_entry_overhead)) + return false; + + return (dtbl_get_size_max_formal (dyn) >= entry_size); +} + + +/** + * Add a new entry to the dynamic table. + * + * If the entry cannot fit the table size limit, the table is reset to the + * empty state and the entry is discarded. + * If there is enough formal free space, the entry is inserted. Otherwise, the + * oldest entries are evicted and the new entry is inserted. + * + * The function copies the provided strings into the table's buffer. + * @param dyn the pointer to the dynamic table structure + * @param name_len the length of the @a name, must fit #mhd_HPACK_DTBL_BITS bits + * @param name the name of the header, does NOT need to be zero-terminated + * @param val_len the length of the @a val, must fit #mhd_HPACK_DTBL_BITS bits + * @param val the value of the header, does NOT need to be zero terminated + */ +static MHD_FN_PAR_IN_SIZE_ (3,2) MHD_FN_PAR_IN_SIZE_ (5,4) void +mhd_dtbl_new_entry (struct mhd_HpackDTblContext *restrict dyn, + size_t name_len, + const char *restrict name, + size_t val_len, + const char *restrict val) +{ + if (mhd_COND_ALMOST_NEVER (! mhd_dtbl_check_entry_fit (dyn, + name_len, + val_len))) + { + /* The entry cannot fit the table. + * Reset table to empty state (need to evict all entries). */ + dtbl_reset (dyn); + + } + else if (dtbl_get_free_formal (dyn) + >= dtbl_new_entry_size_formal ((dtbl_size_ft) name_len, + (dtbl_size_ft) val_len)) + { + /* Enough space. Insert new entry. */ + mhd_assert (mhd_DTBL_VALUE_FITS (name_len)); + mhd_assert (mhd_DTBL_VALUE_FITS (val_len)); + dtbl_extend_with_entry (dyn, + (dtbl_size_ft) name_len, + name, + (dtbl_size_ft) val_len, + val); + } + else + { + /* Not enough free space, but the new entry fit the table after eviction. + * Evict some entries and add a new one. */ + mhd_assert (mhd_DTBL_VALUE_FITS (name_len)); + mhd_assert (mhd_DTBL_VALUE_FITS (val_len)); + dtbl_evict_add_entry (dyn, + (dtbl_size_ft) name_len, + name, + (dtbl_size_ft) val_len, + val); + } + + dtbl_check_internals (dyn); +} + + +/** + * Get a dynamic-table entry by HPACK index. + * + * The HPACK index must refer to the dynamic table (greater than the number + * of entries in the static table). On success, the function returns pointers + * to the non-zero-terminated name and value buffers inside the table and + * their lengths. + * + * The strings returned (on success) in @a name_out and @a value_out must be + * used/processed before any other actions with the dynamic table. Any change + * in the dynamic table may invalidate pointers in @a name_out and + * @a value_out. + * + * Behaviour is undefined if @a idx is less or equal to #mhd_HPACK_STBL_LAST_IDX + * + * @param dyn const pointer to the dynamic table structure + * @param idx the HPACK index of the requested entry, must be strictly larger + * than #mhd_HPACK_STBL_LAST_IDX + * @param[out] name_out the output buffer for the header name, + * the result is NOT zero-terminated + * @param[out] value_out the output buffer for the header value, + * the result is NOT zero-terminated + * @return 'true' if the entry exists and output buffers are set, + * 'false' otherwise + */ +static MHD_FN_PAR_OUT_ (3) MHD_FN_PAR_OUT_ (4) bool +mhd_dtbl_get_entry (const struct mhd_HpackDTblContext *restrict dyn, + dtbl_idx_ft idx, + struct mhd_BufferConst *restrict name_out, + struct mhd_BufferConst *restrict value_out) +{ + const struct mhd_HpackDTblEntryInfo *entry; + mhd_assert (mhd_HPACK_STBL_LAST_IDX < idx); + if (dtbl_is_empty (dyn)) + return false; + if (dtbl_get_pos_edge (dyn) < (idx - mhd_dtbl_hpack_idx_offset)) + return false; + + entry = dtbl_pos_entry_infoc (dyn, + dtbl_get_pos_from_hpack_idx (dyn, + idx)); + name_out->size = (size_t) entry->name_len; + name_out->data = dtbl_entr_strs_ptr_startc (dyn, + entry); + value_out->size = (size_t) entry->val_len; + value_out->data = name_out->data + name_out->size; + + return true; +} + + +/** + * Look up a dynamic-table entry equal to the provided name and value. + * + * If the table is empty or no exact match is found, 0 is returned. + * The input strings do not need to be zero-terminated. + * + * @param dyn const pointer to the dynamic table structure + * @param name_len length of @a name in bytes + * @param name pointer to the header field name, + * does NOT need to be zero-terminated + * @param val_len length of @a val in bytes + * @param val pointer to the header field value, + * does NOT need to be zero-terminated + * @return the HPACK index (> #mhd_HPACK_STBL_LAST_IDX) of the matching entry, + * or 0 if not found + */ +static MHD_FN_PAR_IN_SIZE_ (3,2) MHD_FN_PAR_IN_SIZE_ (5,4) dtbl_idx_t +mhd_dtbl_find_entry (const struct mhd_HpackDTblContext *restrict dyn, + size_t name_len, + const char *restrict name, + size_t val_len, + const char *restrict val) +{ + if (dtbl_is_empty (dyn)) + return 0u; + + if (mhd_COND_HARDLY_EVER (! mhd_DTBL_VALUE_FITS (name_len))) + return 0u; + if (mhd_COND_HARDLY_EVER (! mhd_DTBL_VALUE_FITS (val_len))) + return 0u; + + return dtbl_find_entry (dyn, + (dtbl_size_ft) name_len, + name, + (dtbl_size_ft) val_len, + val); +} + + +/** + * Look up a dynamic-table entry whose name equals @a name. + * + * If the table is empty or no match is found, 0 is returned. + * The input string does not need to be zero-terminated. + * + * @param dyn const pointer to the dynamic table structure + * @param name_len length of @a name in bytes + * @param name pointer to the header field name, + * does NOT need to be zero-terminated + * @return the HPACK index (> #mhd_HPACK_STBL_LAST_IDX) of the matching entry, + * or 0 if not found + */ +static MHD_FN_PAR_IN_SIZE_ (3,2) dtbl_idx_t +mhd_dtbl_find_name (const struct mhd_HpackDTblContext *restrict dyn, + size_t name_len, + const char *restrict name) +{ + if (dtbl_is_empty (dyn)) + return 0u; + + if (mhd_COND_HARDLY_EVER (! mhd_DTBL_VALUE_FITS (name_len))) + return 0u; + + return dtbl_find_name (dyn, + (dtbl_size_ft) name_len, + name); +} + + +/* ****** ----------------- Static table handling ----------------- ****** */ +/* ======================================================================== + * + * The static table data should be accessed only by mhd_* functions. + * + * All functions prefixed with stbl_* are internal helpers and should not + * be used directly. + * + * ======================================================================== + */ + +/** + * HPACK static table element + */ +struct mhd_HpackStaticEntry +{ + /** + * The name of the header field + */ + const struct MHD_String name; + /** + * The value of the header field. + */ + const struct MHD_String value; +}; + +/* The next variable cannot be declared as 'mhd_constexpr' as it contains + pointers to the strings */ +/** + * HPACK static table. + * Add 1 to the array index to obtain the HPACK index. + * + * This table is extracted (and transformed) from RFC 7541. + * See https://datatracker.ietf.org/doc/html/rfc7541#appendix-A + */ +static const struct mhd_HpackStaticEntry + mhd_hpack_static[mhd_HPACK_STBL_ENTRIES] = { + /* 1 */ { mhd_MSTR_INIT (":authority"), mhd_MSTR_INIT ("") }, + /* 2 */ { mhd_MSTR_INIT (":method"), mhd_MSTR_INIT ("GET") }, + /* 3 */ { mhd_MSTR_INIT (":method"), mhd_MSTR_INIT ("POST") }, + /* 4 */ { mhd_MSTR_INIT (":path"), mhd_MSTR_INIT ("/") }, + /* 5 */ { mhd_MSTR_INIT (":path"), mhd_MSTR_INIT ("/index.html") }, + /* 6 */ { mhd_MSTR_INIT (":scheme"), mhd_MSTR_INIT ("http") }, + /* 7 */ { mhd_MSTR_INIT (":scheme"), mhd_MSTR_INIT ("https") }, + /* 8 */ { mhd_MSTR_INIT (":status"), mhd_MSTR_INIT ("200") }, + /* 9 */ { mhd_MSTR_INIT (":status"), mhd_MSTR_INIT ("204") }, + /* 10 */ { mhd_MSTR_INIT (":status"), mhd_MSTR_INIT ("206") }, + /* 11 */ { mhd_MSTR_INIT (":status"), mhd_MSTR_INIT ("304") }, + /* 12 */ { mhd_MSTR_INIT (":status"), mhd_MSTR_INIT ("400") }, + /* 13 */ { mhd_MSTR_INIT (":status"), mhd_MSTR_INIT ("404") }, + /* 14 */ { mhd_MSTR_INIT (":status"), mhd_MSTR_INIT ("500") }, + /* 15 */ { mhd_MSTR_INIT ("accept-charset"), mhd_MSTR_INIT ("") }, + /* 16 */ { mhd_MSTR_INIT ("accept-encoding"), + mhd_MSTR_INIT ("gzip, deflate") }, + /* 17 */ { mhd_MSTR_INIT ("accept-language"), mhd_MSTR_INIT ("") }, + /* 18 */ { mhd_MSTR_INIT ("accept-ranges"), mhd_MSTR_INIT ("") }, + /* 19 */ { mhd_MSTR_INIT ("accept"), mhd_MSTR_INIT ("") }, + /* 20 */ { mhd_MSTR_INIT ("access-control-allow-origin"), + mhd_MSTR_INIT ("") }, + /* 21 */ { mhd_MSTR_INIT ("age"), mhd_MSTR_INIT ("") }, + /* 22 */ { mhd_MSTR_INIT ("allow"), mhd_MSTR_INIT ("") }, + /* 23 */ { mhd_MSTR_INIT ("authorization"), mhd_MSTR_INIT ("") }, + /* 24 */ { mhd_MSTR_INIT ("cache-control"), mhd_MSTR_INIT ("") }, + /* 25 */ { mhd_MSTR_INIT ("content-disposition"), mhd_MSTR_INIT ("") }, + /* 26 */ { mhd_MSTR_INIT ("content-encoding"), mhd_MSTR_INIT ("") }, + /* 27 */ { mhd_MSTR_INIT ("content-language"), mhd_MSTR_INIT ("") }, + /* 28 */ { mhd_MSTR_INIT ("content-length"), mhd_MSTR_INIT ("") }, + /* 29 */ { mhd_MSTR_INIT ("content-location"), mhd_MSTR_INIT ("") }, + /* 30 */ { mhd_MSTR_INIT ("content-range"), mhd_MSTR_INIT ("") }, + /* 31 */ { mhd_MSTR_INIT ("content-type"), mhd_MSTR_INIT ("") }, + /* 32 */ { mhd_MSTR_INIT ("cookie"), mhd_MSTR_INIT ("") }, + /* 33 */ { mhd_MSTR_INIT ("date"), mhd_MSTR_INIT ("") }, + /* 34 */ { mhd_MSTR_INIT ("etag"), mhd_MSTR_INIT ("") }, + /* 35 */ { mhd_MSTR_INIT ("expect"), mhd_MSTR_INIT ("") }, + /* 36 */ { mhd_MSTR_INIT ("expires"), mhd_MSTR_INIT ("") }, + /* 37 */ { mhd_MSTR_INIT ("from"), mhd_MSTR_INIT ("") }, + /* 38 */ { mhd_MSTR_INIT ("host"), mhd_MSTR_INIT ("") }, + /* 39 */ { mhd_MSTR_INIT ("if-match"), mhd_MSTR_INIT ("") }, + /* 40 */ { mhd_MSTR_INIT ("if-modified-since"), mhd_MSTR_INIT ("") }, + /* 41 */ { mhd_MSTR_INIT ("if-none-match"), mhd_MSTR_INIT ("") }, + /* 42 */ { mhd_MSTR_INIT ("if-range"), mhd_MSTR_INIT ("") }, + /* 43 */ { mhd_MSTR_INIT ("if-unmodified-since"), mhd_MSTR_INIT ("") }, + /* 44 */ { mhd_MSTR_INIT ("last-modified"), mhd_MSTR_INIT ("") }, + /* 45 */ { mhd_MSTR_INIT ("link"), mhd_MSTR_INIT ("") }, + /* 46 */ { mhd_MSTR_INIT ("location"), mhd_MSTR_INIT ("") }, + /* 47 */ { mhd_MSTR_INIT ("max-forwards"), mhd_MSTR_INIT ("") }, + /* 48 */ { mhd_MSTR_INIT ("proxy-authenticate"), mhd_MSTR_INIT ("") }, + /* 49 */ { mhd_MSTR_INIT ("proxy-authorization"), mhd_MSTR_INIT ("") }, + /* 50 */ { mhd_MSTR_INIT ("range"), mhd_MSTR_INIT ("") }, + /* 51 */ { mhd_MSTR_INIT ("referer"), mhd_MSTR_INIT ("") }, + /* 52 */ { mhd_MSTR_INIT ("refresh"), mhd_MSTR_INIT ("") }, + /* 53 */ { mhd_MSTR_INIT ("retry-after"), mhd_MSTR_INIT ("") }, + /* 54 */ { mhd_MSTR_INIT ("server"), mhd_MSTR_INIT ("") }, + /* 55 */ { mhd_MSTR_INIT ("set-cookie"), mhd_MSTR_INIT ("") }, + /* 56 */ { mhd_MSTR_INIT ("strict-transport-security"), mhd_MSTR_INIT ("") }, + /* 57 */ { mhd_MSTR_INIT ("transfer-encoding"), mhd_MSTR_INIT ("") }, + /* 58 */ { mhd_MSTR_INIT ("user-agent"), mhd_MSTR_INIT ("") }, + /* 59 */ { mhd_MSTR_INIT ("vary"), mhd_MSTR_INIT ("") }, + /* 60 */ { mhd_MSTR_INIT ("via"), mhd_MSTR_INIT ("") }, + /* 61 */ { mhd_MSTR_INIT ("www-authenticate"), mhd_MSTR_INIT ("") } +}; + +/** + * The position of the first ":status" pseud-header field in the + * @a mhd_hpack_static table + */ +#define mhd_HPACK_STBL_PF_STATUS_START_POS (8u) + +/** + * Convert an HPACK index (matching the static table) to a 0-based position in + * the static table data. + * + * Behaviour is undefined if @a hpack_idx is 0 or greater than + * #mhd_HPACK_STBL_LAST_IDX. + * @param hpack_idx the HPACK index of the static-table entry + * (1 .. #mhd_HPACK_STBL_LAST_IDX) + * @return the 0-based position corresponding to @a hpack_idx + */ +MHD_FN_CONST_ mhd_static_inline dtbl_idx_t +stbl_get_pos_from_hpack_idx (dtbl_idx_ft hpack_idx) +{ + mhd_assert (0u != hpack_idx); + mhd_assert (mhd_HPACK_STBL_LAST_IDX >= hpack_idx); + return (dtbl_idx_t) (hpack_idx - 1u); +} + + +/** + * Convert a 0-based static table position to the HPACK index. + * + * The returned index is in the range 1 .. #mhd_HPACK_STBL_LAST_IDX. + * + * Behaviour is undefined if @a loc_pos is not a valid static-table position, + * i.e. if it is greater than or equal to #mhd_HPACK_STBL_ENTRIES. + * @param loc_pos the 0-based position in the static table + * @return the HPACK index corresponding to @a loc_pos + */ +MHD_FN_CONST_ mhd_static_inline dtbl_idx_t +stbl_get_hpack_idx_from_pos (dtbl_idx_ft loc_pos) +{ + mhd_assert (mhd_HPACK_STBL_LAST_IDX > loc_pos); + return (dtbl_idx_t) (loc_pos + 1u); +} + + +/** + * Get a pointer to the static table entry by its 0-based position. + * + * Behaviour is undefined if @a loc_pos is not a valid static-table position, + * i.e. if it is greater than or equal to #mhd_HPACK_STBL_ENTRIES. + * @param loc_pos the 0-based position in the static table + * @return const pointer to the static entry descriptor + */ +MHD_FN_CONST_ mhd_static_inline const struct mhd_HpackStaticEntry * +stbl_pos_entry_info (dtbl_idx_ft loc_pos) +{ + mhd_assert (sizeof(mhd_hpack_static) / sizeof(mhd_hpack_static[0]) \ + == mhd_HPACK_STBL_ENTRIES); + mhd_assert (mhd_HPACK_STBL_ENTRIES > loc_pos); + return mhd_hpack_static + loc_pos; +} + + +/** + * Get a pointer to the static table entry by its HPACK index. + * + * Behaviour is undefined if @a hpack_idx is 0 or greater than + * #mhd_HPACK_STBL_LAST_IDX. + * @param hpack_idx the HPACK index of the entry + * @return const pointer to the static entry descriptor + */ +MHD_FN_CONST_ mhd_static_inline const struct mhd_HpackStaticEntry * +stbl_idx_entry_info (dtbl_idx_ft hpack_idx) +{ + return stbl_pos_entry_info (stbl_get_pos_from_hpack_idx (hpack_idx)); +} + + +/* **** _____________ End of static table data helpers ______________ ****** */ + +/* ****------------------- Static table data API ---------------------****** */ +/** + * The position of the first real (non-pseudo) header in the + * @a mhd_hpack_static table + */ +#define mhd_HPACK_STBL_NORM_START_POS (14u) + +/** + * The position of the only real (non-pseudo) header with a non-empty value in + * the @a mhd_hpack_static table + */ +#define mhd_HPACK_STBL_NORM_WITH_VALUE_POS (15u) + +/** + * Get a static-table entry by HPACK index. + * + * The index @a idx must refer to the static table + * (i.e. 1 .. #mhd_HPACK_STBL_LAST_IDX). + * On return, @a name_out and @a value_out are set to point to the entry + * data and their lengths. + * + * Behaviour is undefined if @a idx is 0 or greater + * than #mhd_HPACK_STBL_LAST_IDX. + * @param idx the HPACK index within the static table + * @param[out] name_out output buffer for the header name + * @param[out] value_out output buffer for the header value + */ +static MHD_FN_PAR_OUT_ (2) MHD_FN_PAR_OUT_ (3) void +mhd_stbl_get_entry (dtbl_idx_ft idx, + struct mhd_BufferConst *restrict name_out, + struct mhd_BufferConst *restrict value_out) +{ + const struct mhd_HpackStaticEntry *const entry = stbl_idx_entry_info (idx); + + name_out->size = entry->name.len; + name_out->data = entry->name.cstr; + value_out->size = entry->value.len; + value_out->data = entry->value.cstr; +} + + +/** + * Find a static-table entry among "real" (non-pseudo) headers that exactly + * matches the given name and value. + * + * The header name must not start with ':'. + * The input strings do not need to be zero-terminated. + * + * @param name_len length of @a name in bytes, + * must not be zero + * @param name pointer to the header field name, + * does NOT need to be zero-terminated + * @param val_len length of @a val in bytes + * @param val pointer to the header field value, + * does NOT need to be zero-terminated + * @return the HPACK index (<= #mhd_HPACK_STBL_LAST_IDX) of the matching + * static entry, or 0 if not found + */ +static MHD_FN_PAR_IN_SIZE_ (2,1) MHD_FN_PAR_IN_SIZE_ (4,3) dtbl_idx_t +mhd_stbl_find_entry_real (size_t name_len, + const char *restrict name, + size_t val_len, + const char *restrict val) +{ +#ifndef MHD_UNIT_TESTING /* Do not abort on a wrong name when unit-testing */ + mhd_assert (0u != name_len); + mhd_assert (':' != name[0]); +#endif /* ! MHD_UNIT_TESTING */ +#ifndef MHD_FAVOR_SMALL_CODE + if (mhd_COND_ALMOST_ALWAYS (0u != val_len)) + { /* non-empty 'value' */ + /* Process the only normal (real header) entry that has non-empty value */ + mhd_constexpr dtbl_idx_ft i = mhd_HPACK_STBL_NORM_WITH_VALUE_POS; + do + { + const struct mhd_HpackStaticEntry *const entry = stbl_pos_entry_info (i); + + if (name_len != entry->name.len) + continue; + mhd_assert (0u != entry->name.len); + mhd_assert (0u != entry->value.len); + + if (0 == memcmp (name, + entry->name.cstr, + name_len)) + { /* 'name' matches */ + if (0 == memcmp (val, + entry->value.cstr, + val_len)) + { /* 'value' matches */ + /* Full match found, return the HPACK index */ + return stbl_get_hpack_idx_from_pos (i); + } + } + + + } while (0); + } + else + { /* (0u == val_len) */ + /* empty 'value' */ + dtbl_idx_ft i; + mhd_assert (0u == val_len); + for (i = mhd_HPACK_STBL_NORM_START_POS; i < mhd_HPACK_STBL_ENTRIES; ++i) + { + const struct mhd_HpackStaticEntry *const entry = stbl_pos_entry_info (i); + + if (mhd_HPACK_STBL_NORM_WITH_VALUE_POS == i) + continue; + + if (name_len != entry->name.len) + continue; + mhd_assert (0u != entry->name.len); + mhd_assert (0u == entry->value.len); + if (0 == memcmp (name, + entry->name.cstr, + name_len)) + { /* 'name' matches (and 'value' is empty) */ + /* Full match found, return the HPACK index */ + return stbl_get_hpack_idx_from_pos (i); + } + } + } +#else /* ! MHD_FAVOR_SMALL_CODE */ + if (1) + { + dtbl_idx_ft i; + for (i = mhd_HPACK_STBL_NORM_START_POS; i < mhd_HPACK_STBL_ENTRIES; ++i) + { + const struct mhd_HpackStaticEntry *const entry = stbl_pos_entry_info (i); + if (name_len != entry->name.len) + continue; + if (val_len != entry->value.len) + continue; + mhd_assert (0u != entry->name.len); + if (0 == memcmp (name, + entry->name.cstr, + name_len)) + { /* 'name' matches */ + if ((0u == val_len) || + (0 == memcmp (val, + entry->value.cstr, + val_len))) + { /* 'value' matches (empty or identical) */ + /* Full match found, return the HPACK index */ + return stbl_get_hpack_idx_from_pos (i); + } + } + } + } +#endif /* !MHD_FAVOR_SMALL_CODE */ + + return 0u; /* Not found */ +} + + +/** + * Find a static-table entry among "real" (non-pseudo) headers whose name + * exactly matches @a name. + * + * The header name must not start with ':'. + * The input string does not need to be zero-terminated. + * + * @param name_len length of @a name in bytes, + * must NOT be zero + * @param name pointer to the header field name, + * does NOT need to be zero-terminated + * @return the HPACK index (<= #mhd_HPACK_STBL_LAST_IDX) of the matching + * static entry, or 0 if not found + */ +static MHD_FN_PAR_IN_SIZE_ (2,1) dtbl_idx_t +mhd_stbl_find_name_real (size_t name_len, + const char *restrict name) +{ + dtbl_idx_ft i; +#ifndef MHD_UNIT_TESTING /* Do not abort on a wrong name when unit-testing */ + mhd_assert (0u != name_len); + mhd_assert (':' != name[0]); +#endif /* ! MHD_UNIT_TESTING */ + for (i = mhd_HPACK_STBL_NORM_START_POS; i < mhd_HPACK_STBL_ENTRIES; ++i) + { + const struct mhd_HpackStaticEntry *const entry = stbl_pos_entry_info (i); + + if (name_len != entry->name.len) + continue; + mhd_assert (0u != entry->name.len); + if (0 == memcmp (name, + entry->name.cstr, + name_len)) + { /* Found the entry, return the HPACK index */ + return stbl_get_hpack_idx_from_pos (i); + } + } + + return 0u; /* Not found */ +} + + +/* ****** -------------- HPACK header tables handling -------------- ****** */ +/* + * mhd_htbl_ functions are handling combination of HPACK static and dynamic + * tables. + * Functions need a pointer to a dynamic table instance. + * + * These functions are just convenient wrappers for some operations; they are + * not designed to cover all operations with static and dynamic tables. + * Some operations must be performed directly on static or dynamic tables. + */ +/** + * Get a header-table entry (static or dynamic) by HPACK index. + * + * On success, @a name_out and @a value_out are set to point to the entry + * data and their lengths. The returned buffers are not guaranteed to be + * zero-terminated and must not be relied upon as C strings. + * + * @param dyn const pointer to the dynamic table context + * @param idx the HPACK index (static or dynamic) + * @param[out] name_out output buffer for the header name + * @param[out] value_out output buffer for the header value + * @return 'true' if the entry exists and outputs are set, + * 'false' otherwise + */ +static MHD_FN_PAR_OUT_ (3) MHD_FN_PAR_OUT_ (4) bool +mhd_htbl_get_entry (const struct mhd_HpackDTblContext *restrict dyn, + dtbl_idx_ft idx, + struct mhd_BufferConst *restrict name_out, + struct mhd_BufferConst *restrict value_out) +{ + if (mhd_COND_HARDLY_EVER (0u == idx)) + return false; + if (mhd_HPACK_STBL_LAST_IDX >= idx) + { + mhd_stbl_get_entry (idx, + name_out, + value_out); + return true; + } + + return mhd_dtbl_get_entry (dyn, + idx, + name_out, + value_out); +} + + +/** + * Look up a header-table entry (static "real" headers first, then dynamic) + * that exactly matches the given name and value. + * + * Pseudo-headers (names starting with ':') are not searched. The input + * strings do not need to be zero-terminated. + * + * @param dyn const pointer to the dynamic table context + * @param name_len length of @a name in bytes + * @param name pointer to the header field name, must not start with ':', + * does NOT need to be zero-terminated + * @param val_len length of @a val in bytes + * @param val pointer to the header field value, + * does NOT need to be zero-terminated + * @return the HPACK index of the matching entry (either static or dynamic), + * or 0 if not found + */ +static MHD_FN_PAR_IN_SIZE_ (3,2) MHD_FN_PAR_IN_SIZE_ (5,4) dtbl_idx_t +mhd_htbl_find_entry_real (const struct mhd_HpackDTblContext *restrict dyn, + size_t name_len, + const char *restrict name, + size_t val_len, + const char *restrict val) +{ + dtbl_idx_ft idx; +#ifndef MHD_UNIT_TESTING /* Do not abort on a wrong name when unit-testing */ + mhd_assert ((0u == name_len) || (':' != name[0])); +#endif /* ! MHD_UNIT_TESTING */ + + if (0u != name_len) + idx = mhd_stbl_find_entry_real (name_len, + name, + val_len, + val); + else + idx = 0u; + + if (0u == idx) + idx = mhd_dtbl_find_entry (dyn, + name_len, + name, + val_len, + val); + + return (dtbl_idx_t) idx; +} + + +/** + * Look up a header-table entry (static "real" headers first, then dynamic) + * whose name exactly matches @a name. + * + * Pseudo-headers (names starting with ':') are not searched. The input + * string does not need to be zero-terminated. + * + * @param dyn const pointer to the dynamic table context + * @param name_len length of @a name in bytes + * @param name pointer to the header field name, must not start with ':', + * does NOT need to be zero-terminated + * @return the HPACK index of the matching entry (either static or dynamic), + * or 0 if not found + */ +static MHD_FN_PAR_IN_SIZE_ (3,2) dtbl_idx_t +mhd_htbl_find_name_real (const struct mhd_HpackDTblContext *restrict dyn, + size_t name_len, + const char *restrict name) +{ + dtbl_idx_ft idx; +#ifndef MHD_UNIT_TESTING /* Do not abort on a wrong name when unit-testing */ + mhd_assert ((0u == name_len) || (':' != name[0])); +#endif /* ! MHD_UNIT_TESTING */ + + if (0u != name_len) + idx = mhd_stbl_find_name_real (name_len, + name); + else + idx = 0u; + + if (0u == idx) + idx = mhd_dtbl_find_name (dyn, + name_len, + name); + + return (dtbl_idx_t) idx; +} + + +/* **** ___________ End of HPACK header tables handling ____________ ****** */ + +/** + * H2 HPACK default maximum size of the dynamic table + */ +mhd_constexpr size_t mhd_hpack_def_dyn_table_size = 4096u; + +#if ! defined(mhd_HPACK_TESTING_TABLES_ONLY) || ! defined(MHD_UNIT_TESTING) + +/** + * Exactly eight bits all set (to one). + */ +mhd_constexpr uint8_t b8ones = 0xFFu; + +/** + * The maximum number of bytes allowed to encode numbers in HPACK. + * + * Current implementation supports only 32-bit numbers for strings and indices, + * but extra zeros at the end of the encoded numbers can be safely processed. + * This value limits the number of extra zero bytes at the end to a reasonable + * value. It is enough to process the output of some weak encoder which may + * encode numbers always as 64-bit-long values with some extra zero bytes at + * the end of the encoded form. + */ +mhd_constexpr uint_fast8_t mhd_hpack_num_max_bytes = 12u; + +/* ****** ----------------- HPACK headers decoding ----------------- ****** */ + +/** + * Result of hpack_dec_number() + */ +enum MHD_FIXED_ENUM_ mhd_HpackGetNumResult +{ + mhd_HPACK_GET_NUM_RES_NO_ERROR, /**< Success */ + mhd_HPACK_GET_NUM_RES_INCOMPLETE,/**< Not enough data in the input buffer */ + mhd_HPACK_GET_NUM_RES_TOO_LARGE, /**< The decoded integer is too large for 32-bit */ + mhd_HPACK_GET_NUM_RES_TOO_LONG /**< The tail of the encoded number has too many extra zero bytes */ +}; + +/** + * Decode an HPACK integer number from the input buffer using a prefix in + * the first byte. + * @param first_byte_prefix_bits number of prefix bits in the first byte (1..7) + * @param buf_size the size of @a buf + * @param buf the input buffer + * @param[out] num_out where to store the decoded value (fits into 32-bit range) + * @param[out] bytes_decoded where to store the number of decoded bytes + * @return #mhd_HPACK_GET_NUM_RES_NO_ERROR on success, + * error code otherwise + */ +static MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_SIZE_ (3,2) +MHD_FN_PAR_OUT_ (4) MHD_FN_PAR_OUT_ (5) enum mhd_HpackGetNumResult +hpack_dec_number (uint_fast8_t first_byte_prefix_bits, + const size_t buf_size, + const uint8_t buf[MHD_FN_PAR_DYN_ARR_SIZE_ (buf_size)], + uint_fast32_t *restrict num_out, + size_t *restrict bytes_decoded) +{ + /** The maximum value of the first byte. Also the mask for the first byte. */ + const uint_fast8_t first_byte_val_max = + (uint_fast8_t) (b8ones >> first_byte_prefix_bits); + uint_fast8_t first_byte; + uint_fast32_t dec_num; + uint_fast8_t i; + + mhd_assert (0 != first_byte_prefix_bits); + mhd_assert (8 > first_byte_prefix_bits); + + first_byte = (buf[0] & first_byte_val_max); + if (first_byte_val_max != first_byte) + { + *num_out = (uint_fast32_t) first_byte; + *bytes_decoded = 1u; + return mhd_HPACK_GET_NUM_RES_NO_ERROR; /* Success exit point */ + } + dec_num = first_byte; + +#ifndef MHD_FAVOR_SMALL_CODE + /* Unrolled loop */ + i = 1u; + if (buf_size == i) + return mhd_HPACK_GET_NUM_RES_INCOMPLETE; /* Failure exit point */ + else + { + const uint_fast8_t cur_byte = buf[i]; + const bool is_final = (0u == (cur_byte & 0x80u)); + const uint_fast8_t byte_val = (uint_fast8_t) (cur_byte & 0x7Fu); + dec_num += (uint_fast32_t) (((uint_fast32_t) byte_val) << (7u * (i - 1u))); + if (is_final) + { + *num_out = dec_num; + *bytes_decoded = (size_t) (i + 1u); + return mhd_HPACK_GET_NUM_RES_NO_ERROR; /* Success exit point */ + } + } + + i = 2u; + if (buf_size == i) + return mhd_HPACK_GET_NUM_RES_INCOMPLETE; /* Failure exit point */ + else + { + const uint_fast8_t cur_byte = buf[i]; + const bool is_final = (0u == (cur_byte & 0x80u)); + const uint_fast8_t byte_val = (uint_fast8_t) (cur_byte & 0x7Fu); + dec_num += (uint_fast32_t) (((uint_fast32_t) byte_val) << (7u * (i - 1u))); + if (is_final) + { + *num_out = dec_num; + *bytes_decoded = (size_t) (i + 1u); + return mhd_HPACK_GET_NUM_RES_NO_ERROR; /* Success exit point */ + } + } + + i = 3u; + if (buf_size == i) + return mhd_HPACK_GET_NUM_RES_INCOMPLETE; /* Failure exit point */ + else + { + const uint_fast8_t cur_byte = buf[i]; + const bool is_final = (0u == (cur_byte & 0x80u)); + const uint_fast8_t byte_val = (uint_fast8_t) (cur_byte & 0x7Fu); + dec_num += (uint_fast32_t) (((uint_fast32_t) byte_val) << (7u * (i - 1u))); + if (is_final) + { + *num_out = dec_num; + *bytes_decoded = (size_t) (i + 1u); + return mhd_HPACK_GET_NUM_RES_NO_ERROR; /* Success exit point */ + } + } + + i = 4u; + if (buf_size == i) + return mhd_HPACK_GET_NUM_RES_INCOMPLETE; /* Failure exit point */ + else + { + const uint_fast8_t cur_byte = buf[i]; + const bool is_final = (0u == (cur_byte & 0x80u)); + const uint_fast8_t byte_val = (uint_fast8_t) (cur_byte & 0x7Fu); + dec_num += (uint_fast32_t) (((uint_fast32_t) byte_val) << (7u * (i - 1u))); + if (is_final) + { + *num_out = dec_num; + *bytes_decoded = (size_t) (i + 1u); + return mhd_HPACK_GET_NUM_RES_NO_ERROR; /* Success exit point */ + } + } + + i = 5u; +#else /* MHD_FAVOR_SMALL_CODE */ + /* First four bytes cannot overflow the output */ + for (i = 1u; 4u >= i; ++i) + { + if (buf_size == i) + return mhd_HPACK_GET_NUM_RES_INCOMPLETE; /* Failure exit point */ + else + { + const uint_fast8_t cur_byte = buf[i]; + const bool is_final = (0u == (cur_byte & 0x80u)); + const uint_fast8_t byte_val = (uint_fast8_t) (cur_byte & 0x7Fu); + dec_num += (uint_fast32_t) (((uint_fast32_t) byte_val) << (7u * (i - 1u))) + ; + if (is_final) + { + *num_out = dec_num; + *bytes_decoded = (size_t) (i + 1u); + return mhd_HPACK_GET_NUM_RES_NO_ERROR; /* Success exit point */ + } + } + } +#endif /* MHD_FAVOR_SMALL_CODE */ + + mhd_assert (0u == (dec_num >> 29u)); + mhd_assert (5u == i); + if (buf_size == i) + return mhd_HPACK_GET_NUM_RES_INCOMPLETE; /* Failure exit point */ + else + { /* Handle the fifth byte with overflow checks */ + const uint_fast8_t cur_byte = buf[i]; + const bool is_final = (0u == (cur_byte & 0x80u)); + const uint_fast8_t byte_val = (uint_fast8_t) (cur_byte & 0x7Fu); + const uint_fast32_t add_val = + (uint_fast32_t) (((uint_fast32_t) byte_val) << (7u * (i - 1u))); + if (byte_val != ((add_val & 0xFFFFFFFFu) >> (7u * (i - 1u)))) + return mhd_HPACK_GET_NUM_RES_TOO_LARGE; /* Failure exit point */ + dec_num += add_val; + if ((dec_num & 0xFFFFFFFFu) < add_val) + return mhd_HPACK_GET_NUM_RES_TOO_LARGE; /* Failure exit point */ + else if (is_final) + { + *num_out = dec_num; + *bytes_decoded = (size_t) (i + 1u); + return mhd_HPACK_GET_NUM_RES_NO_ERROR; /* Success exit point */ + } + } + + /* Process possible extra zero-valued tail bytes */ + while (++i <= mhd_hpack_num_max_bytes) + { + if (buf_size == i) + return mhd_HPACK_GET_NUM_RES_INCOMPLETE; /* Failure exit point */ + else + { + const uint_fast8_t cur_byte = buf[i]; + const bool is_final = (0u == (cur_byte & 0x80u)); + const uint_fast8_t byte_val = (uint_fast8_t) (cur_byte & 0x7Fu); + if (0u != byte_val) + return mhd_HPACK_GET_NUM_RES_TOO_LARGE; /* Failure exit point */ + else if (is_final) + { + *num_out = dec_num; + *bytes_decoded = (size_t) (i + 1u); + return mhd_HPACK_GET_NUM_RES_NO_ERROR; /* Success exit point */ + } + } + } + + return mhd_HPACK_GET_NUM_RES_TOO_LONG; /* Failure exit point */ +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_OUT_ (1) bool +mhd_hpack_dec_init (struct mhd_HpackDecContext *hk_dec) +{ + hk_dec->dyn = mhd_dtbl_create (mhd_hpack_def_dyn_table_size); + + if (NULL == hk_dec->dyn) + return false; /* Failure exit point */ + + mhd_assert (mhd_hpack_def_dyn_table_size == \ + mhd_dtbl_get_table_max_size (hk_dec->dyn)); + + hk_dec->max_allowed_dyn_size = mhd_hpack_def_dyn_table_size; + hk_dec->last_remote_dyn_size = hk_dec->max_allowed_dyn_size; + + return true; /* Success exit point */ +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_INOUT_ (1) void +mhd_hpack_dec_deinit (struct mhd_HpackDecContext *hk_dec) +{ + if (NULL == hk_dec->dyn) + return; /* Nothing to de-initialise */ + + mhd_dtbl_destroy (hk_dec->dyn); + hk_dec->dyn = NULL; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_INOUT_ (1) void +mhd_hpack_dec_set_allowed_dyn_size (struct mhd_HpackDecContext *hk_dec, + size_t new_allowed_dyn_size) +{ + mhd_assert (mhd_DTBL_MAX_SIZE >= new_allowed_dyn_size); + hk_dec->max_allowed_dyn_size = new_allowed_dyn_size; +} + + +/** + * Ensure that any pending dynamic table resize is applied before decoding + * fields. + * Also check for possible missing Dynamic Table Size Update messages (after + * reception of ACK for settings reducing the maximum table size). + * @param hk_dec pointer to the decoder context + * @return non-error decoder result on success; + * an error code if resize is disallowed or memory allocation fails + */ +static enum mhd_HpackDecResult +dec_check_resize_pending (struct mhd_HpackDecContext *restrict hk_dec) +{ + mhd_assert (mhd_DTBL_MAX_SIZE >= hk_dec->last_remote_dyn_size); + if (hk_dec->max_allowed_dyn_size < hk_dec->last_remote_dyn_size) + return mhd_HPACK_DEC_RES_DYN_SIZE_UPD_MISSING; /* Failure exit point */ + + if (mhd_dtbl_get_table_max_size (hk_dec->dyn) != hk_dec->last_remote_dyn_size) + { + /* Resize must be performed before processing any headers data */ + if (! mhd_dtbl_resize (&(hk_dec->dyn), + hk_dec->last_remote_dyn_size)) + return mhd_HPACK_DEC_RES_ALLOC_ERR; /* Failure exit point */ + } + + mhd_assert (mhd_dtbl_get_table_max_size (hk_dec->dyn) \ + == hk_dec->last_remote_dyn_size); + return mhd_HPACK_DEC_RES_NEW_FIELD; /* Success, return any non-error code */ +} + + +/** + * Decode an indexed header field and write "name\0value\0" to @a out_buff. + * @param hk_dec the decoder context + * @param enc_data_size the size of @a enc_data + * @param enc_data the encoded data + * @param out_buff_size the size of @a out_buff + * @param[out] out_buff the output buffer for "name\0value\0" + * @param[out] name_len set to the length of the name, not counting + * terminating zero + * @param[out] val_len set to the length of the value, not counting + * terminating zero + * @param[out] bytes_decoded set to the number of decoded bytes + * @return #mhd_HPACK_DEC_RES_NEW_FIELD on success or an error code + */ +static MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_SIZE_ (3,2) MHD_FN_PAR_OUT_SIZE_ (5,4) +MHD_FN_PAR_OUT_ (6) MHD_FN_PAR_OUT_ (7) +MHD_FN_PAR_OUT_ (8) enum mhd_HpackDecResult +hpack_dec_field_indexed (struct mhd_HpackDecContext *restrict hk_dec, + size_t enc_data_size, + const uint8_t *restrict enc_data, + size_t out_buff_size, + char *restrict out_buff, + size_t *restrict name_len, + size_t *restrict val_len, + size_t *restrict bytes_decoded) +{ + enum mhd_HpackDecResult res; + enum mhd_HpackGetNumResult dec_res; + size_t idx_enc_len; + uint_fast32_t field_idx; + struct mhd_BufferConst idx_name; + struct mhd_BufferConst idx_value; + + mhd_assert (1u == (enc_data[0] >> 7u)); + mhd_assert (0u != out_buff_size); + + /* If any dynamic table resize is pending, it must be performed before + header strings processing. */ + res = dec_check_resize_pending (hk_dec); + if (mhd_HPACK_DEC_RES_IS_ERR (res)) + return res; + + dec_res = hpack_dec_number (1u, + enc_data_size, + enc_data, + &field_idx, + &idx_enc_len); + switch (dec_res) + { + case mhd_HPACK_GET_NUM_RES_INCOMPLETE: + return mhd_HPACK_DEC_RES_INCOMPLETE; /* Failure exit point */ + case mhd_HPACK_GET_NUM_RES_TOO_LARGE: + return mhd_HPACK_DEC_RES_HPACK_BAD_IDX; /* Failure exit point */ + case mhd_HPACK_GET_NUM_RES_TOO_LONG: + return mhd_HPACK_DEC_RES_NUMBER_TOO_LONG; /* Failure exit point */ + case mhd_HPACK_GET_NUM_RES_NO_ERROR: + break; + default: + mhd_UNREACHABLE (); + return mhd_HPACK_DEC_RES_INTERNAL_ERR; /* Failure exit point */ + } + + mhd_assert (0u != idx_enc_len); + + if (mhd_COND_HARDLY_EVER (mhd_HPACK_MAX_POSSIBLE_IDX < field_idx)) + return mhd_HPACK_DEC_RES_HPACK_BAD_IDX; /* Failure exit point */ + + if (! mhd_htbl_get_entry (hk_dec->dyn, + (dtbl_idx_ft) field_idx, + &idx_name, + &idx_value)) + return mhd_HPACK_DEC_RES_HPACK_BAD_IDX; /* Failure exit point */ + + /* No math overflow check is needed here as both strings are already stored + in memory together with pointers. */ + if (out_buff_size < (idx_name.size + idx_value.size + 2u)) + return mhd_HPACK_DEC_RES_BUFFER_TOO_SMALL; /* Failure exit point */ + + memcpy (out_buff, + idx_name.data, + idx_name.size); + out_buff[idx_name.size] = '\0'; /* Zero-terminate field name */ + + memcpy (out_buff + idx_name.size + 1u, + idx_value.data, + idx_value.size); + out_buff[idx_name.size + 1u + idx_value.size] = '\0'; /* Zero-terminate field value */ + + *name_len = idx_name.size; + *val_len = idx_value.size; + *bytes_decoded = idx_enc_len; + + return mhd_HPACK_DEC_RES_NEW_FIELD; +} + + +/** + * Decode an HPACK string literal (with or without Huffman coding). + * The output string in @a out_buff is zero-terminated. + * @param enc_data_size the size of @a enc_data + * @param enc_data the pointer to the encoded data + * @param out_buff_size the size of @a out_buff + * @param[out] out_buff the output buffer for the decoded string, + * the output is zero-terminated + * @param[out] out_len set to the decoded string length, + * not counting zero-termination + * @param[out] bytes_decoded set to the number of decoded bytes + * @return #mhd_HPACK_DEC_RES_NEW_FIELD on success, + * error code otherwise + */ +static MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_SIZE_ (2,1) MHD_FN_PAR_OUT_SIZE_ (4,3) +MHD_FN_PAR_OUT_ (5) MHD_FN_PAR_OUT_ (6) enum mhd_HpackDecResult +hpack_dec_string_literal (size_t enc_data_size, + const uint8_t *restrict enc_data, + size_t out_buff_size, + char *restrict out_buff, + size_t *restrict out_len, + size_t *restrict bytes_decoded) +{ + const bool is_huff_enc = (0u != (enc_data[0] & 0x80u)); + uint_fast32_t enc_str_len; + enum mhd_HpackGetNumResult dec_res; + size_t enc_num_len; + size_t dec_str_len; + + mhd_assert (0u != enc_data_size); + mhd_assert (0u != out_buff_size); + + dec_res = hpack_dec_number (1u, + enc_data_size, + enc_data, + &enc_str_len, + &enc_num_len); + switch (dec_res) + { + case mhd_HPACK_GET_NUM_RES_INCOMPLETE: + return mhd_HPACK_DEC_RES_INCOMPLETE;/* Failure exit point */ + case mhd_HPACK_GET_NUM_RES_TOO_LARGE: + return mhd_HPACK_DEC_RES_STRING_TOO_LONG; /* Failure exit point */ + case mhd_HPACK_GET_NUM_RES_TOO_LONG: + return mhd_HPACK_DEC_RES_NUMBER_TOO_LONG; /* Failure exit point */ + case mhd_HPACK_GET_NUM_RES_NO_ERROR: + break; + default: + mhd_UNREACHABLE (); + return mhd_HPACK_DEC_RES_INTERNAL_ERR; /* Failure exit point */ + } + + mhd_assert (0u != enc_num_len); + mhd_assert (enc_num_len <= enc_data_size); + + if ((enc_data_size - enc_num_len) < enc_str_len) + return mhd_HPACK_DEC_RES_INCOMPLETE; /* Failure exit point */ + + if (mhd_COND_HARDLY_EVER (0u == enc_str_len)) + dec_str_len = 0; /* Zero length string, can be Huffman-encoded or not */ + else if (is_huff_enc) + { /* String with Huffman encoding */ + enum mhd_H2HuffDecodeRes huff_dec_res; + + /* mhd_h2_huffman_decode() will check whether the output buffer is large + enough. */ + dec_str_len = mhd_h2_huffman_decode ((size_t) enc_str_len, + enc_data + enc_num_len, + out_buff_size - 1u, /* leave one byte for zero-termination */ + out_buff, + &huff_dec_res); + switch (huff_dec_res) + { + case MHD_H2_HUFF_DEC_RES_NO_SPACE: + return mhd_HPACK_DEC_RES_BUFFER_TOO_SMALL; /* Failure exit point */ + case MHD_H2_HUFF_DEC_RES_BROKEN_DATA: + return mhd_HPACK_DEC_RES_HUFFMAN_ERR; /* Failure exit point */ + break; + case MHD_H2_HUFF_DEC_RES_OK: + break; + default: + mhd_UNREACHABLE (); + return mhd_HPACK_DEC_RES_INTERNAL_ERR; /* Failure exit point */ + } + mhd_assert (0u != dec_str_len); + mhd_assert (MHD_H2_HUFF_DEC_RES_OK == huff_dec_res); + mhd_assert (dec_str_len < out_buff_size); + } + else + { /* String without Huffman encoding */ + if (out_buff_size <= enc_str_len) /* leave one byte for zero-termination */ + return mhd_HPACK_DEC_RES_BUFFER_TOO_SMALL; /* Failure exit point */ + + dec_str_len = (size_t) enc_str_len; + memcpy (out_buff, + enc_data + enc_num_len, + dec_str_len); + } + + mhd_assert (out_buff_size > dec_str_len); + + out_buff[dec_str_len] = '\0'; /* Zero-terminate the result */ + *out_len = dec_str_len; + *bytes_decoded = enc_num_len + (size_t) enc_str_len; + return mhd_HPACK_DEC_RES_NEW_FIELD; /* Return any non-error code */ +} + + +/** + * Decode a literal header field (with or without indexing) and write + * "name\0value\0" to the output buffer @a out_buff. + * If @a with_indexing is 'true', the decoded field is inserted into the + * dynamic table. + * @param hk_dec the decoder context + * @param enc_data_size the size of @a enc_data + * @param enc_data the encoded data + * @param out_buff_size the size of @a out_buff + * @param with_indexing non-zero to insert the field into the dynamic table + * @param[out] out_buff output the buffer for the decoded strings + * @param[out] name_len set to the length of the name, not counting + * zero-terminating + * @param[out] val_len set to the length of the value, not counting + * zero-terminating + * @param[out] bytes_decoded set to the number of decoded bytes + * @return #mhd_HPACK_DEC_RES_NEW_FIELD on success, + * error code otherwise + */ +static MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_SIZE_ (3,2) MHD_FN_PAR_OUT_SIZE_ (6,5) +MHD_FN_PAR_OUT_ (7) MHD_FN_PAR_OUT_ (8) +MHD_FN_PAR_OUT_ (9) enum mhd_HpackDecResult +hpack_dec_field_literal (struct mhd_HpackDecContext *restrict hk_dec, + size_t enc_data_size, + const uint8_t *restrict enc_data, + bool with_indexing, + size_t out_buff_size, + char *restrict out_buff, + size_t *restrict name_len, + size_t *restrict val_len, + size_t *restrict bytes_decoded) +{ + const uint_fast8_t prfx_bits = (with_indexing ? 2u : 4u); + enum mhd_HpackDecResult res; + size_t pos; + size_t pos_incr; + uint_fast32_t name_idx; + + mhd_assert (with_indexing || \ + (1u == (enc_data[0] >> 4u)) || (0u == (enc_data[0] >> 4u))); + mhd_assert (! with_indexing || \ + (1u == (enc_data[0] >> 6u))); + mhd_assert (0u != enc_data_size); + mhd_assert (2u <= out_buff_size); + + /* If any dynamic table resize is pending, it must be performed before + headers strings processing. */ + res = dec_check_resize_pending (hk_dec); + if (mhd_HPACK_DEC_RES_IS_ERR (res)) + return res; + + pos = 0u; +#ifndef MHD_FAVOR_SMALL_CODE + if (0u == (enc_data[0] & (b8ones >> prfx_bits))) + { + name_idx = 0u; /* Shortcut for frequent case */ + pos_incr = 1u; + } + else +#endif /* ! MHD_FAVOR_SMALL_CODE */ + if (1) + { + enum mhd_HpackGetNumResult dec_res; + dec_res = hpack_dec_number (prfx_bits, + enc_data_size, + enc_data, + &name_idx, + &pos_incr); + switch (dec_res) + { + case mhd_HPACK_GET_NUM_RES_INCOMPLETE: + return mhd_HPACK_DEC_RES_INCOMPLETE; /* Failure exit point */ + case mhd_HPACK_GET_NUM_RES_TOO_LARGE: + return mhd_HPACK_DEC_RES_HPACK_BAD_IDX; /* Failure exit point */ + case mhd_HPACK_GET_NUM_RES_TOO_LONG: + return mhd_HPACK_DEC_RES_NUMBER_TOO_LONG; /* Failure exit point */ + case mhd_HPACK_GET_NUM_RES_NO_ERROR: + break; + default: + mhd_UNREACHABLE (); + return mhd_HPACK_DEC_RES_INTERNAL_ERR; /* Failure exit point */ + } + + mhd_assert (0u != pos_incr); +#ifndef MHD_FAVOR_SMALL_CODE + mhd_assert (0u != name_idx); +#endif /* ! MHD_FAVOR_SMALL_CODE */ + } + + pos += pos_incr; + mhd_assert (0u != pos); + + if (enc_data_size == pos) + return mhd_HPACK_DEC_RES_INCOMPLETE; /* Failure exit point */ + + if (0u == name_idx) + { /* Literal name */ + mhd_assert (1u == pos); + pos = 1u; /* Help compiler to optimise */ + + res = hpack_dec_string_literal (enc_data_size - pos, + enc_data + pos, + out_buff_size - 1u, /* At least one char for the value string */ + out_buff, + name_len, + &pos_incr); + if (mhd_HPACK_DEC_RES_IS_ERR (res)) + return res; /* Failure exit point */ + } + else + { /* Indexed name */ + struct mhd_BufferConst idx_name; + struct mhd_BufferConst idx_value; /* extracted value is unused */ + + if (mhd_COND_HARDLY_EVER (mhd_HPACK_MAX_POSSIBLE_IDX < name_idx)) + return mhd_HPACK_DEC_RES_HPACK_BAD_IDX; /* Failure exit point */ + + if (! mhd_htbl_get_entry (hk_dec->dyn, + (dtbl_idx_ft) name_idx, + &idx_name, + &idx_value)) + return mhd_HPACK_DEC_RES_HPACK_BAD_IDX; /* Failure exit point */ + + if (idx_name.size >= (out_buff_size - 1u)) + return mhd_HPACK_DEC_RES_BUFFER_TOO_SMALL; /* Failure exit point */ + + memcpy (out_buff, + idx_name.data, + idx_name.size); + out_buff[idx_name.size] = '\0'; /* Zero-terminate resulting string */ + *name_len = idx_name.size; + + pos_incr = 0u; + } + pos += pos_incr; + + if (enc_data_size == pos) + return mhd_HPACK_DEC_RES_INCOMPLETE; /* Failure exit point */ + + mhd_assert (out_buff_size >= (*name_len + 2u)); + res = hpack_dec_string_literal (enc_data_size - pos, + enc_data + pos, + out_buff_size - (*name_len + 1u), + out_buff + (*name_len + 1u), + val_len, + &pos_incr); + if (mhd_HPACK_DEC_RES_IS_ERR (res)) + return res; /* Failure exit point */ + + pos += pos_incr; + *bytes_decoded = pos; + + if (with_indexing) + mhd_dtbl_new_entry (hk_dec->dyn, + *name_len, + out_buff, + *val_len, + out_buff + (*name_len) + 1u); + + return mhd_HPACK_DEC_RES_NEW_FIELD; +} + + +/** + * Decode and apply a Dynamic Table Size Update. + * Performs eviction only; actual resize is deferred until before first header + * decoding. + * @param hk_dec the decoder context + * @param enc_data_size the size of @a enc_data + * @param enc_data the encoded data + * @param[out] bytes_decoded set to the number of decoded bytes + * @return #mhd_HPACK_DEC_RES_NO_NEW_FIELD on success, + * error code otherwise + */ +static MHD_FN_PAR_IN_SIZE_ (3,2) +MHD_FN_PAR_OUT_ (4) enum mhd_HpackDecResult +dec_update_dyn_size (struct mhd_HpackDecContext *restrict hk_dec, + const size_t enc_data_size, + const uint8_t *restrict enc_data, + size_t *restrict bytes_decoded) +{ + uint_fast32_t new_dyn_size; + size_t used_bytes; + enum mhd_HpackGetNumResult dec_res; + + mhd_assert ((1u == (enc_data[0] >> 5u)) && \ + "the first byte must be the dynamic table update signal"); + dec_res = hpack_dec_number (3u, + enc_data_size, + enc_data, + &new_dyn_size, + &used_bytes); + switch (dec_res) + { + case mhd_HPACK_GET_NUM_RES_INCOMPLETE: + return mhd_HPACK_DEC_RES_INCOMPLETE; /* Failure exit point */ + case mhd_HPACK_GET_NUM_RES_TOO_LARGE: + return mhd_HPACK_DEC_RES_DYN_SIZE_UPD_TOO_LARGE; /* Failure exit point */ + case mhd_HPACK_GET_NUM_RES_TOO_LONG: + return mhd_HPACK_DEC_RES_NUMBER_TOO_LONG; /* Failure exit point */ + case mhd_HPACK_GET_NUM_RES_NO_ERROR: + break; + default: + mhd_UNREACHABLE (); + return mhd_HPACK_DEC_RES_INTERNAL_ERR; /* Failure exit point */ + } + mhd_assert (0u != used_bytes); + + if (hk_dec->max_allowed_dyn_size < new_dyn_size) + return mhd_HPACK_DEC_RES_DYN_SIZE_UPD_TOO_LARGE; /* Failure exit point */ + + mhd_assert (mhd_DTBL_MAX_SIZE >= new_dyn_size); + + /* Only evict here, no resize yet to avoid repetitive realloc() calls if + remote sends multiple table size updates in a row. */ + mhd_dtbl_evict_to_size (hk_dec->dyn, + (size_t) new_dyn_size); + + hk_dec->last_remote_dyn_size = (size_t) new_dyn_size; + + *bytes_decoded = used_bytes; + return mhd_HPACK_DEC_RES_NO_NEW_FIELD; /* Success exit point */ +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_INOUT_ (1) +MHD_FN_PAR_IN_SIZE_ (3, 2) +MHD_FN_PAR_OUT_SIZE_ (5, 4) +MHD_FN_PAR_OUT_ (6) MHD_FN_PAR_OUT_ (7) +MHD_FN_PAR_OUT_ (8) enum mhd_HpackDecResult +mhd_hpack_dec_data (struct mhd_HpackDecContext *restrict hk_dec, + size_t enc_data_size, + const uint8_t *restrict enc_data, + size_t out_buff_size, + char *restrict out_buff, + size_t *restrict name_len, + size_t *restrict val_len, + size_t *restrict bytes_decoded) +{ + uint_fast8_t action_id; + + mhd_assert (0u != enc_data_size); + mhd_assert (2u <= out_buff_size); + + action_id = enc_data[0] >> 4u; + + switch (action_id) + { + case (1u << 3u) + 0u: + case (1u << 3u) + 1u: + case (1u << 3u) + 2u: + case (1u << 3u) + 3u: + case (1u << 3u) + 4u: + case (1u << 3u) + 5u: + case (1u << 3u) + 6u: + case (1u << 3u) + 7u: + /* Indexed field */ + return hpack_dec_field_indexed (hk_dec, + enc_data_size, + enc_data, + out_buff_size, + out_buff, + name_len, + val_len, + bytes_decoded); + case (1u << 2u) + 0u: + case (1u << 2u) + 1u: + case (1u << 2u) + 2u: + case (1u << 2u) + 3u: + /* Literal field with indexing */ + return hpack_dec_field_literal (hk_dec, + enc_data_size, + enc_data, + true, + out_buff_size, + out_buff, + name_len, + val_len, + bytes_decoded); + case 0u << 0u: + /* Literal field without indexing */ + return hpack_dec_field_literal (hk_dec, + enc_data_size, + enc_data, + false, + out_buff_size, + out_buff, + name_len, + val_len, + bytes_decoded); + case 1u << 0u: + /* Literal field never indexed */ + return hpack_dec_field_literal (hk_dec, + enc_data_size, + enc_data, + false, + out_buff_size, + out_buff, + name_len, + val_len, + bytes_decoded); + case (1u << 1u) + 0u: + case (1u << 1u) + 1u: + /* Dynamic table size update */ + return dec_update_dyn_size (hk_dec, + enc_data_size, + enc_data, + bytes_decoded); + default: + break; + } + mhd_UNREACHABLE (); + return mhd_HPACK_DEC_RES_INTERNAL_ERR; +} + + +/* ****** _____________ End of HPACK headers decoding ______________ ****** */ + +/* ****** ----------------- HPACK headers encoding ----------------- ****** */ + +/** + * Compute the number of bytes required to encode an HPACK integer. + * + * Implements the integer encoding algorithm from RFC 7541, Section 5.1. + * The @a prefix_bits parameter specifies the count of fixed most-significant + * bits in the first byte (e.g., 1 for "1xxxxxxx", 2 for "01xxxxxx", + * 3 for "001xxxxx", 4 for "0000xxxx"/"0001xxxx"). + * The number of value bits available in the first byte is (8 - @a prefix_bits). + * + * @param[in] prefix_bits the count of fixed high-order bits in the first byte; + * must be greater than zero and less than 8 + * @param[in] number the value to encode, must fit 32 bits + * @return the total number of bytes needed (always non-zero) + */ +static size_t +hpack_number_len (uint_fast8_t prefix_bits, + uint_fast32_t number) +{ + const uint_fast8_t first_byte_val_max = + (uint_fast8_t) (b8ones >> prefix_bits); + uint_least32_t val_for_next_bytes; + + mhd_assert (0u != prefix_bits); + mhd_assert (8u > prefix_bits); + mhd_assert ((number & 0xFFFFFFFFu) == number); + + if (first_byte_val_max > number) /* the number must be strictly less than */ + return 1u; + val_for_next_bytes = (uint_least32_t) (number - first_byte_val_max); + if (0 == val_for_next_bytes) + return 2u; + return (uint_fast8_t) \ + ((mhd_BIT_WIDTH32NZ (val_for_next_bytes) + 6u) / 7u) + 1u; +} + + +/** + * Encode an HPACK integer into the provided output buffer. + * + * Encodes @a number according to RFC 7541, Section 5.1 using the given + * first-byte prefix. The @a first_byte_prefix must have its lowest + * (8 - @a first_byte_prefix_bits) bits cleared; these bits will be filled + * with the encoded value. + * + * @param[in] first_byte_prefix the first byte with fixed MSB pattern set; + * lower value bits must be zero + * @param[in] first_byte_prefix_bits the count of fixed MSBs in the first byte + * (1 for 1xxxxxxx, 2 for 01xxxxxx, etc.) + * @param[in] number the value to encode, must fit 32 bits + * @param[in] buf_size the size of @a buf in bytes, + * must not be zero + * @param[out] buf the output buffer to write the encoded bytes + * @return the number of bytes written on success; + * zero if output buffer is too small to fit the number encoded + */ +static MHD_FN_PAR_OUT_SIZE_ (5,4) size_t +hpack_put_number_to_buf (uint_fast8_t first_byte_prefix, + uint_fast8_t first_byte_prefix_bits, + uint_fast32_t number, + size_t buf_size, + uint8_t buf[MHD_FN_PAR_DYN_ARR_SIZE_ (buf_size)]) +{ + const uint_fast8_t first_byte_val_max = + (uint_fast8_t) (b8ones >> first_byte_prefix_bits); + uint_fast32_t number_left; + uint_fast8_t i; + + mhd_assert (0u == (first_byte_prefix & first_byte_val_max)); + mhd_assert (0u == ((first_byte_prefix >> 4u) >> 4u)); + mhd_assert (0u != first_byte_prefix_bits); + mhd_assert (8u > first_byte_prefix_bits); + mhd_assert ((number & 0xFFFFFFFFu) == number); + mhd_assert (0u != buf_size); + + if (first_byte_val_max > number) /* the number must be strictly less than */ + { + buf[0] = (uint8_t) (first_byte_prefix | (uint8_t) number); + return 1u; + } + buf[0] = (uint8_t) (first_byte_prefix | first_byte_val_max); + number_left = number - first_byte_val_max; + for (i = 1u; mhd_COND_PREDOMINANTLY (i < buf_size); ++i) + { + const uint8_t cur_byte = (uint8_t) (number_left & 0x7Fu); + number_left >>= 7u; + if (0 == number_left) + { + mhd_assert (0u == (cur_byte & 0x80u)); + buf[i] = cur_byte; + return i + 1u; /* Success exit point */ + } + buf[i] = (uint8_t) (cur_byte | 0x80u); + mhd_assert (6u > i); + } + return 0u; /* Not enough space */ +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_OUT_ (1) bool +mhd_hpack_enc_init (struct mhd_HpackEncContext *hk_enc) +{ + hk_enc->dyn = mhd_dtbl_create (mhd_hpack_def_dyn_table_size); + + if (NULL == hk_enc->dyn) + return false; /* Failure exit point */ + + mhd_assert (mhd_hpack_def_dyn_table_size == \ + mhd_dtbl_get_table_max_size (hk_enc->dyn)); + + /* Set all sizes to the same initial value */ + hk_enc->new_dyn_size = mhd_hpack_def_dyn_table_size; + hk_enc->smallest_dyn_size = hk_enc->new_dyn_size; + + return true; /* Success exit point */ +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_INOUT_ (1) void +mhd_hpack_enc_deinit (struct mhd_HpackEncContext *hk_enc) +{ + if (NULL == hk_enc->dyn) + return; /* Nothing to deinit */ + + mhd_dtbl_destroy (hk_enc->dyn); + hk_enc->dyn = NULL; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_INOUT_ (1) void +mhd_hpack_enc_set_dyn_size (struct mhd_HpackEncContext *hk_enc, + size_t new_dyn_size) +{ + mhd_assert (mhd_DTBL_MAX_SIZE >= new_dyn_size); + if (hk_enc->smallest_dyn_size > new_dyn_size) + hk_enc->smallest_dyn_size = new_dyn_size; + + /* Postpone actual table resize to avoid several realloc() calls if + multiple table resizes are performed. */ + hk_enc->new_dyn_size = new_dyn_size; +} + + +/** + * Encode an indexed field representation (RFC 7541, Section 6.1). + * + * @param[in] idx the 1-based field index, must be non-zero + * @param[in] out_buff_size the size of @a out_buff in bytes, + * must not be zero + * @param[out] out_buff the output buffer to write the encoded field + * @param[out] bytes_encoded to be set to the number of bytes written to the + * @a out_buff + * @return 'true' on success; + * 'false' if the output buffer is too small + */ +static MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_OUT_SIZE_ (3,2) MHD_FN_PAR_OUT_ (4) bool +hpack_enc_field_indexed (dtbl_idx_ft idx, + const size_t out_buff_size, + uint8_t *restrict out_buff, + size_t *restrict bytes_encoded) +{ + mhd_constexpr uint_fast8_t field_indexed_prfx = (uint_fast8_t) (1u << 7u); + mhd_constexpr uint_fast8_t field_indexed_prfx_bits = 1u; + size_t pos; + + mhd_assert (0u != idx); + mhd_assert (mhd_HPACK_MAX_POSSIBLE_IDX >= idx); + mhd_assert (0u != out_buff_size); + + pos = hpack_put_number_to_buf (field_indexed_prfx, + field_indexed_prfx_bits, + idx, + out_buff_size, + out_buff); + + if (0u == pos) + return false; /* Not enough space in the output buffer */ + + *bytes_encoded = pos; + return true; +} + + +/** + * Literal header indexing type for HPACK literal representations. + * + * Selects which literal form to use (RFC 7541, Sections 6.2.1-6.2.3). + */ +enum MHD_FIXED_ENUM_ mhd_HpackEncLitIndexingType +{ + /** + * "Literal Header Field with Incremental Indexing" + * RFC 7541, Section 6.2.1. + */ + mhd_HPACK_ENC_LIT_IDX_TYPE_INDEXING, + /** + * "Literal Header Field without Indexing" + * RFC 7541, Section 6.2.2. + */ + mhd_HPACK_ENC_LIT_IDX_TYPE_NOT_INDEXING, + /** + * "Literal Header Field Never Indexed" + * RFC 7541, Section 6.2.3. + */ + mhd_HPACK_ENC_LIT_IDX_TYPE_NEVER_INDEXING +}; + + +/** + * Encode a string literal with optional Huffman coding (RFC 7541, Section 5.2). + * + * @param[in,out] hk_enc the encoder context + * @param[in] str_data the field string to encode + * @param[in] huffman_allowed set to 'true' to allow Huffman encoding + * @param[in] out_buff_size the size of @a out_buff in bytes, could be zero + * @param[out] out_buff the output buffer + * @param[out] bytes_encoded to be set to the size of the encoded data + * written to the @a out_buff + * @return 'true' on success; + * 'false' if the output buffer is too small + */ +static MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_ (1) +MHD_FN_PAR_OUT_SIZE_ (4,3) MHD_FN_PAR_OUT_ (5) bool +hpack_enc_string_literal (const struct mhd_BufferConst *restrict str_data, + bool huffman_allowed, + const size_t out_buff_size, + uint8_t *restrict out_buff, + size_t *restrict bytes_encoded) +{ + /** The prefix for Huffman-encoded string */ + mhd_constexpr uint8_t huff_on_prfx = (uint8_t) (1u << 7u); + /** The prefix for literal string without Huffman encoding */ + mhd_constexpr uint8_t huff_off_prfx = (uint8_t) (0u << 7u); + mhd_constexpr uint8_t huff_prfx_bits = 1u; + size_t enc_size; + size_t enc_size_enc_len; + + mhd_assert ((str_data->size & 0xFFFFFFFFu) == str_data->size); + + if (mhd_COND_ALMOST_NEVER (0u == str_data->size)) + { + if (0u == out_buff_size) + return false; + /* If Huffman is allowed, encode zero size as "Huffman encoded" for + consistency. */ + out_buff[0] = (huffman_allowed ? huff_on_prfx : huff_off_prfx); + *bytes_encoded = 1u; + return true; + } + + if (huffman_allowed) + { + uint_fast32_t est_enc_size; + size_t est_enc_size_enc_len; + bool is_limited_by_buff_size; + + est_enc_size = + mhd_h2_huffman_est_avg_size ((uint_fast32_t) str_data->size); + est_enc_size_enc_len = hpack_number_len (huff_prfx_bits, + est_enc_size); + if ((out_buff_size <= est_enc_size_enc_len) || + ((out_buff_size - est_enc_size_enc_len) < est_enc_size)) + { + /* Probably the buffer is not large enough to encode the string */ + /* Try as if the string were compressible to a minimal size */ + est_enc_size = + mhd_h2_huffman_est_min_size ((uint_fast32_t) str_data->size); + est_enc_size_enc_len = hpack_number_len (huff_prfx_bits, + est_enc_size); + if (out_buff_size < (est_enc_size_enc_len + est_enc_size)) + return false; /* The output buffer is not large enough */ + is_limited_by_buff_size = true; + } + else + is_limited_by_buff_size = + ((out_buff_size - est_enc_size_enc_len) < str_data->size); + + mhd_assert (out_buff_size > est_enc_size_enc_len); + mhd_assert ((out_buff_size - est_enc_size_enc_len) \ + >= est_enc_size); + mhd_assert (is_limited_by_buff_size || \ + ((out_buff_size - est_enc_size_enc_len) >= str_data->size)); + + /* Limit the size of the buffer for the encoded string to the size of + the original (not encoded) string or the size of the buffer (whatever + is smaller). By limiting the size of the buffer to the size of the + original string, Huffman encoding that grows larger than the original + is aborted early. */ + enc_size = + mhd_h2_huffman_encode (str_data->size, + str_data->data, + (size_t) + (is_limited_by_buff_size ? + (out_buff_size - est_enc_size_enc_len) : + str_data->size), + out_buff + est_enc_size_enc_len); + + mhd_assert (out_buff_size - est_enc_size_enc_len >= enc_size); + + if (0u != enc_size) + { + /* Successfully Huffman-encoded the string */ + enc_size_enc_len = + hpack_put_number_to_buf (huff_on_prfx, + huff_prfx_bits, + (uint_fast32_t) enc_size, + est_enc_size_enc_len, + out_buff); + if (mhd_COND_ALMOST_NEVER (0u == enc_size_enc_len)) + { + /* The actual encoded size is larger than estimated */ + size_t calc_enc_size_enc_len; + + mhd_assert (est_enc_size < enc_size); + + calc_enc_size_enc_len = hpack_number_len (huff_prfx_bits, + (uint_fast32_t) enc_size); + if ((out_buff_size - enc_size) < calc_enc_size_enc_len) + return false; /* The output buffer is not large enough */ + + memmove (out_buff + calc_enc_size_enc_len, + out_buff + est_enc_size_enc_len, + enc_size); + + enc_size_enc_len = + hpack_put_number_to_buf (huff_on_prfx, + huff_prfx_bits, + (uint_fast32_t) enc_size, + calc_enc_size_enc_len, + out_buff); + mhd_assert (calc_enc_size_enc_len == enc_size_enc_len); + } + else if (est_enc_size_enc_len != enc_size_enc_len) + { + mhd_assert (est_enc_size_enc_len > enc_size_enc_len); + memmove (out_buff + enc_size_enc_len, + out_buff + est_enc_size_enc_len, + enc_size); + } + + *bytes_encoded = (enc_size_enc_len + enc_size); + return true; /* Success exit point */ + } + else /* 0u == enc_size */ + { + /* Huffman-encoded version needs more space than provided */ + /* If available space was less than needed to put the string without + Huffman encoding, then return failure here. */ + if (is_limited_by_buff_size) + return false; + } + /* Retry without Huffman encoding */ + } + + /* Put string without Huffman encoding */ + enc_size = str_data->size; + + if (enc_size >= out_buff_size) + return false; /* The output buffer is not large enough */ + + enc_size_enc_len = + hpack_put_number_to_buf (huff_off_prfx, + huff_prfx_bits, + (uint_fast32_t) enc_size, + out_buff_size - enc_size, + out_buff); + + if (0u == enc_size_enc_len) + return false; /* The output buffer is not large enough */ + + mhd_assert ((out_buff_size - enc_size_enc_len) >= enc_size); + + memcpy (out_buff + enc_size_enc_len, + str_data->data, + enc_size); + + *bytes_encoded = (enc_size_enc_len + enc_size); + return true; /* Success exit point */ +} + + +/** + * Encode a literal field (name by index reference or literal; value + * always literal). + * + * Produces one of the literal field representations (RFC 7541, + * Sections 6.2.1-6.2.3). + * The name may be encoded by index reference (if allowed) or literally; the + * value is always encoded literally. + * String representations may use Huffman coding if permitted. + * + * @param[in,out] hk_enc the encoder context + * @param[in] name the field name bytes and size + * @param[in] name_idx the field name index if known and indexed name is + * allowed, + * zero if index is not known or indexed name is not + * allowed + * @param[in] value the field value bytes and size + * @param[in] msg_type the literal representation kind to use + * @param[in] name_idx_stat_allowed set to 'true' if name encoding as a + * reference to the static table is allowed + * @param[in] name_idx_dyn_allowed set to 'true' if name encoding as a + * reference to the dynamic table is allowed + * @param[in] huffman_allowed set to 'true' if Huffman coding is allowed + * @param[in] out_buff_size the size of @a out_buff in bytes, + * could be zero (the function will always fail + * if it is less than two) + * @param[out] out_buff the output buffer for the encoded field + * @param[out] bytes_encoded to be set to the number of bytes written to + * the @a out_buff + * @return 'true' on success; + * 'false' if the output buffer is too small + */ +static MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_INOUT_ (1) +MHD_FN_PAR_IN_ (2) MHD_FN_PAR_IN_ (4) +MHD_FN_PAR_OUT_SIZE_ (10,9) MHD_FN_PAR_OUT_ (11) bool +hpack_enc_field_literal (struct mhd_HpackEncContext *restrict hk_enc, + const struct mhd_BufferConst *restrict name, + dtbl_idx_ft name_idx, + const struct mhd_BufferConst *restrict value, + enum mhd_HpackEncLitIndexingType msg_type, + bool name_idx_stat_allowed, + bool name_idx_dyn_allowed, + bool huffman_allowed, + const size_t out_buff_size, + uint8_t *restrict out_buff, + size_t *restrict bytes_encoded) +{ + mhd_constexpr uint_fast8_t field_indexing_prfx = (uint_fast8_t) (1u << 6u); + mhd_constexpr uint_fast8_t field_indexing_prfx_bits = 2u; + mhd_constexpr uint_fast8_t field_not_idxng_prfx = (uint_fast8_t) (0u << 4u); + mhd_constexpr uint_fast8_t field_not_idxng_prfx_bits = 4u; + mhd_constexpr uint_fast8_t field_never_idxng_prfx = (uint_fast8_t) (1u << 4u); + mhd_constexpr uint_fast8_t field_never_idxng_prfx_bits = 4u; + struct mhd_HpackDTblContext const *restrict dyn = hk_enc->dyn; + uint_fast8_t first_byte_prefix; + uint_fast8_t first_byte_prefix_bits; + size_t pos; + size_t pos_incr; + + mhd_assert ((0u == name->size) || (':' != name->data[0])); + + if (2u > out_buff_size) + return false; /* No space even for the minimal field */ + + switch (msg_type) + { + case mhd_HPACK_ENC_LIT_IDX_TYPE_INDEXING: + first_byte_prefix = field_indexing_prfx; + first_byte_prefix_bits = field_indexing_prfx_bits; + break; + case mhd_HPACK_ENC_LIT_IDX_TYPE_NOT_INDEXING: + first_byte_prefix = field_not_idxng_prfx; + first_byte_prefix_bits = field_not_idxng_prfx_bits; + break; + case mhd_HPACK_ENC_LIT_IDX_TYPE_NEVER_INDEXING: + first_byte_prefix = field_never_idxng_prfx; + first_byte_prefix_bits = field_never_idxng_prfx_bits; + break; + default: + mhd_UNREACHABLE (); + return false; + } + + if (0u == name_idx) + { + if (name_idx_stat_allowed && name_idx_dyn_allowed) + name_idx = mhd_htbl_find_name_real (dyn, + name->size, + name->data); + else if (name_idx_stat_allowed && (0u != name->size)) + name_idx = mhd_stbl_find_name_real (name->size, + name->data); + else if (mhd_COND_ALMOST_NEVER (name_idx_dyn_allowed)) + name_idx = mhd_dtbl_find_name (dyn, + name->size, + name->data); + } + else + { + mhd_assert (name_idx_stat_allowed || \ + (mhd_HPACK_STBL_LAST_IDX < name_idx)); + mhd_assert (name_idx_dyn_allowed || \ + (mhd_HPACK_STBL_LAST_IDX >= name_idx)); + } + + pos = 0u; + + if (0u != name_idx) + { + /* Add name as a reference */ + mhd_assert (name_idx_dyn_allowed || name_idx_stat_allowed); + pos_incr = hpack_put_number_to_buf (first_byte_prefix, + first_byte_prefix_bits, + name_idx, + out_buff_size - pos - 1u, /* Reserve one byte for the field value */ + out_buff + pos); + if (0u == pos_incr) + return false; /* Not enough space */ + pos += pos_incr; + } + else + { + /* Add name literally */ + + /* Use 'zero' index to indicate literal name */ + out_buff[pos++] = (uint8_t) first_byte_prefix; + + /* The buffer has at least one byte (or more) available; + the next call will fail if only one byte is available. */ + if (! hpack_enc_string_literal (name, + huffman_allowed, + out_buff_size - pos - 1u, /* Reserve one byte for the field value */ + out_buff + pos, + &pos_incr)) + return false; /* Not enough space */ + + pos += pos_incr; + } + + /* The output buffer should have at least one byte of space available */ + mhd_assert (out_buff_size > pos); + + /* Add value literally */ + + if (! hpack_enc_string_literal (value, + huffman_allowed, + out_buff_size - pos, + out_buff + pos, + &pos_incr)) + return false; /* Not enough space */ + + pos += pos_incr; + mhd_assert (out_buff_size >= pos); + + *bytes_encoded = pos; + return true; +} + + +/** + * Internal per-field encoding result. + */ +enum MHD_FIXED_ENUM_ mhd_HpackEncResultInternal +{ + /** + * The output buffer is too small + */ + mhd_ENC_RESULT_INT_NO_SPACE = 0, + /** + * The field is encoded successfully, do not add the field to the dynamic + * table + */ + mhd_ENC_RESULT_INT_OK_NO_ADD_TO_DYN, + /** + * The field is encoded successfully, add the field to the dynamic table + */ + mhd_ENC_RESULT_INT_OK_ADD_TO_DYN +}; + +/** + * Encode one field according to the requested indexing policy. + * + * Chooses between indexed and literal representations based on table contents + * and the @a enc_pol policy, and decides whether to add the field to the + * dynamic table (using simple size-based heuristics when not explicitly + * forced). + * + * @param[in,out] hk_enc the encoder context + * @param[in] name the header name + * @param[in] value the header value + * @param[in] enc_pol the encoding policy to apply + * @param[in] out_buff_size the size of @a out_buff in bytes, + * must not be zero + * @param[out] out_buff the output buffer + * @param[out] bytes_encoded to be set to the number of bytes written to + * the @a out_buff + * @return #mhd_ENC_RESULT_INT_NO_SPACE on insufficient buffer; + * #mhd_ENC_RESULT_INT_OK_NO_ADD_TO_DYN or + * #mhd_ENC_RESULT_INT_OK_ADD_TO_DYN on success + */ +static MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_INOUT_ (1) +MHD_FN_PAR_IN_ (2) MHD_FN_PAR_IN_ (3) +MHD_FN_PAR_OUT_SIZE_ (6,5) MHD_FN_PAR_OUT_ (7) enum mhd_HpackEncResultInternal +hpack_enc_field (struct mhd_HpackEncContext *restrict hk_enc, + const struct mhd_BufferConst *restrict name, + const struct mhd_BufferConst *restrict value, + enum mhd_HpackEncPolicy enc_pol, + const size_t out_buff_size, + uint8_t *restrict out_buff, + size_t *restrict bytes_encoded) +{ + mhd_assert (0u != out_buff_size); + mhd_assert ((name->size & 0xFFFFFFFFu) == name->size); + mhd_assert ((value->size & 0xFFFFFFFFu) == value->size); + + /* Check the enum values order */ + // TODO: replace with static asserts + mhd_assert (mhd_HPACK_ENC_POL_FORCED_NEW_IDX < mhd_HPACK_ENC_POL_FORCED); + mhd_assert (mhd_HPACK_ENC_POL_ALWAYS_IF_FIT < mhd_HPACK_ENC_POL_NOT_INDEXED); + mhd_assert (mhd_HPACK_ENC_POL_ALWAYS_IF_FIT < mhd_HPACK_ENC_POL_DESIRABLE); + mhd_assert (mhd_HPACK_ENC_POL_DESIRABLE < mhd_HPACK_ENC_POL_LOWEST_PRIO); + mhd_assert (mhd_HPACK_ENC_POL_LOWEST_PRIO < mhd_HPACK_ENC_POL_AVOID_NEW_IDX); + mhd_assert (mhd_HPACK_ENC_POL_AVOID_NEW_IDX < mhd_HPACK_ENC_POL_NOT_INDEXED); + mhd_assert (mhd_HPACK_ENC_POL_NOT_INDEXED < \ + mhd_HPACK_ENC_POL_NEVER_W_NAME_IDX); + mhd_assert (mhd_HPACK_ENC_POL_NEVER_W_NAME_IDX < \ + mhd_HPACK_ENC_POL_NEVER_W_NAME_LIT_NO_HUFFMAN); + + if ((mhd_HPACK_ENC_POL_FORCED <= enc_pol) + && (mhd_HPACK_ENC_POL_AVOID_NEW_IDX >= enc_pol)) + { + const dtbl_idx_ft field_idx = + mhd_htbl_find_entry_real (hk_enc->dyn, + name->size, + name->data, + value->size, + value->data); + + if (0u != field_idx) + { + if (! hpack_enc_field_indexed (field_idx, + out_buff_size, + out_buff, + bytes_encoded)) + return mhd_ENC_RESULT_INT_NO_SPACE; + + return mhd_ENC_RESULT_INT_OK_NO_ADD_TO_DYN; + } + } + + /* The field is not in the tables or should not be added as an indexed + field */ + + /* Add the field literally */ + + if (mhd_HPACK_ENC_POL_NEVER_W_NAME_IDX <= enc_pol) + { + /* Add field literally as "never indexed" */ + const bool name_idx_stat_allowed = + (mhd_HPACK_ENC_POL_NEVER_W_NAME_IDX_STATIC >= enc_pol); + const bool name_idx_dyn_allowed = + (mhd_HPACK_ENC_POL_NEVER_W_NAME_IDX_STATIC > enc_pol); + const bool huffman_allowed = + (mhd_HPACK_ENC_POL_NEVER_W_NAME_LIT_NO_HUFFMAN > enc_pol); + if (! hpack_enc_field_literal (hk_enc, + name, + 0u, + value, + mhd_HPACK_ENC_LIT_IDX_TYPE_NEVER_INDEXING, + name_idx_stat_allowed, + name_idx_dyn_allowed, + huffman_allowed, + out_buff_size, + out_buff, + bytes_encoded)) + return mhd_ENC_RESULT_INT_NO_SPACE; + + return mhd_ENC_RESULT_INT_OK_NO_ADD_TO_DYN; + } + + if (mhd_HPACK_ENC_POL_AVOID_NEW_IDX <= enc_pol) + { + /* Adding to the tables is not allowed */ + mhd_assert (mhd_HPACK_ENC_POL_NOT_INDEXED >= enc_pol); + + if (! hpack_enc_field_literal (hk_enc, + name, + 0u, + value, + mhd_HPACK_ENC_LIT_IDX_TYPE_NOT_INDEXING, + true, + true, + true, + out_buff_size, + out_buff, + bytes_encoded)) + return mhd_ENC_RESULT_INT_NO_SPACE; + + return mhd_ENC_RESULT_INT_OK_NO_ADD_TO_DYN; + } + + if (mhd_HPACK_ENC_POL_ALWAYS_IF_FIT >= enc_pol) + { + bool add_to_idx; + if ((mhd_HPACK_ENC_POL_FORCED == enc_pol) || + (mhd_HPACK_ENC_POL_FORCED_NEW_IDX == enc_pol)) + add_to_idx = true; + else + add_to_idx = mhd_dtbl_check_entry_fit (hk_enc->dyn, + name->size, + value->size); + + if (! hpack_enc_field_literal (hk_enc, + name, + 0u, + value, + add_to_idx ? + mhd_HPACK_ENC_LIT_IDX_TYPE_INDEXING : + mhd_HPACK_ENC_LIT_IDX_TYPE_NOT_INDEXING, + true, + true, + true, + out_buff_size, + out_buff, + bytes_encoded)) + return mhd_ENC_RESULT_INT_NO_SPACE; + + return add_to_idx ? + mhd_ENC_RESULT_INT_OK_ADD_TO_DYN : + mhd_ENC_RESULT_INT_OK_NO_ADD_TO_DYN; + } + + /* Indexing or not indexing is not forced. + Need to decide whether to add the field to the index based on some + heuristics. + Use only field size and buffer data when deciding. Do not analyse the + field name or value (it should be performed by caller). */ + + mhd_assert (mhd_HPACK_ENC_POL_DESIRABLE <= enc_pol); + mhd_assert (mhd_HPACK_ENC_POL_LOWEST_PRIO >= enc_pol); + + if (1) /* For local scope */ + { + enum mhd_Tristate add_to_idx; + + add_to_idx = + mhd_dtbl_check_entry_fit (hk_enc->dyn, + name->size, + value->size) ? mhd_T_MAYBE : mhd_T_NO; + + /* The following algorithm is simplified and can be improved */ + + if (mhd_T_IS_MAYBE (add_to_idx)) + { + const size_t field_size = + name->size + value->size + mhd_dtbl_entry_overhead; + const size_t dyn_size = hk_enc->new_dyn_size; + const size_t dyn_used = mhd_dtbl_get_table_used (hk_enc->dyn); + const size_t dyn_free = dyn_size - dyn_used; + const size_t num_entries = mhd_dtbl_get_num_entries (hk_enc->dyn); + + mhd_assert (dyn_size >= dyn_used); + + if (512u > dyn_size) + { + /* Very small table, use very basic logic */ + add_to_idx = + (mhd_HPACK_ENC_POL_NEUTRAL >= enc_pol) ? mhd_T_YES : mhd_T_NO; + } + else if (mhd_HPACK_ENC_POL_DESIRABLE >= enc_pol) + { + mhd_assert (mhd_HPACK_ENC_POL_DESIRABLE == enc_pol); + if (field_size <= dyn_free) + add_to_idx = mhd_T_YES; + else if (field_size <= (dyn_size - dyn_size / 4)) + add_to_idx = mhd_T_YES; + else if (2u >= num_entries) + add_to_idx = mhd_T_YES; + else + add_to_idx = mhd_T_NO; + } + else if (mhd_HPACK_ENC_POL_NEUTRAL == enc_pol) + { + if (field_size <= dyn_free / 4) + add_to_idx = mhd_T_YES; + else if (field_size <= dyn_size / 32) + add_to_idx = mhd_T_YES; + else if ((field_size <= dyn_size / 4) + && ((field_size / 2) >= (dyn_used / num_entries))) + add_to_idx = mhd_T_YES; + else + add_to_idx = mhd_T_NO; + } + else if (mhd_HPACK_ENC_POL_LOW_PRIO == enc_pol) + { + if (field_size <= dyn_free / 16) + add_to_idx = mhd_T_YES; + else if (field_size <= dyn_size / 128) + add_to_idx = mhd_T_YES; + else + add_to_idx = mhd_T_NO; + } + else if (mhd_HPACK_ENC_POL_LOWEST_PRIO == enc_pol) + { + if (field_size <= dyn_free / 64) + add_to_idx = mhd_T_YES; + else if (field_size <= dyn_size / 512) + add_to_idx = mhd_T_YES; + else + add_to_idx = mhd_T_NO; + } + else + { + mhd_UNREACHABLE (); + add_to_idx = mhd_T_NO; + } + } + mhd_assert (mhd_T_IS_NOT_MAYBE (add_to_idx)); + + if (mhd_T_IS_YES (add_to_idx)) + { + if (! hpack_enc_field_literal (hk_enc, + name, + 0u, + value, + mhd_HPACK_ENC_LIT_IDX_TYPE_INDEXING, + true, + true, + true, + out_buff_size, + out_buff, + bytes_encoded)) + return mhd_ENC_RESULT_INT_NO_SPACE; + + return mhd_ENC_RESULT_INT_OK_ADD_TO_DYN; + } + } + + if (! hpack_enc_field_literal (hk_enc, + name, + 0u, + value, + mhd_HPACK_ENC_LIT_IDX_TYPE_NOT_INDEXING, + true, + true, + true, + out_buff_size, + out_buff, + bytes_encoded)) + return mhd_ENC_RESULT_INT_NO_SPACE; + + return mhd_ENC_RESULT_INT_OK_NO_ADD_TO_DYN; +} + + +/** + * Emit Dynamic Table Size Update representation(s) if needed. + * + * If the current dynamic table size differs from the pending minimal/final + * sizes accumulated in @a hk_enc, this function encodes one or two size + * updates, and performs local eviction down to the minimal size for + * consistency. + * + * @param[in,out] hk_enc the encoder context + * @param[in] out_buff_size the size of @a out_buff in bytes, + * could be zero + * @param[out] out_buff the output buffer to write encoded messages + * @param[out] bytes_encoded the output variable to be set to the number of + * bytes written + * @return 'true' on success; + * 'false' if the output buffer is too small + */ +static MHD_FN_PAR_OUT_SIZE_ (3,2) MHD_FN_PAR_OUT_ (4) bool +hpack_enc_check_dyn_size_update ( + struct mhd_HpackEncContext *restrict hk_enc, + size_t out_buff_size, + uint8_t *restrict out_buff, + size_t *restrict bytes_encoded) +{ + /** The prefix for Dynamic Table Size Update message */ + mhd_constexpr uint_fast8_t dyn_size_upd_msg_prfx = (uint_fast8_t) (1u << 5u); + mhd_constexpr uint_fast8_t dyn_size_upd_msg_prfx_bits = 3u; + size_t pos; + size_t pos_incr; + struct mhd_HpackDTblContext *restrict const dyn = hk_enc->dyn; + + mhd_assert (mhd_DTBL_MAX_SIZE >= hk_enc->smallest_dyn_size); + mhd_assert (mhd_DTBL_MAX_SIZE >= hk_enc->new_dyn_size); + mhd_assert (hk_enc->new_dyn_size >= hk_enc->smallest_dyn_size); + mhd_assert (mhd_dtbl_get_table_max_size (dyn) \ + >= hk_enc->smallest_dyn_size); + + if ((mhd_dtbl_get_table_max_size (dyn) == hk_enc->smallest_dyn_size) && + (hk_enc->new_dyn_size == hk_enc->smallest_dyn_size)) + { + *bytes_encoded = 0u; + return true; /* No resize signal needed */ + } + + /* Need to create a "Dynamic Table Size Update" signal */ + if (0u == out_buff_size) + return false; /* Not enough space */ + + pos = 0u; + + if (mhd_dtbl_get_table_max_size (dyn) != hk_enc->smallest_dyn_size) + { + /* Signal the minimal size so the peer evicts entries */ + pos_incr = + hpack_put_number_to_buf (dyn_size_upd_msg_prfx, + dyn_size_upd_msg_prfx_bits, + (uint_fast32_t) hk_enc->smallest_dyn_size, + out_buff_size, + out_buff); + + if (0u == pos_incr) + return false; /* Not enough space */ + + pos += pos_incr; + + mhd_dtbl_evict_to_size (dyn, + hk_enc->smallest_dyn_size); + } + + if (hk_enc->new_dyn_size != hk_enc->smallest_dyn_size) + { + if (pos == out_buff_size) + return false; /* Not enough space for the second resize message */ + + /* Signal the final dynamic table size */ + pos_incr = + hpack_put_number_to_buf (dyn_size_upd_msg_prfx, + dyn_size_upd_msg_prfx_bits, + (uint_fast32_t) hk_enc->new_dyn_size, + out_buff_size - pos, + out_buff + pos); + + if (0u == pos_incr) + return false; /* Not enough space */ + + pos += pos_incr; + } + + mhd_assert (0u != pos); + *bytes_encoded = pos; + return true; +} + + +/** + * Apply a pending Dynamic Table Size Update for the encoder. + * + * Resizes the dynamic table to @a hk_enc->new_dyn_size if needed and updates + * hk_enc data accordingly. + * + * @param[in,out] hk_enc the encoder context + * @return 'true' on success; + * 'false' on allocation error + */ +static bool +hpack_enc_perform_dyn_size_update (struct mhd_HpackEncContext *restrict hk_enc) +{ + if (mhd_dtbl_get_table_max_size (hk_enc->dyn) != hk_enc->new_dyn_size) + { + if (mhd_COND_HARDLY_EVER (! mhd_dtbl_resize (&(hk_enc->dyn), \ + hk_enc->new_dyn_size))) + return false; + + mhd_assert (mhd_dtbl_get_table_max_size (hk_enc->dyn) == \ + hk_enc->new_dyn_size); + } + + hk_enc->smallest_dyn_size = hk_enc->new_dyn_size; + + return true; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_INOUT_ (1) +MHD_FN_PAR_IN_ (2) MHD_FN_PAR_IN_ (3) +MHD_FN_PAR_OUT_SIZE_ (6,5) MHD_FN_PAR_OUT_ (7) enum mhd_HpackEncResult +mhd_hpack_enc_field (struct mhd_HpackEncContext *restrict hk_enc, + const struct mhd_BufferConst *restrict name, + const struct mhd_BufferConst *restrict value, + enum mhd_HpackEncPolicy enc_pol, + const size_t out_buff_size, + uint8_t *restrict out_buff, + size_t *restrict bytes_encoded) +{ + size_t pos; + size_t pos_incr; + enum mhd_HpackEncResultInternal enc_field_res; + + mhd_assert (0u != out_buff_size); + mhd_assert ((name->size & 0xFFFFFFFFu) == name->size); + mhd_assert ((value->size & 0xFFFFFFFFu) == value->size); + mhd_assert ((0u == name->size) || (':' != name->data[0])); + + pos = 0u; + + /* Add Dynamic Table Size Update message if needed */ + if (! hpack_enc_check_dyn_size_update (hk_enc, + out_buff_size - 1u, /* Reserve one byte for minimal field size */ + out_buff, + &pos_incr)) + return mhd_HPACK_ENC_BUFFER_TOO_SMALL; + + pos += pos_incr; + mhd_assert (pos < out_buff_size); + + enc_field_res = + hpack_enc_field (hk_enc, + name, + value, + enc_pol, + out_buff_size - pos, + out_buff + pos, + &pos_incr); + + if (mhd_ENC_RESULT_INT_NO_SPACE == enc_field_res) + return mhd_HPACK_ENC_BUFFER_TOO_SMALL; + + pos += pos_incr; + + /* Finally resize the dynamic table (if resize is pending) */ + if (! hpack_enc_perform_dyn_size_update (hk_enc)) + return mhd_HPACK_ENC_RES_ALLOC_ERR; + + /* Add the field (if needed) only after dynamic table resizing (if any) */ + if (mhd_ENC_RESULT_INT_OK_ADD_TO_DYN == enc_field_res) + mhd_dtbl_new_entry (hk_enc->dyn, + name->size, + name->data, + value->size, + value->data); + else + mhd_assert (mhd_ENC_RESULT_INT_OK_NO_ADD_TO_DYN == enc_field_res); + + *bytes_encoded = pos; + return mhd_HPACK_ENC_RES_OK; +} + + +/** + * Convert an HTTP status @a code to a three-character decimal string. + * + * @param code the status code; must be >= 100 and <= 699 + * @param[out] code_str destination buffer of exactly 3 bytes; + * receives the decimal digits of @a code + */ +mhd_static_inline +MHD_FN_PAR_OUT_ (2) void +status_to_str (uint_fast16_t code, + char code_str[3]) +{ + mhd_assert (100u <= code); + mhd_assert (699u >= code); + + code_str[0] = (char) ('0' + (char) (uint8_t) ((code / 100u) % 10)); + code_str[1] = (char) ('0' + (char) (uint8_t) ((code / 10u) % 10)); + code_str[2] = (char) ('0' + (char) (uint8_t) ((code / 1u) % 10)); +} + + +/** + * Pseudo-header ":status" name in the string form + */ +static const struct mhd_BufferConst pf_status_str = mhd_MSTR_INIT (":status"); + +/** + * Encode one pseudo-header ":status" according to the requested indexing + * policy. + * + * Chooses between indexed and literal representations based on table contents + * and the @a enc_pol policy, and decides whether to add the field to the + * dynamic table (using simple size-based heuristics when not explicitly + * forced). + * + * @param[in,out] hk_enc the encoder context + * @param[in] code the status code, must be >= 100 and <= 699 + * @param[in] enc_pol the encoding policy to apply + * @param[out] code_str where the string representation of the @a code + * to be written if literal encoding is used + * @param[in] out_buff_size the size of @a out_buff in bytes, + * must not be zero + * @param[out] out_buff the output buffer + * @param[out] bytes_encoded to be set to the number of bytes written to + * the @a out_buff + * @return #mhd_ENC_RESULT_INT_NO_SPACE on insufficient buffer; + * #mhd_ENC_RESULT_INT_OK_NO_ADD_TO_DYN or + * #mhd_ENC_RESULT_INT_OK_ADD_TO_DYN on success + */ +static MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_INOUT_ (1) +MHD_FN_PAR_OUT_ (4) +MHD_FN_PAR_OUT_SIZE_ (6,5) MHD_FN_PAR_OUT_ (7) enum mhd_HpackEncResultInternal +hpack_enc_pf_status (struct mhd_HpackEncContext *restrict hk_enc, + uint_fast16_t code, + enum mhd_HpackEncPFieldStatusPolicy enc_pol, + char code_str[3], + const size_t out_buff_size, + uint8_t *restrict out_buff, + size_t *restrict bytes_encoded) +{ + mhd_constexpr dtbl_idx_ft pf_status_first_idx = + mhd_HPACK_STBL_PF_STATUS_START_POS; + mhd_constexpr dtbl_idx_ft pf_status_200_idx = pf_status_first_idx + 0u; + mhd_constexpr dtbl_idx_ft pf_status_204_idx = pf_status_first_idx + 1u; + mhd_constexpr dtbl_idx_ft pf_status_206_idx = pf_status_first_idx + 2u; + mhd_constexpr dtbl_idx_ft pf_status_304_idx = pf_status_first_idx + 3u; + mhd_constexpr dtbl_idx_ft pf_status_400_idx = pf_status_first_idx + 4u; + mhd_constexpr dtbl_idx_ft pf_status_404_idx = pf_status_first_idx + 5u; + mhd_constexpr dtbl_idx_ft pf_status_500_idx = pf_status_first_idx + 6u; + struct mhd_BufferConst code_val; + + mhd_assert (14u == pf_status_500_idx); + + mhd_assert (0u != out_buff_size); + + /* Check the enum values order */ + // TODO: replace with static asserts + mhd_assert (mhd_HPACK_ENC_PFS_POL_ALWAYS_NEW_IDX_IF_FIT < \ + mhd_HPACK_ENC_PFS_POL_NORMAL); + mhd_assert (mhd_HPACK_ENC_PFS_POL_NORMAL < \ + mhd_HPACK_ENC_PFS_POL_AVOID_NEW_IDX); + mhd_assert (mhd_HPACK_ENC_PFS_POL_AVOID_NEW_IDX < \ + mhd_HPACK_ENC_PFS_POL_STATIC_IDX); + mhd_assert (mhd_HPACK_ENC_PFS_POL_STATIC_IDX < \ + mhd_HPACK_ENC_PFS_POL_NOT_INDEXED); + mhd_assert (mhd_HPACK_ENC_PFS_POL_NOT_INDEXED < \ + mhd_HPACK_ENC_PFS_POL_NEVER_W_NAME_IDX); + mhd_assert (mhd_HPACK_ENC_PFS_POL_NEVER_W_NAME_IDX < \ + mhd_HPACK_ENC_PFS_POL_NEVER_W_NAME_LIT_FORCED); + mhd_assert (mhd_HPACK_ENC_PFS_POL_NEVER_W_NAME_LIT_FORCED < \ + mhd_HPACK_ENC_PFS_POL_NEVER_W_NAME_LIT_NO_HUFFMAN); + + + if ((mhd_HPACK_ENC_PFS_POL_NORMAL <= enc_pol) + && (mhd_HPACK_ENC_PFS_POL_STATIC_IDX >= enc_pol)) + { + dtbl_idx_ft field_idx; + switch (code) + { + case 200u: + field_idx = pf_status_200_idx; + break; + case 204u: + field_idx = pf_status_204_idx; + break; + case 206u: + field_idx = pf_status_206_idx; + break; + case 304u: + field_idx = pf_status_304_idx; + break; + case 400u: + field_idx = pf_status_400_idx; + break; + case 404u: + field_idx = pf_status_404_idx; + break; + case 500u: + field_idx = pf_status_500_idx; + break; + default: + field_idx = 0u; + break; + } + + if (0u != field_idx) + { + if (! hpack_enc_field_indexed (field_idx, + out_buff_size, + out_buff, + bytes_encoded)) + return mhd_ENC_RESULT_INT_NO_SPACE; + + return mhd_ENC_RESULT_INT_OK_NO_ADD_TO_DYN; + } + } + + /* The pseudo-header is not in the static table or should not be added as an + indexed field */ + + /* Create a string representation of the code */ + status_to_str (code, code_str); + code_val.data = code_str; + code_val.size = 3u; + + if ((mhd_HPACK_ENC_PFS_POL_NORMAL <= enc_pol) + && (mhd_HPACK_ENC_PFS_POL_AVOID_NEW_IDX >= enc_pol)) + { + const dtbl_idx_ft field_idx = + mhd_dtbl_find_entry (hk_enc->dyn, + pf_status_str.size, + pf_status_str.data, + 3u, + code_str); + + if (0u != field_idx) + { + if (! hpack_enc_field_indexed (field_idx, + out_buff_size, + out_buff, + bytes_encoded)) + return mhd_ENC_RESULT_INT_NO_SPACE; + + return mhd_ENC_RESULT_INT_OK_NO_ADD_TO_DYN; + } + } + + /* The field is not in the tables or should not be added as an indexed + field */ + + /* Add the field literally */ + + if (mhd_HPACK_ENC_PFS_POL_NEVER_W_NAME_IDX <= enc_pol) + { + /* Add field literally as "never indexed" */ + const bool name_idx_stat_allowed = + (mhd_HPACK_ENC_PFS_POL_NEVER_W_NAME_IDX == enc_pol); + const bool huffman_allowed = + (mhd_HPACK_ENC_PFS_POL_NEVER_W_NAME_LIT_NO_HUFFMAN > enc_pol); + if (! hpack_enc_field_literal (hk_enc, + &pf_status_str, + name_idx_stat_allowed ? + pf_status_first_idx : 0u, + &code_val, + mhd_HPACK_ENC_LIT_IDX_TYPE_NEVER_INDEXING, + name_idx_stat_allowed, + false, + huffman_allowed, + out_buff_size, + out_buff, + bytes_encoded)) + return mhd_ENC_RESULT_INT_NO_SPACE; + + return mhd_ENC_RESULT_INT_OK_NO_ADD_TO_DYN; + } + + if (mhd_HPACK_ENC_PFS_POL_AVOID_NEW_IDX <= enc_pol) + { + /* Adding to the tables is not allowed */ + mhd_assert (mhd_HPACK_ENC_PFS_POL_NOT_INDEXED >= enc_pol); + + if (! hpack_enc_field_literal (hk_enc, + &pf_status_str, + pf_status_first_idx, + &code_val, + mhd_HPACK_ENC_LIT_IDX_TYPE_NOT_INDEXING, + true, + false, + true, + out_buff_size, + out_buff, + bytes_encoded)) + return mhd_ENC_RESULT_INT_NO_SPACE; + + return mhd_ENC_RESULT_INT_OK_NO_ADD_TO_DYN; + } + + mhd_assert (mhd_HPACK_ENC_PFS_POL_ALWAYS_NEW_IDX_IF_FIT <= enc_pol); + mhd_assert (mhd_HPACK_ENC_PFS_POL_NORMAL >= enc_pol); + + if (1) /* For local scope */ + { + const bool add_to_idx = + mhd_dtbl_check_entry_fit (hk_enc->dyn, + pf_status_str.size, + 3u); + + if (hpack_enc_field_literal (hk_enc, + &pf_status_str, + pf_status_first_idx, + &code_val, + add_to_idx ? + mhd_HPACK_ENC_LIT_IDX_TYPE_INDEXING : + mhd_HPACK_ENC_LIT_IDX_TYPE_NOT_INDEXING, + true, + false, + true, + out_buff_size, + out_buff, + bytes_encoded)) + return add_to_idx ? + mhd_ENC_RESULT_INT_OK_ADD_TO_DYN : + mhd_ENC_RESULT_INT_OK_NO_ADD_TO_DYN; + + } + + return mhd_ENC_RESULT_INT_NO_SPACE; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_INOUT_ (1) +MHD_FN_PAR_OUT_SIZE_ (5,4) MHD_FN_PAR_OUT_ (6) enum mhd_HpackEncResult +mhd_hpack_enc_ph_status (struct mhd_HpackEncContext *restrict hk_enc, + uint_fast16_t code, + enum mhd_HpackEncPFieldStatusPolicy enc_pol, + const size_t out_buff_size, + uint8_t *restrict out_buff, + size_t *restrict bytes_encoded) +{ + char code_str[3] = ""; + size_t pos; + size_t pos_incr; + enum mhd_HpackEncResultInternal enc_field_res; + + mhd_assert (0u != out_buff_size); + mhd_assert (100u <= code); + mhd_assert (699u >= code); + + pos = 0u; + + /* Add Dynamic Table Size Update message if needed */ + if (! hpack_enc_check_dyn_size_update (hk_enc, + out_buff_size - 1u, /* Reserve one byte for minimal field size */ + out_buff, + &pos_incr)) + return mhd_HPACK_ENC_BUFFER_TOO_SMALL; + + pos += pos_incr; + mhd_assert (pos < out_buff_size); + + enc_field_res = + hpack_enc_pf_status (hk_enc, + code, + enc_pol, + code_str, + out_buff_size, + out_buff, + &pos_incr); + + if (mhd_ENC_RESULT_INT_NO_SPACE == enc_field_res) + return mhd_HPACK_ENC_BUFFER_TOO_SMALL; + + pos += pos_incr; + + /* Finally resize the dynamic table (if resize is pending) */ + if (! hpack_enc_perform_dyn_size_update (hk_enc)) + return mhd_HPACK_ENC_RES_ALLOC_ERR; + + /* Add the field (if needed) only after dynamic table resizing (if any) */ + if (mhd_ENC_RESULT_INT_OK_ADD_TO_DYN == enc_field_res) + { + mhd_assert ('1' <= code_str[0]); + mhd_assert ('6' >= code_str[0]); + mhd_assert ('0' == code_str[1]); + mhd_dtbl_new_entry (hk_enc->dyn, + pf_status_str.size, + pf_status_str.data, + sizeof(code_str) / sizeof(char), + code_str); + } + else + mhd_assert (mhd_ENC_RESULT_INT_OK_NO_ADD_TO_DYN == enc_field_res); + + *bytes_encoded = pos; + return mhd_HPACK_ENC_RES_OK; +} + + +/* ****** _____________ End of HPACK headers encoding ______________ ****** */ + +#endif /* ! mhd_HPACK_TESTING_TABLES_ONLY || ! MHD_UNIT_TESTING */ diff --git a/src/mhd2/h2/hpack/mhd_hpack_codec.h b/src/mhd2/h2/hpack/mhd_hpack_codec.h @@ -0,0 +1,576 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2025 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/h2/hpack/mhd_hpack_codec.h + * @brief The declarations for HPACK header compression codec functions + * @author Karlson2k (Evgeny Grin) + * + * The sizes of all strings are intentionally limited to 32 bits (4GiB). + */ + +#ifndef MHD_HPACK_CODEC_H +#define MHD_HPACK_CODEC_H 1 + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" +#include "sys_base_types.h" + +#include "mhd_buffer.h" + +#ifndef mhd_HPACK_DTBL_BITS +# if ((SIZEOF_SIZE_T + 0) >= 4) && ((SIZEOF_VOIDP + 0) >= 4) +# define mhd_HPACK_DTBL_BITS 32 +# else +# define mhd_HPACK_DTBL_BITS 16 +# endif +#else /* mhd_HPACK_DTBL_BITS */ +# if (mhd_HPACK_DTBL_BITS != 16) && (mhd_HPACK_DTBL_BITS != 32) +#error Unsupported mhd_HPACK_DTBL_BITS value +# endif +#endif /* mhd_HPACK_DTBL_BITS */ + +/** + * @def mhd_DTBL_MAX_SIZE + * The maximum possible size of the dynamic table + */ +#if mhd_HPACK_DTBL_BITS == 32 +# define mhd_DTBL_MAX_SIZE (((size_t) 1u) * 1024u * 1024u) +#elif mhd_HPACK_DTBL_BITS == 16 +# define mhd_DTBL_MAX_SIZE (((size_t) 60u) * 1024u) +#endif + +struct mhd_HpackDecContext; /* forward declaration */ + +/** + * Initialise HPACK decoder context and create the dynamic table. + * @param hk_dec the decoder context to initialise + * @return 'true' on success, + * 'false' on allocation error + */ +MHD_INTERNAL bool +mhd_hpack_dec_init (struct mhd_HpackDecContext *hk_dec) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_OUT_ (1); + +/** + * Deinitialise HPACK decoder context and free the dynamic table if present. + * @param hk_dec the pointer to the decoder context + */ +MHD_INTERNAL void +mhd_hpack_dec_deinit (struct mhd_HpackDecContext *hk_dec) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_INOUT_ (1); + +/** + * Set the maximum allowed dynamic table size for the decoder. + * Should be called when the remote peer has ACKed settings with an updated + * dynamic table size. + * @param hk_dec the decoder context + * @param new_allowed_dyn_size new limit in bytes, + * must be <= #mhd_DTBL_MAX_SIZE + */ +MHD_INTERNAL void +mhd_hpack_dec_set_allowed_dyn_size (struct mhd_HpackDecContext *hk_dec, + size_t new_allowed_dyn_size) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_INOUT_ (1); + +/** + * Result codes of #mhd_hpack_dec_data() + */ +enum MHD_FIXED_ENUM_ mhd_HpackDecResult +{ + /** + * Success, no new field decoded. + * Could be decoding of a Dynamic Table Size Update message. + */ + mhd_HPACK_DEC_RES_NO_NEW_FIELD = -1 + , + /** + * Success, new field decoded. + */ + mhd_HPACK_DEC_RES_NEW_FIELD = 0 + , + /** + * The encoded data is incomplete. More data needed. + */ + mhd_HPACK_DEC_RES_INCOMPLETE = 1 + , + /** + * Memory allocation error when resizing the dynamic table. + */ + mhd_HPACK_DEC_RES_ALLOC_ERR + , + /** + * The output buffer is too small for the decoded field. + */ + mhd_HPACK_DEC_RES_BUFFER_TOO_SMALL + , + /** + * The length of the field strings is too long for this code (> 4GiB). + */ + mhd_HPACK_DEC_RES_STRING_TOO_LONG + , + /** + * The length of the number in the encoded form is too long. + * The encoded number used more bytes than needed to encode 64-bit nu + */ + mhd_HPACK_DEC_RES_NUMBER_TOO_LONG + , + /** + * Received a Dynamic Table Size Update message with a size larger than + * allowed. + */ + mhd_HPACK_DEC_RES_DYN_SIZE_UPD_TOO_LARGE + , + /** + * The remote peer did not send the expected Dynamic Table Size + * Update. + */ + mhd_HPACK_DEC_RES_DYN_SIZE_UPD_MISSING + , + /** + * Huffman decoding error + */ + mhd_HPACK_DEC_RES_HUFFMAN_ERR + , + /** + * Field (header) index specified in HPACK data does not exist. + */ + mhd_HPACK_DEC_RES_HPACK_BAD_IDX + , + /** + * Other HPACK decoding errors + */ + mhd_HPACK_DEC_RES_HPACK_ERR + , + /** + * Internal error. + * Should never happen. + */ + mhd_HPACK_DEC_RES_INTERNAL_ERR +}; + +/** + * Check whether the @a dec_res is a kind of error code. + * @param dec_res result code returned by #mhd_hpack_dec_data() + * @return boolean 'true' if @a dec_res denotes an error; + * boolean 'false' otherwise + */ +#define mhd_HPACK_DEC_RES_IS_ERR(dec_res) \ + (mhd_HPACK_DEC_RES_NEW_FIELD < (dec_res)) + + +/** + * Decode a single HPACK representation (indexed, literal, or size + * update). + * For header fields, writes "name\0value\0" to @a out_buff. + * @param hk_dec the decoder context + * @param enc_data_size the size of @a enc_data + * @param enc_data the encoded data + * @param out_buff_size the size of @a out_buff + * @param[out] out_buff the output buffer for the decoded strings + * @param[out] name_len to be set to the length of the name, not counting + * zero-terminating + * @param[out] val_len to be set to the length of the value, not counting + * zero-terminating + * @param[out] bytes_decoded to be set to the number of decoded bytes + * @return #mhd_HPACK_DEC_RES_NEW_FIELD if new field successfully + * decoded and placed into + * @a out_buff, + * #mhd_HPACK_DEC_RES_NO_NEW_FIELD if chunk of data + * successfully decoded, but + * no new field added, + * error code otherwise + */ +MHD_INTERNAL enum mhd_HpackDecResult +mhd_hpack_dec_data (struct mhd_HpackDecContext *restrict hk_dec, + const size_t enc_data_size, + const uint8_t *restrict enc_data, + size_t out_buff_size, + char *restrict out_buff, + size_t *restrict name_len, + size_t *restrict val_len, + size_t *restrict bytes_decoded) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_INOUT_(1) +MHD_FN_PAR_IN_SIZE_(3, 2) +MHD_FN_PAR_OUT_SIZE_(5, 4) +MHD_FN_PAR_OUT_(6) MHD_FN_PAR_OUT_(7) MHD_FN_PAR_OUT_ (8); + + +struct mhd_HpackEncContext; /* forward declaration */ + +/** + * Initialise HPACK encoder context and create the dynamic table. + * @param hk_enc the encoder context to initialise + * @return 'true' on success, + * 'false' on allocation error + */ +MHD_INTERNAL bool +mhd_hpack_enc_init (struct mhd_HpackEncContext *hk_enc) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_OUT_ (1); + +/** + * Deinitialise HPACK encoder context and free the dynamic table if + * present. + * @param hk_enc the encoder context + */ +MHD_INTERNAL void +mhd_hpack_enc_deinit (struct mhd_HpackEncContext *hk_enc) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_INOUT_ (1); + +/** + * Set the current maximum dynamic table size for the encoder. + * + * @param hk_enc the encoder context + * @param new_dyn_size the new limit in bytes, + * must be <= #mhd_DTBL_MAX_SIZE and must be within + * the limits expected by the remote peer + */ +MHD_INTERNAL void +mhd_hpack_enc_set_dyn_size (struct mhd_HpackEncContext *hk_enc, + size_t new_dyn_size) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_INOUT_ (1); + + +/** + * Preference for HPACK encoding + * + * The items are sorted, keep them in this order. + */ +enum MHD_FIXED_ENUM_ mhd_HpackEncPolicy +{ + /** + * Force the field to be encoded literally and added to the dynamic + * table. If the field does not fit the dynamic table, the dynamic + * table is completely evicted without adding the new field. + * The name of the field can be encoded as a reference to the name + * in the static or dynamic table. + */ + mhd_HPACK_ENC_POL_FORCED_NEW_IDX + , + /** + * Encode the field literally; if the field fits the dynamic table, + * add it to the dynamic table as a new index even if the same field + * is already in the tables. If the field does not fit the dynamic + * table, it is encoded literally without adding it to the table. + * The name of the field can be encoded as a reference to the name + * in the static or dynamic table. + */ + mhd_HPACK_ENC_POL_ALWAYS_NEW_IDX_IF_FIT + , + /** + * Allow field encoding as a reference to an indexed field in the + * static or dynamic table; if the field is not in the tables and + * the field does not fit the dynamic table, the dynamic table is + * completely evicted without adding the new field. + * When the field is not in the tables, the name of the field can be + * encoded as a reference to the name in the static or dynamic + * table. + */ + mhd_HPACK_ENC_POL_FORCED + , + /** + * Allow field encoding as a reference to an indexed field in the + * static or dynamic table; if the field is not in the tables, + * always add the field to the dynamic table if it fits the table. + * When the field is not in the tables, the name of the field can be + * encoded as a reference to the name in the static or dynamic + * table. + */ + mhd_HPACK_ENC_POL_ALWAYS_IF_FIT + , + /** + * Allow field encoding as a reference to an indexed field in the + * static or dynamic table; if the field is not in the tables, + * prefer adding the field to the dynamic table if possible. + * When the field is not in the tables, the name of the field can be + * encoded as a reference to the name in the static or dynamic + * table. + */ + mhd_HPACK_ENC_POL_DESIRABLE + , + /** + * Allow field encoding as a reference to an indexed field in the + * static or dynamic table; if the field is not in the tables, use + * a neutral preference regarding adding the field to the dynamic + * table. + * When the field is not in the tables, the name of the field can be + * encoded as a reference to the name in the static or dynamic + * table. + */ + mhd_HPACK_ENC_POL_NEUTRAL + , + /** + * Allow field encoding as a reference to an indexed field in the + * static or dynamic table; if the field is not in the tables, + * consider adding the field to the dynamic table only with low + * priority (the encoder may choose not to add it). + * When the field is not in the tables, the name of the field can be + * encoded as a reference to the name in the static or dynamic + * table. + */ + mhd_HPACK_ENC_POL_LOW_PRIO + , + /** + * Allow field encoding as a reference to an indexed field in the + * static or dynamic table; if the field is not in the tables, + * consider adding the field to the dynamic table only with the + * lowest priority (the encoder is expected to avoid adding it in + * most cases). + * When the field is not in the tables, the name of the field can be + * encoded as a reference to the name in the static or dynamic + * table. + */ + mhd_HPACK_ENC_POL_LOWEST_PRIO + , + /** + * Allow field encoding as a reference to an indexed field in the + * static or dynamic table; if the field is not in the tables already, + * avoid adding the field to the dynamic table. + * When the field is not in the tables, the name of the field can be + * encoded as a reference to the name in the static or dynamic + * table. + */ + mhd_HPACK_ENC_POL_AVOID_NEW_IDX + , + /** + * Use field literal encoding without indexing. + * The name of the field can still be encoded as a reference to the + * name in the static or dynamic table. + */ + mhd_HPACK_ENC_POL_NOT_INDEXED + , + /** + * Use field literal encoding without indexing, with an additional + * "never indexed" mark to signal intermediaries to avoid using it + * as an indexed field when re-encoding the message. + * The name of the field can still be encoded as a reference to the + * name in the static or dynamic table. + */ + mhd_HPACK_ENC_POL_NEVER_W_NAME_IDX + , + /** + * Use field literal encoding without indexing, with an additional + * "never indexed" mark to signal intermediaries to avoid using it + * as an indexed field when re-encoding the message. + * The name of the field can be encoded as a reference to the name + * in the static table. + */ + mhd_HPACK_ENC_POL_NEVER_W_NAME_IDX_STATIC + , + /** + * Use field literal encoding without indexing, with an additional + * "never indexed" mark to signal intermediaries to avoid using it + * as an indexed field when re-encoding the message. + * The name of the field is always encoded literally. + */ + mhd_HPACK_ENC_POL_NEVER_W_NAME_LIT_FORCED + , + /** + * Use field literal encoding without indexing, with an additional + * "never indexed" mark to signal intermediaries to avoid using it + * as an indexed field when re-encoding the message. + * The name of the field is always encoded literally, and Huffman + * encoding is not used for the name or the value of the field. + */ + mhd_HPACK_ENC_POL_NEVER_W_NAME_LIT_NO_HUFFMAN +}; + + +/** + * Result codes of field encoding + */ +enum MHD_FIXED_ENUM_ mhd_HpackEncResult +{ + /** + * The field has been encoded successfully + */ + mhd_HPACK_ENC_RES_OK = 0 + , + /** + * The output buffer is too small to fit the data + */ + mhd_HPACK_ENC_BUFFER_TOO_SMALL + , + /** + * Error allocating memory when resizing the dynamic table + */ + mhd_HPACK_ENC_RES_ALLOC_ERR +}; + +/** + * Encode a single HPACK field. + * + * May emit a Dynamic Table Size Update representation first if the encoder + * has a pending size change. Encodes the field using indexed or literal + * representation according to @a enc_pol and the state of the static/dynamic + * tables. On success, the number of bytes written is stored in + * @a bytes_encoded. + * + * @param hk_enc the encoder context + * @param name the field name, must be a "real" header, + * must not start with the ':' character + * @param value the field value + * @param enc_pol the encoding policy to apply + * @param out_buff_size the size of @a out_buff in bytes + * @param[out] out_buff the output buffer to receive the encoded data + * @param[out] bytes_encoded set to the number of bytes written to @a out_buff + * @return #mhd_HPACK_ENC_RES_OK on success, + * #mhd_HPACK_ENC_BUFFER_TOO_SMALL if the output buffer is too small, + * #mhd_HPACK_ENC_RES_ALLOC_ERR on dynamic table allocation error + */ +MHD_INTERNAL enum mhd_HpackEncResult +mhd_hpack_enc_field (struct mhd_HpackEncContext *restrict hk_enc, + const struct mhd_BufferConst *restrict name, + const struct mhd_BufferConst *restrict value, + enum mhd_HpackEncPolicy enc_pol, + const size_t out_buff_size, + uint8_t *restrict out_buff, + size_t *restrict bytes_encoded) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_INOUT_(1) +MHD_FN_PAR_IN_(2) MHD_FN_PAR_IN_(3) +MHD_FN_PAR_OUT_SIZE_(6,5) MHD_FN_PAR_OUT_ (7); + + +/** + * Preference for HPACK encoding of pseudo-header ":status" + * + * The items are sorted, keep them in this order. + */ +enum MHD_FIXED_ENUM_ mhd_HpackEncPFieldStatusPolicy +{ + /** + * Encode the pseudo-header literally; if the pseudo-header fits the dynamic + * table, add it to the dynamic table as a new index even if the same + * pseudo-header is already in the tables. If the pseudo-header does not fit + * the dynamic table, it is encoded literally without adding it to the + * table. + * The name of the pseudo-header is encoded as a reference to the name + * in the static table. + */ + mhd_HPACK_ENC_PFS_POL_ALWAYS_NEW_IDX_IF_FIT + , + /** + * Allow pseudo-header encoding as a reference to an indexed pseudo-header in + * the static or dynamic table; if the pseudo-header is not in the tables, + * add the pseudo-header to the dynamic table if possible. + * When the pseudo-header is not in the tables, the name of the pseudo-header + * is encoded as a reference to the name in the static table. + */ + mhd_HPACK_ENC_PFS_POL_NORMAL + , + /** + * Allow pseudo-header encoding as a reference to an indexed pseudo-header in + * the static or dynamic table; if the pseudo-header is not in the tables + * already, avoid adding the pseudo-header to the dynamic table. + * When the pseudo-header is not in the tables, the name of the pseudo-header + * is encoded as a reference to the name in the static table. + */ + mhd_HPACK_ENC_PFS_POL_AVOID_NEW_IDX + , + /** + * Allow pseudo-header encoding as a reference to an indexed pseudo-header in + * the static table only; if the pseudo-header is not in the static table, + * encode it literally, without adding to the dynamic table. + * When the pseudo-header is not in the static table, the name of the + * pseudo-header is encoded as a reference to the name in the static table. + */ + mhd_HPACK_ENC_PFS_POL_STATIC_IDX + , + /** + * Use pseudo-header literal encoding without indexing. + * The name of the pseudo-header is encoded as a reference to the name in + * the static table. + */ + mhd_HPACK_ENC_PFS_POL_NOT_INDEXED + , + /** + * Use pseudo-header literal encoding without indexing, with an additional + * "never indexed" mark to signal intermediaries to avoid using it + * as an indexed pseudo-header when re-encoding the message. + * The name of the pseudo-header is encoded as a reference to the name in + * the static table. + */ + mhd_HPACK_ENC_PFS_POL_NEVER_W_NAME_IDX + , + /** + * Use pseudo-header literal encoding without indexing, with an additional + * "never indexed" mark to signal intermediaries to avoid using it + * as an indexed pseudo-header when re-encoding the message. + * The name of the pseudo-header is always encoded literally. + */ + mhd_HPACK_ENC_PFS_POL_NEVER_W_NAME_LIT_FORCED + , + /** + * Use pseudo-header literal encoding without indexing, with an additional + * "never indexed" mark to signal intermediaries to avoid using it + * as an indexed pseudo-header when re-encoding the message. + * The name of the pseudo-header is always encoded literally, and Huffman + * encoding is not used for the name or the value of the pseudo-header. + */ + mhd_HPACK_ENC_PFS_POL_NEVER_W_NAME_LIT_NO_HUFFMAN +}; + +/** + * Encode a single HPACK pseudo-header ":status". + * + * May emit a Dynamic Table Size Update representation first if the encoder + * has a pending size change. + * On success, the number of bytes written is stored in @a bytes_encoded. + * + * @param hk_enc the encoder context + * @param code the status code, must be >= 100 and <= 699 + * @param enc_pol the encoding policy to apply + * @param out_buff_size the size of @a out_buff in bytes + * @param[out] out_buff the output buffer to receive the encoded data + * @param[out] bytes_encoded set to the number of bytes written to @a out_buff + * @return #mhd_HPACK_ENC_RES_OK on success, + * #mhd_HPACK_ENC_BUFFER_TOO_SMALL if the output buffer is too small, + * #mhd_HPACK_ENC_RES_ALLOC_ERR on dynamic table allocation error + */ +MHD_INTERNAL enum mhd_HpackEncResult +mhd_hpack_enc_ph_status (struct mhd_HpackEncContext *restrict hk_enc, + uint_fast16_t code, + enum mhd_HpackEncPFieldStatusPolicy enc_pol, + const size_t out_buff_size, + uint8_t *restrict out_buff, + size_t *restrict bytes_encoded) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_INOUT_(1) +MHD_FN_PAR_OUT_SIZE_(5,4) MHD_FN_PAR_OUT_ (6); + +#endif /* ! MHD_HPACK_CODEC_H */ diff --git a/src/mhd2/h2/hpack/mhd_hpack_dec_types.h b/src/mhd2/h2/hpack/mhd_hpack_dec_types.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2025 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/h2/hpack/mhd_hpack_dec_types.h + * @brief The definition of HPACK decoding context data + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_HPACK_DEC_TYPES_H +#define MHD_HPACK_DEC_TYPES_H 1 + +#include "mhd_sys_options.h" + +#include "sys_sizet_type.h" + +struct mhd_HpackDTblContext; /* forward declaration */ + +/** + * HPACK decoder context + */ +struct mhd_HpackDecContext +{ + /** + * Dynamic table data + */ + struct mhd_HpackDTblContext *dyn; + + /** + * The maximum allowed size of the dynamic table + */ + size_t max_allowed_dyn_size; + + /** + * The last value set by Dynamic Table Size Update signal. + * Set initially to the default size of the dynamic table. + */ + size_t last_remote_dyn_size; +}; + +#endif /* ! MHD_HPACK_DEC_TYPES_H */ diff --git a/src/mhd2/h2/hpack/mhd_hpack_enc_types.h b/src/mhd2/h2/hpack/mhd_hpack_enc_types.h @@ -0,0 +1,85 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2025 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/h2/hpack/mhd_hpack_enc_types.h + * @brief The definition of HPACK encoding context data + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_HPACK_ENC_TYPES_H +#define MHD_HPACK_ENC_TYPES_H 1 + +#include "mhd_sys_options.h" + +#include "sys_sizet_type.h" + +struct mhd_HpackDTblContext; /* forward declaration */ + +/** + * HPACK encoder context + */ +struct mhd_HpackEncContext +{ + /** + * Dynamic table data + */ + struct mhd_HpackDTblContext *dyn; + + /** + * The latest set dynamic table maximum size. + * If it is different from the current size set in the @a dyn, then the + * Dynamic Table Size Update message will be added automatically before the + * first encoded header. + * Set initially to the default size of the dynamic table. + */ + size_t new_dyn_size; + + /** + * The smallest dynamic table size used after the last Dynamic Table Size + * Update message. + * If this value is different from the current size set in the @a dyn, then + * two Dynamic Table Size Update messages will be added automatically before + * the first encoded header (first message with the minimal size and the + * second message with the final size). + * Set initially to the default size of the dynamic table. + */ + size_t smallest_dyn_size; +}; + +#endif /* ! MHD_HPACK_ENC_TYPES_H */