mirror of
https://github.com/stefanocasazza/ULib.git
synced 2025-09-28 19:05:55 +08:00
541 lines
17 KiB
C++
541 lines
17 KiB
C++
// ============================================================================
|
|
//
|
|
// = LIBRARY
|
|
// ULib - c++ library
|
|
//
|
|
// = FILENAME
|
|
// http2.h - HTTP/2 utility
|
|
//
|
|
// = AUTHOR
|
|
// Stefano Casazza
|
|
//
|
|
// ============================================================================
|
|
|
|
#ifndef ULIB_HTTP2_H
|
|
#define ULIB_HTTP2_H 1
|
|
|
|
#include <ulib/net/ipaddress.h>
|
|
#include <ulib/utility/uhttp.h>
|
|
#include <ulib/utility/hexdump.h>
|
|
|
|
#define HTTP2_FRAME_HEADER_SIZE 9 // The number of bytes of the frame header
|
|
#define HTTP2_HEADER_TABLE_ENTRY_SIZE_OFFSET 32
|
|
|
|
#define HTTP2_CONNECTION_PREFACE "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" // (24 bytes)
|
|
|
|
class UHTTP;
|
|
class UClientImage_Base;
|
|
|
|
class U_EXPORT UHTTP2 {
|
|
public:
|
|
|
|
struct Stream {
|
|
uint32_t id,
|
|
state;
|
|
};
|
|
|
|
enum StreamState {
|
|
STREAM_STATE_IDLE = 0x000,
|
|
STREAM_STATE_RESERVED = 0x001,
|
|
STREAM_STATE_OPEN = 0x002,
|
|
STREAM_STATE_HALF_CLOSED = 0x004,
|
|
STREAM_STATE_CLOSED = 0x008
|
|
};
|
|
|
|
struct Settings {
|
|
uint32_t header_table_size,
|
|
enable_push,
|
|
max_concurrent_streams,
|
|
initial_window_size,
|
|
max_frame_size,
|
|
max_header_list_size;
|
|
};
|
|
|
|
enum SettingsId {
|
|
HEADER_TABLE_SIZE = 1,
|
|
ENABLE_PUSH = 2,
|
|
MAX_CONCURRENT_STREAMS = 3,
|
|
INITIAL_WINDOW_SIZE = 4,
|
|
MAX_FRAME_SIZE = 5,
|
|
MAX_HEADER_LIST_SIZE = 6
|
|
};
|
|
|
|
struct HpackHeaderTableEntry {
|
|
const UString* name;
|
|
const UString* value;
|
|
};
|
|
|
|
struct HpackDynamicTable {
|
|
uint32_t num_entries,
|
|
entry_capacity,
|
|
entry_start_index;
|
|
// size and capacities are 32+name_len+value_len (as defined by hpack spec)
|
|
uint32_t hpack_size,
|
|
hpack_capacity, // the value set by SETTINGS_HEADER_TABLE_SIZE _and_ dynamic table size update
|
|
hpack_max_capacity; // the value set by SETTINGS_HEADER_TABLE_SIZE
|
|
HpackHeaderTableEntry* entries; // ring buffer
|
|
};
|
|
|
|
enum ConnectionState {
|
|
CONN_STATE_IDLE = 0x000,
|
|
CONN_STATE_OPEN = 0x001,
|
|
CONN_STATE_IS_CLOSING = 0x002
|
|
};
|
|
|
|
class Connection {
|
|
public:
|
|
|
|
// Check for memory error
|
|
U_MEMORY_TEST
|
|
|
|
// Allocator e Deallocator
|
|
U_MEMORY_ALLOCATOR
|
|
U_MEMORY_DEALLOCATOR
|
|
|
|
int32_t out_window, // sender flow control window (its size is a measure of the buffering capacity of the sender)
|
|
inp_window; // receiver flow control window (its size is a measure of the buffering capacity of the receiver)
|
|
ConnectionState state; // state
|
|
Settings peer_settings; // settings
|
|
UHashMap<UString> itable; // headers request
|
|
HpackDynamicTable idyntbl, odyntbl; // hpack dynamic table (request, response)
|
|
// streams
|
|
uint32_t max_open_stream_id,
|
|
max_processed_stream_id;
|
|
Stream streams[100];
|
|
#ifdef DEBUG
|
|
UHashMap<UString> dtable;
|
|
HpackDynamicTable ddyntbl;
|
|
#endif
|
|
|
|
// COSTRUTTORI
|
|
|
|
Connection();
|
|
~Connection()
|
|
{
|
|
U_TRACE_UNREGISTER_OBJECT(0, Connection)
|
|
}
|
|
|
|
// SERVICES
|
|
|
|
static void preallocate(uint32_t max_connection)
|
|
{
|
|
U_TRACE(0+256, "UHTTP2::Connection::preallocate(%u)", max_connection)
|
|
|
|
U_INTERNAL_ASSERT_EQUALS(vConnection, 0)
|
|
|
|
U_INTERNAL_DUMP("sizeof(Connection) = %u sizeof(Stream) = %u", sizeof(Connection), sizeof(Stream))
|
|
|
|
vConnection = new Connection[max_connection];
|
|
}
|
|
|
|
// DEBUG
|
|
|
|
#if defined(U_STDCPP_ENABLE) && defined(DEBUG)
|
|
const char* dump(bool reset) const U_EXPORT;
|
|
#endif
|
|
|
|
private:
|
|
#ifdef U_COMPILER_DELETE_MEMBERS
|
|
Connection(const Connection&) = delete;
|
|
Connection& operator=(const Connection&) = delete;
|
|
#else
|
|
Connection(const Connection&) {}
|
|
Connection& operator=(const Connection&) { return *this; }
|
|
#endif
|
|
};
|
|
|
|
protected:
|
|
enum FrameTypesId {
|
|
DATA = 0x00,
|
|
HEADERS = 0x01,
|
|
PRIORITY = 0x02,
|
|
RST_STREAM = 0x03,
|
|
SETTINGS = 0x04,
|
|
PUSH_PROMISE = 0x05,
|
|
PING = 0x06,
|
|
GOAWAY = 0x07,
|
|
WINDOW_UPDATE = 0x08,
|
|
CONTINUATION = 0x09
|
|
};
|
|
|
|
enum FrameFlagsId {
|
|
FLAG_NONE = 0x00,
|
|
FLAG_ACK = 0x01, // 1
|
|
FLAG_END_STREAM = 0x01, // 1
|
|
FLAG_END_HEADERS = 0x04, // 100
|
|
FLAG_PADDED = 0x08, // 1000
|
|
FLAG_PRIORITY = 0x20 // 100000
|
|
};
|
|
|
|
enum FrameErrorCode { // The status codes for the RST_STREAM and GOAWAY frames
|
|
NO_ERROR = 0x00,
|
|
PROTOCOL_ERROR = 0x01,
|
|
INTERNAL_ERROR = 0x02,
|
|
FLOW_CONTROL_ERROR = 0x03,
|
|
SETTINGS_TIMEOUT = 0x04,
|
|
STREAM_CLOSED = 0x05,
|
|
FRAME_SIZE_ERROR = 0x06,
|
|
REFUSED_STREAM = 0x07,
|
|
CANCEL = 0x08,
|
|
COMPRESSION_ERROR = 0x09,
|
|
CONNECT_ERROR = 0x0a,
|
|
ENHANCE_YOUR_CALM = 0x0b,
|
|
INADEQUATE_SECURITY = 0x0c,
|
|
HTTP_1_1_REQUIRED = 0x0d,
|
|
ERROR_NOCLOSE = 0xfe,
|
|
ERROR_INCOMPLETE = 0xff
|
|
};
|
|
|
|
/**
|
|
* All frames begin with a fixed 9-octet header followed by a variable-length payload
|
|
*
|
|
* 0 1 2 3
|
|
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
|
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
* | Length (24) |
|
|
* +---------------+---------------+---------------+
|
|
* | Type (8) | Flags (8) |
|
|
* +-+-+-----------+---------------+-------------------------------+
|
|
* |R| Stream Identifier (31) |
|
|
* +=+=============================================================+
|
|
* | Frame Payload (0...) ...
|
|
* +---------------------------------------------------------------+
|
|
*/
|
|
|
|
struct FrameHeader {
|
|
unsigned char* payload;
|
|
int32_t length, // The length field of this frame, excluding frame header
|
|
stream_id; // The stream identifier (aka, stream ID)
|
|
uint8_t type, // The type of this frame
|
|
flags;
|
|
};
|
|
|
|
static int nerror;
|
|
static Stream* pStream;
|
|
static FrameHeader frame;
|
|
static Connection* vConnection;
|
|
static Connection* pConnection;
|
|
static const Settings settings;
|
|
static const char* upgrade_settings;
|
|
static bool settings_ack, continue100;
|
|
|
|
static uint32_t hash_static_table[61];
|
|
static HpackHeaderTableEntry hpack_static_table[61];
|
|
|
|
// SERVICES
|
|
|
|
static void ctor();
|
|
static void dtor()
|
|
{
|
|
U_TRACE_NO_PARAM(0, "UHTTP2::dtor()")
|
|
|
|
delete[] vConnection;
|
|
}
|
|
|
|
static void resetDataRead()
|
|
{
|
|
U_TRACE_NO_PARAM(0, "UHTTP2::resetDataRead()")
|
|
|
|
UClientImage_Base::rstart = 0;
|
|
|
|
UClientImage_Base::rbuffer->setEmptyForce();
|
|
|
|
if (USocketExt::read(UServer_Base::csocket, *UClientImage_Base::rbuffer, U_SINGLE_READ, UServer_Base::timeoutMS, UHTTP::request_read_timeout) == false) nerror = PROTOCOL_ERROR;
|
|
}
|
|
|
|
static void setEncoding(const UString& x)
|
|
{
|
|
U_TRACE(0, "UHTTP2::setEncoding(%V)", x.rep)
|
|
|
|
U_INTERNAL_ASSERT_EQUALS(U_http_is_accept_gzip, 0)
|
|
|
|
U_INTERNAL_DUMP("Accept-Encoding: = %V", x.rep)
|
|
|
|
U_INTERNAL_ASSERT(u_find(x.data(), 30, U_CONSTANT_TO_PARAM("gzip")))
|
|
|
|
U_http_is_accept_gzip = '1';
|
|
}
|
|
|
|
static void setURI(const char* ptr, uint32_t len)
|
|
{
|
|
U_TRACE(0, "UHTTP2::setURI(%.*S,%u)", len, ptr, len)
|
|
|
|
U_INTERNAL_ASSERT_EQUALS(U_http_info.uri_len, 0)
|
|
|
|
U_http_info.query = (const char*) memchr((U_http_info.uri = ptr), '?', (U_http_info.uri_len = len));
|
|
|
|
U_INTERNAL_DUMP("URI = %.*S", U_HTTP_URI_TO_TRACE)
|
|
|
|
if (U_http_info.query)
|
|
{
|
|
U_http_info.query_len = U_http_info.uri_len - (U_http_info.query++ - U_http_info.uri);
|
|
|
|
U_http_info.uri_len -= U_http_info.query_len;
|
|
|
|
U_INTERNAL_DUMP("query = %.*S", U_HTTP_QUERY_TO_TRACE)
|
|
}
|
|
}
|
|
|
|
static void readFrame();
|
|
static void openStream();
|
|
static void manageData();
|
|
static void sendGoAway();
|
|
static void manageHeaders();
|
|
static int handlerRequest();
|
|
static void handlerResponse();
|
|
static bool readBodyRequest();
|
|
static void reset(uint32_t idx, bool bsocket_open);
|
|
static bool updateSetting(unsigned char* ptr, uint32_t len);
|
|
|
|
static const char* getFrameErrorCodeDescription(uint32_t error);
|
|
|
|
#ifdef DEBUG
|
|
static bool bdecodeHeadersDebug;
|
|
|
|
static bool findHeader(uint32_t index)
|
|
{
|
|
U_TRACE(0, "UHTTP2::findHeader(%u)", index)
|
|
|
|
UHashMap<UString>* table = &(pConnection->dtable);
|
|
|
|
table->hash = hash_static_table[index];
|
|
|
|
table->lookup(hpack_static_table[index].name->rep);
|
|
|
|
if (table->node) U_RETURN(true);
|
|
|
|
U_RETURN(false);
|
|
}
|
|
|
|
# ifdef U_STDCPP_ENABLE
|
|
friend ostream& operator<<(ostream& _os, const HpackDynamicTable& v);
|
|
|
|
static void saveHpackDynamicTable(HpackDynamicTable* table)
|
|
{
|
|
U_TRACE(0+256, "UHTTP2::saveHpackDynamicTable(%p)", table)
|
|
|
|
char buffer[2 * 1024 * 1024];
|
|
|
|
uint32_t len = UObject2String(*table, buffer, sizeof(buffer));
|
|
|
|
U_INTERNAL_ASSERT_MINOR(len, sizeof(buffer))
|
|
|
|
(void) UFile::writeToTmp(buffer, len, false, "dtable.hpack.%P", 0);
|
|
}
|
|
# endif
|
|
|
|
static void decodeHeadersResponse()
|
|
{
|
|
U_TRACE_NO_PARAM(0, "UHTTP2::decodeHeadersResponse()")
|
|
|
|
bdecodeHeadersDebug = true;
|
|
|
|
char buffer[2 * 1024 * 1024];
|
|
const char* start = UClientImage_Base::wbuffer->c_pointer(HTTP2_FRAME_HEADER_SIZE);
|
|
|
|
decodeHeaders(&(pConnection->dtable),
|
|
&(pConnection->ddyntbl), (unsigned char*)start, (unsigned char*)(start + (ntohl(*(uint32_t*)UClientImage_Base::wbuffer->data() & 0x00ffffff) >> 8)));
|
|
|
|
bdecodeHeadersDebug = false;
|
|
|
|
uint32_t len = UObject2String(pConnection->dtable, buffer, sizeof(buffer));
|
|
|
|
U_INTERNAL_ASSERT_MINOR(len, sizeof(buffer))
|
|
|
|
(void) UFile::writeToTmp(buffer, len, false, "response.hpack.%P", 0);
|
|
|
|
U_ASSERT(findHeader( 7)) // UString::str_status
|
|
U_ASSERT(findHeader(53)) // UString::str_server
|
|
U_ASSERT(findHeader(32)) // UString::str_date
|
|
U_ASSERT(findHeader(27)) // UString::str_content_length
|
|
}
|
|
|
|
static void saveHpackData(const char* ptr, uint32_t len, bool breq)
|
|
{
|
|
U_TRACE(0+256, "UHTTP2::saveHpackData(%.*S,%u,%b)", len, ptr, len, breq)
|
|
|
|
/**
|
|
* We save the Header Block Fragment of the frame to inspect it with inflatehd (https://github.com/tatsuhiro-t/nghttp2)
|
|
*
|
|
* ./inflatehd < inflatehd.json => { "cases": [ { "wire": "8285" } ] }
|
|
*/
|
|
|
|
UString tmp(U_CAPACITY);
|
|
|
|
UHexDump::encode(ptr, len, tmp);
|
|
|
|
(void) UFile::writeToTmp(U_STRING_TO_PARAM(tmp), false, "%s.hpack.%P", breq ? "request" : "response");
|
|
}
|
|
|
|
static const char* getStreamStatusDescription();
|
|
static const char* getConnectionStatusDescription();
|
|
static const char* getFrameTypeDescription(char type);
|
|
#endif
|
|
|
|
static void decodeHeaders( unsigned char* ptr, unsigned char* endptr);
|
|
static void decodeHeaders(UHashMap<UString>* itable, HpackDynamicTable* idyntbl, unsigned char* ptr, unsigned char* endptr);
|
|
|
|
/**
|
|
* static void eraseHeader(uint32_t index)
|
|
* {
|
|
* U_TRACE(0, "UHTTP2::eraseHeader(%u)", index)
|
|
*
|
|
* UHashMap<UString>* table = &(pConnection->itable);
|
|
*
|
|
* table->hash = hash_static_table[index];
|
|
*
|
|
* table->lookup(hpack_static_table[index].name->rep);
|
|
*
|
|
* if (table->node) table->eraseAfterFind();
|
|
* }
|
|
*
|
|
* static void setFrameLengthAndType(char* ptr, uint32_t length, FrameTypesId type)
|
|
* {
|
|
* U_TRACE(0, "UHTTP2::setFrameLengthAndType(%p,%u,%d)", ptr, length, type)
|
|
*
|
|
* U_INTERNAL_ASSERT(type <= CONTINUATION)
|
|
*
|
|
* *(uint32_t*)ptr = htonl(length & 0x00ffffff) >> 8;
|
|
* ptr[3] = type;
|
|
*
|
|
* U_INTERNAL_DUMP("length = %#.4S", ptr) // "\000\000\004\003" (big endian: 0x11223344)
|
|
* }
|
|
*/
|
|
|
|
static int32_t getIndexStaticTable(const UString& key);
|
|
|
|
static bool setIndexStaticTable(UHashMap<void*>* table, const char* key, uint32_t length)
|
|
{
|
|
U_TRACE(0, "UHTTP2::setIndexStaticTable(%p,%.*S,%u)", table, length, key, length)
|
|
|
|
U_INTERNAL_DUMP("table->hash = %u", table->hash)
|
|
|
|
table->index = table->hash % table->_capacity;
|
|
|
|
U_RETURN(true); // NB: ignore case...
|
|
}
|
|
|
|
// HPACK (HTTP headers compression)
|
|
|
|
static HpackHeaderTableEntry* getHpackDynTblEntry(HpackDynamicTable* table, uint32_t index)
|
|
{
|
|
U_TRACE(0, "UHTTP2::getHpackDynTblEntry(%p,%u)", table, index)
|
|
|
|
U_INTERNAL_DUMP("table->entry_start_index = %u table->entry_capacity = %u", table->entry_start_index, table->entry_capacity)
|
|
|
|
uint32_t entry_index = (table->entry_start_index + index) % table->entry_capacity;
|
|
|
|
HpackHeaderTableEntry* entry = table->entries + entry_index;
|
|
|
|
U_INTERNAL_ASSERT_POINTER(entry->name)
|
|
|
|
U_INTERNAL_DUMP("entry->name = %V entry->value = %V", entry->name->rep, entry->value->rep)
|
|
|
|
U_RETURN_POINTER(entry, HpackHeaderTableEntry);
|
|
}
|
|
|
|
static void evictHpackDynTblEntry(HpackDynamicTable* table)
|
|
{
|
|
U_TRACE(0, "UHTTP2::evictHpackDynTblEntry(%p)", table)
|
|
|
|
U_INTERNAL_ASSERT_MAJOR(table->num_entries, 0)
|
|
|
|
table->num_entries--;
|
|
|
|
HpackHeaderTableEntry* entry = getHpackDynTblEntry(table, table->num_entries);
|
|
|
|
U_DEBUG("HTTP compressor header table index %u: (%V %V) removed for size %u", table->num_entries, entry->name->rep, entry->value->rep, table->hpack_size);
|
|
|
|
table->hpack_size -= entry->name->size() + entry->value->size() + HTTP2_HEADER_TABLE_ENTRY_SIZE_OFFSET;
|
|
|
|
delete entry->name;
|
|
delete entry->value;
|
|
}
|
|
|
|
static void setHpackDynTblCapacity(HpackDynamicTable* dyntbl, uint32_t value)
|
|
{
|
|
U_TRACE(0, "UHTTP2::setHpackDynTblCapacity(%p,%u)", dyntbl, value)
|
|
|
|
U_INTERNAL_DUMP("hpack_capacity = %u hpack_max_capacity = %u", dyntbl->hpack_capacity, dyntbl->hpack_max_capacity)
|
|
|
|
U_INTERNAL_ASSERT_DIFFERS(dyntbl->hpack_capacity, value)
|
|
|
|
dyntbl->hpack_capacity = value;
|
|
|
|
// adjust the size
|
|
|
|
while (dyntbl->num_entries != 0 &&
|
|
dyntbl->hpack_size > dyntbl->hpack_capacity)
|
|
{
|
|
evictHpackDynTblEntry(dyntbl);
|
|
}
|
|
}
|
|
|
|
static void clearHpackDynTbl( HpackDynamicTable* table);
|
|
static void addHpackDynTblEntry(HpackDynamicTable* table, const UString& name, const UString& value);
|
|
|
|
enum HuffDecodeFlag {
|
|
HUFF_ACCEPTED = 1, // FSA accepts this state as the end of huffman encoding sequence
|
|
HUFF_SYM = (1 << 1), // This state emits symbol
|
|
HUFF_FAIL = (1 << 2) // If state machine reaches this state, decoding fails
|
|
};
|
|
|
|
struct HuffDecode {
|
|
/**
|
|
* huffman decoding state, which is actually the node ID of internal huffman tree.
|
|
* We have 257 leaf nodes, but they are identical to root node other than emitting
|
|
* a symbol, so we have 256 internal nodes [1..255], inclusive
|
|
*/
|
|
uint8_t state,
|
|
flags, // bitwise OR of zero or more of the HuffDecodeFlag
|
|
sym; // symbol if HUFF_SYM flag set
|
|
};
|
|
|
|
struct HuffSym {
|
|
uint32_t nbits, // The number of bits in this code
|
|
code; // Huffman code aligned to LSB
|
|
};
|
|
|
|
static const HuffSym huff_sym_table[];
|
|
static const HuffDecode huff_decode_table[][16];
|
|
|
|
static unsigned char* hpackEncodeInt(unsigned char* dst, uint32_t value, uint8_t prefix_max)
|
|
{
|
|
U_TRACE(0, "UHTTP2::hpackEncodeInt(%p,%u,%u)", dst, value, prefix_max)
|
|
|
|
if (value <= prefix_max) *dst++ |= value;
|
|
else
|
|
{
|
|
value -= prefix_max;
|
|
|
|
U_INTERNAL_ASSERT(value <= 0x0fffffff)
|
|
|
|
for (*dst++ |= prefix_max; value >= 256; value >>= 8) *dst++ = 0x80 | value;
|
|
|
|
*dst++ = value;
|
|
}
|
|
|
|
return dst;
|
|
}
|
|
|
|
static unsigned char* hpackDecodeInt( unsigned char* src, unsigned char* src_end, int32_t* pvalue, uint8_t prefix_max);
|
|
static unsigned char* hpackDecodeString(unsigned char* src, unsigned char* src_end, UString* pvalue);
|
|
|
|
static unsigned char* hpackEncodeString(unsigned char* dst, const char* src, uint32_t len);
|
|
static unsigned char* hpackEncodeHeader(unsigned char* dst, HpackDynamicTable* dyntbl, UVector<UString>& vec, UString& key, const UString& value, bool bfirst);
|
|
|
|
private:
|
|
friend class UHTTP;
|
|
friend class UClientImage_Base;
|
|
|
|
#ifdef U_COMPILER_DELETE_MEMBERS
|
|
UHTTP2(const UHTTP2&) = delete;
|
|
UHTTP2& operator=(const UHTTP2&) = delete;
|
|
#else
|
|
UHTTP2(const UHTTP2&) {}
|
|
UHTTP2& operator=(const UHTTP2&) { return *this; }
|
|
#endif
|
|
};
|
|
|
|
#endif
|