From 5ebf33088be58c114e5ae03a56ae545e240ccdf7 Mon Sep 17 00:00:00 2001 From: princeb612 Date: Sat, 12 Oct 2024 20:02:57 +0900 Subject: [PATCH] hotplace rev.624 study RFC 9204 QPACK --- CHANGELOG.md | 6 +- README.md | 3 +- sdk/io/system/windows/multiplexer_iocp.cpp | 7 +- sdk/net/http/http2/hpack.hpp | 6 + sdk/net/http/http2/hpack_session.cpp | 5 + .../http/http2/http_header_compression.cpp | 9 +- .../http/http2/http_header_compression.hpp | 97 +++--- .../http2/http_header_compression_session.cpp | 191 ++++++++--- sdk/net/http/http3/qpack.hpp | 6 + sdk/net/http/http3/qpack_encoder.cpp | 36 +- sdk/net/http/http3/qpack_session.cpp | 4 +- sdk/net/http/http_server_builder.cpp | 5 + sdk/net/http/http_server_builder.hpp | 1 + test/hpack/sample.cpp | 15 +- test/httpserver3/sample.cpp | 311 +++++++++++++++++- test/nostd/sample.cpp | 3 + test/qpack/sample.cpp | 32 +- 17 files changed, 587 insertions(+), 150 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a428398..21e1ddb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # history +* Revision 624 + * [changed] QPACK duplicate + * Revision 623 * [tested] HPACK eviction @@ -332,9 +335,6 @@ * Revision 433 * [changed] COSE untagged message -* Revision 433 - * [changed] COSE untagged message - * Revision 431 * [added] variant diff --git a/README.md b/README.md index ccaab0d..ed66608 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,8 @@ * JOSE ![implemented](https://img.shields.io/badge/implemented-green) * CBOR ![implemented](https://img.shields.io/badge/implemented-green) * COSE ![implemented](https://img.shields.io/badge/implemented-green) - * HTTP/1.1,2,3 ![studying](https://img.shields.io/badge/studying-magenta) + * HTTP/1.1,2 ![implemented](https://img.shields.io/badge/implemented-green) + * HTTP/3 ![studying](https://img.shields.io/badge/studying-magenta) * ASN.1 ![studying](https://img.shields.io/badge/studying-magenta) * link * [changelog](CHANGELOG.md) diff --git a/sdk/io/system/windows/multiplexer_iocp.cpp b/sdk/io/system/windows/multiplexer_iocp.cpp index 0689466..bd93195 100644 --- a/sdk/io/system/windows/multiplexer_iocp.cpp +++ b/sdk/io/system/windows/multiplexer_iocp.cpp @@ -159,8 +159,9 @@ return_t multiplexer_iocp::event_loop_run(multiplexer_context_t* handle, handle_ break; } - // GetQueuedCompletionStatus - // GetQueuedCompletionStatusEx : retrieves multiple completion port entries simultaneously + // GetQueuedCompletionStatus | Windows XP | Windows Server 2003 + // GetQueuedCompletionStatusEx | Windows Vista | Windows Server 2008 + // retrieves multiple completion port entries simultaneously DWORD size_transfered = 0; ULONG_PTR completion_key = 0; LPOVERLAPPED overlapped = nullptr; @@ -172,7 +173,7 @@ return_t multiplexer_iocp::event_loop_run(multiplexer_context_t* handle, handle_ } else if (errorcode_t::success == ret) { /* mingw environments */ continue; } else { - break; // GLE - Windows 2003 returns 87, Windows 7 returns 735 + break; // GLE - Windows 2003 returns 87, Windows 7 returns 735(ERROR_ABANDONED_WAIT_0) } } if (0 == completion_key) { diff --git a/sdk/net/http/http2/hpack.hpp b/sdk/net/http/http2/hpack.hpp index 3ebde7e..016a5ae 100644 --- a/sdk/net/http/http2/hpack.hpp +++ b/sdk/net/http/http2/hpack.hpp @@ -230,6 +230,12 @@ class hpack_encoder : public http_header_compression { class hpack_session : public http_header_compression_session { public: hpack_session(); + /** + * @brief duplicate + * @param const std::string& name [in] + * @param const std::string& value [in] + */ + virtual return_t duplicate(const std::string& name, const std::string& value); /** * @brief HPACK query function * @param int cmd [in] see header_compression_cmd_t diff --git a/sdk/net/http/http2/hpack_session.cpp b/sdk/net/http/http2/hpack_session.cpp index a46f6b2..8da2fac 100644 --- a/sdk/net/http/http2/hpack_session.cpp +++ b/sdk/net/http/http2/hpack_session.cpp @@ -16,6 +16,11 @@ namespace net { hpack_session::hpack_session() : http_header_compression_session() {} +return_t hpack_session::duplicate(const std::string& name, const std::string& value) { + return_t ret = errorcode_t::success; + return ret; +} + return_t hpack_session::query(int cmd, void* req, size_t reqsize, void* resp, size_t& respsize) { return_t ret = errorcode_t::success; __try2 { diff --git a/sdk/net/http/http2/http_header_compression.cpp b/sdk/net/http/http2/http_header_compression.cpp index 623f554..579e72f 100644 --- a/sdk/net/http/http2/http_header_compression.cpp +++ b/sdk/net/http/http2/http_header_compression.cpp @@ -196,7 +196,7 @@ return_t http_header_compression::decode_string(const byte_t* p, size_t& pos, ui return ret; } -return_t http_header_compression::set_dynamic_table_size(binary_t& target, uint8 maxsize) { +return_t http_header_compression::set_dynamic_table_size(http_header_compression_session* session, binary_t& target, uint8 maxsize) { // RFC 7541 Figure 12: Maximum Dynamic Table Size Change // 0 1 2 3 4 5 6 7 // +---+---+---+---+---+---+---+---+ @@ -209,6 +209,13 @@ return_t http_header_compression::set_dynamic_table_size(binary_t& target, uint8 // | 0 | 0 | 1 | Capacity (5+) | // +---+---+---+-------------------+ + if (session) { + // RFC 7541 4.2. Maximum Table Size + // RFC 7541 6.3. Dynamic Table Size Update + // SETTINGS_HEADER_TABLE_SIZE + session->set_capacity(maxsize); + } + return encode_int(target, 0x20, 5, maxsize); } diff --git a/sdk/net/http/http2/http_header_compression.hpp b/sdk/net/http/http2/http_header_compression.hpp index 9583ff3..30be917 100644 --- a/sdk/net/http/http2/http_header_compression.hpp +++ b/sdk/net/http/http2/http_header_compression.hpp @@ -11,8 +11,6 @@ #ifndef __HOTPLACE_SDK_NET_HTTP_HEADER_COMPRESSION__ #define __HOTPLACE_SDK_NET_HTTP_HEADER_COMPRESSION__ -#include - #include namespace hotplace { @@ -149,13 +147,14 @@ class http_header_compression { /** * @brief dynamic table size + * @param http_header_compression_session* session [in] * @param binary_t& target * @param uint8 maxsize * @remarks * RFC 7541 6.3. Dynamic Table Size Update * RFC 9204 4.3.1. Set Dynamic Table Capacity */ - return_t set_dynamic_table_size(binary_t& target, uint8 maxsize); + return_t set_dynamic_table_size(http_header_compression_session* session, binary_t& target, uint8 maxsize); /** * @brief size of entry * @param const std::string& name [in] @@ -223,59 +222,27 @@ enum header_compression_cmd_t { qpack_cmd_capacity = 4, }; -struct qpack_section_prefix_t { - size_t capacity; - size_t ric; - size_t base; - size_t eic; - size_t delta; - - qpack_section_prefix_t(size_t c) : capacity(c), ric(0), base(0), eic(0), delta(0) {} - qpack_section_prefix_t(size_t c, size_t r, size_t b) : capacity(c), ric(r), base(b), eic(0), delta(0) { calc(); } - qpack_section_prefix_t(const qpack_section_prefix_t& rhs) : capacity(rhs.capacity), ric(rhs.ric), base(rhs.base), eic(rhs.eic), delta(rhs.delta) { calc(); } - qpack_section_prefix_t(qpack_section_prefix_t* rhs) { - copyfrom(rhs); - calc(); - } - void copyfrom(qpack_section_prefix_t* rhs) { - if (rhs) { - capacity = rhs->capacity; - ric = rhs->ric; - base = rhs->base; - eic = rhs->eic; - delta = rhs->delta; - } - } - void calc() { - if ((0 == eic) && (0 == delta)) { - /* RFC 9204 4.5.1.1. Required Insert Count - * if (ReqInsertCount) EncInsertCount = (ReqInsertCount mod (2 * MaxEntries)) + 1 - * else EncInsertCount = 0; - */ - if (0 == ric) { - eic = ric; - } else { - size_t maxentries = ::floor(capacity / 32); - eic = (ric % (2 * maxentries)) + 1; - } - /* RFC 9204 4.5.1.2. Base - * A Sign bit of 1 indicates that the Base is less than the Required Insert Count - * - * if (0 == Sign) Base = DeltaBase + ReqInsertCount - * else Base = ReqInsertCount - DeltaBase - 1 - * - * if (0 == Sign) DeltaBase = Base - ReqInsertCount - * else DeltaBase = ReqInsertCount - Base - 1 - */ - if (sign()) { - delta = ric - base - 1; - } else { - delta = ric - base; - } - } - } - bool sign() { return ric > base; } -}; +/** + * @brief Field Section Prefix + * @param size_t capacity [in] + * @param size_t ric [in] + * @param size_t base [in] + * @param size_t& eic [out] + * @param bool& sign [out] + * @param size_t& deltabase [out] + */ +return_t qpack_ric2eic(size_t capacity, size_t ric, size_t base, size_t& eic, bool& sign, size_t& deltabase); +/** + * @brief Field Section Prefix (reconstruct) + * @param size_t capacity [in] + * @param size_t tni [in] total number of inserts + * @param size_t eic [in] + * @param bool sign [in] + * @param size_t deltabase [in] + * @param size_t& ric [out] + * @param size_t& base [out] + */ +return_t qpack_eic2ric(size_t capacity, size_t tni, size_t eic, bool sign, size_t deltabase, size_t& ric, size_t& base); /** * @brief session @@ -294,6 +261,10 @@ class http_header_compression_session { */ bool operator==(const http_header_compression_session& rhs); bool operator!=(const http_header_compression_session& rhs); + /** + * @brief trace + */ + void trace(std::function f); /** * @brief match * @param const std::string& name [in] @@ -318,6 +289,12 @@ class http_header_compression_session { * @brief evict */ virtual return_t evict(); + /** + * @brief duplicate + * @param const std::string& name [in] + * @param const std::string& value [in] + */ + virtual return_t duplicate(const std::string& name, const std::string& value); /** * @brief capacity */ @@ -339,19 +316,19 @@ class http_header_compression_session { protected: typedef http_header_compression::table_entry_t table_entry_t; - typedef std::multimap dynamic_map_t; - typedef std::map dynamic_reversemap_t; - typedef std::map entry_size_t; // map + typedef std::multimap dynamic_map_t; // table_entry_t(value, entry) + typedef std::map dynamic_reversemap_t; // table_entry_t(name, entry size) dynamic_map_t _dynamic_map; dynamic_reversemap_t _dynamic_reversemap; - entry_size_t _entry_size; bool _separate; // false:HPACK, true:QPACK uint32 _capacity; size_t _tablesize; size_t _inserted; size_t _dropped; + + std::function _df; }; /* diff --git a/sdk/net/http/http2/http_header_compression_session.cpp b/sdk/net/http/http2/http_header_compression_session.cpp index 9c6425b..b5657f1 100644 --- a/sdk/net/http/http2/http_header_compression_session.cpp +++ b/sdk/net/http/http2/http_header_compression_session.cpp @@ -8,12 +8,95 @@ * Date Name Description */ +#include + #include #include namespace hotplace { namespace net { +return_t qpack_ric2eic(size_t capacity, size_t ric, size_t base, size_t& eic, bool& sign, size_t& deltabase) { + return_t ret = errorcode_t::success; + /* RFC 9204 4.5.1.1. Required Insert Count + * if (ReqInsertCount) EncInsertCount = (ReqInsertCount mod (2 * MaxEntries)) + 1 + * else EncInsertCount = 0; + */ + if (0 == ric) { + eic = ric; + } else { + size_t maxentries = ::floor(capacity / 32); + eic = (ric % (2 * maxentries)) + 1; + } + + /* RFC 9204 4.5.1.2. Base + * A Sign bit of 1 indicates that the Base is less than the Required Insert Count + * + * if (0 == Sign) DeltaBase = Base - ReqInsertCount + * else DeltaBase = ReqInsertCount - Base - 1 + */ + sign = (ric > base); + if (ric > base) { + deltabase = ric - base - 1; + } else { + deltabase = ric - base; + } + + return ret; +} + +return_t qpack_eic2ric(size_t capacity, size_t tni, size_t eic, bool sign, size_t deltabase, size_t& ric, size_t& base) { + return_t ret = errorcode_t::success; + __try2 { + // RFC 9204 4.5.1.1. Required Insert Count + size_t maxentries = ::floor(capacity / 32); + eic = (ric % (2 * maxentries)) + 1; + size_t fullrange = 2 * maxentries; + if (0 == eic) { + ric = 0; + } else { + if (eic > fullrange) { + ret = errorcode_t::invalid_request; + __leave2; + } + + size_t maxvalue = tni + maxentries; + size_t maxwrapped = ::floor(maxvalue / fullrange) * fullrange; + ric = maxwrapped + eic - 1; + + if (ric > maxvalue) { + if (ric <= fullrange) { + ret = errorcode_t::invalid_request; + __leave2; + } else { + ric -= fullrange; + } + } + + if (0 == ric) { + ret = errorcode_t::invalid_request; + __leave2; + } + + /* RFC 9204 4.5.1.2. Base + * A Sign bit of 1 indicates that the Base is less than the Required Insert Count + * + * if (0 == Sign) Base = DeltaBase + ReqInsertCount + * else Base = ReqInsertCount - DeltaBase - 1 + */ + if (0 == sign) { + base = deltabase + ric; + } else { + base = ric - deltabase - 1; + } + } + } + __finally2 { + // do nothing + } + return ret; +} + http_header_compression_session::http_header_compression_session() : _separate(false), _inserted(0), _dropped(0), _capacity(0x10000), _tablesize(0) {} void http_header_compression_session::for_each(std::function v) { @@ -32,35 +115,45 @@ bool http_header_compression_session::operator!=(const http_header_compression_s return (_separate != rhs._separate) || (_dynamic_map != rhs._dynamic_map); } +void http_header_compression_session::trace(std::function f) { _df = f; } + match_result_t http_header_compression_session::match(const std::string& name, const std::string& value, size_t& index) { match_result_t state = match_result_t::not_matched; auto lbound = _dynamic_map.lower_bound(name); auto ubound = _dynamic_map.upper_bound(name); + std::priority_queue pq; + for (auto iter = lbound; iter != ubound; iter++) { const auto& k = iter->first; - const auto& v = iter->second; - if ((name == k) && (value == v.first)) { - state = match_result_t::all_matched_dynamic; - /** - * get index from v.second - * - * consider following cases - * capacity = 3, _inserted = 2, _dropped = 0, table {1 0}, table.size = 2 - * capacity = 3, _inserted = 3, _dropped = 0, table {2 1 0}, table.size = 3 - * capacity = 3, _inserted = 4, _dropped = 1, table {3 2 1}, table.size = 3 - * capacity = 3, _inserted = 5, _dropped = 2, table {4 3 2}, table.size = 3 - * - * conclusion - * index = _inserted - _dropped - v.second + _dropped - 1 = _inserted - v.second - 1 - */ - index = _inserted - v.second - 1; - if (false == _separate) { - // HPACK - auto static_entries = http_resource::get_instance()->sizeof_hpack_static_table_entries(); - index += (static_entries + 1); - } - break; + const auto& v = iter->second; // pair(value, entry) + const auto& val = v.first; + const auto& ent = v.second; + if ((name == k) && (value == val)) { + pq.push(ent); + } + } + + if (pq.size()) { + state = match_result_t::all_matched_dynamic; + const auto& ent = pq.top(); + /** + * get index from v.second + * + * consider following cases + * capacity = 3, _inserted = 2, _dropped = 0, table {1 0}, table.size = 2 + * capacity = 3, _inserted = 3, _dropped = 0, table {2 1 0}, table.size = 3 + * capacity = 3, _inserted = 4, _dropped = 1, table {3 2 1}, table.size = 3 + * capacity = 3, _inserted = 5, _dropped = 2, table {4 3 2}, table.size = 3 + * + * conclusion + * index = _inserted - _dropped - v.second + _dropped - 1 = _inserted - v.second - 1 + */ + index = _inserted - ent - 1; + if (false == _separate) { + // HPACK + auto static_entries = http_resource::get_instance()->sizeof_hpack_static_table_entries(); + index += (static_entries + 1); } } @@ -90,15 +183,18 @@ return_t http_header_compression_session::select(size_t index, std::string& name const auto& t = _inserted - index - 1; auto riter = _dynamic_reversemap.find(t); // never happen (_dynamic_reversemap.end() == riter) - const auto& k = riter->second; - auto lbound = _dynamic_map.lower_bound(k); - auto ubound = _dynamic_map.upper_bound(k); + const auto& pne = riter->second; // pair(name, entry size) + const auto& nam = pne.first; + auto lbound = _dynamic_map.lower_bound(nam); + auto ubound = _dynamic_map.upper_bound(nam); for (auto iter = lbound; iter != ubound; iter++) { - const auto& v = iter->second; - if (t == v.second) { - name = k; - value = v.first; + const auto& pve = iter->second; // pair(value, entry) + const auto& val = pve.first; + const auto& ent = pve.second; + if (t == ent) { + name = nam; + value = val; break; } } @@ -136,8 +232,7 @@ return_t http_header_compression_session::insert(const std::string& name, const evict(); _dynamic_map.insert({name, {value, _inserted}}); - _dynamic_reversemap.insert({_inserted, name}); - _entry_size.insert({_inserted, entrysize}); + _dynamic_reversemap.insert({_inserted, {name, entrysize}}); _inserted++; @@ -151,21 +246,30 @@ return_t http_header_compression_session::evict() { // RFC 7541 4.2. Maximum Table Size // RFC 7541 4.4. Entry Eviction When Adding New Entries // RFC 9204 3.2.2. Dynamic Table Capacity and Eviction - auto back = _dynamic_reversemap.find(_dropped); + auto entry = _dropped; + auto back = _dynamic_reversemap.find(entry); - auto const& t = back->first; - auto const& k = back->second; + auto const& t = back->first; // entry + auto const& k = back->second; // (name, entry size) - auto dpiter = _entry_size.find(t); - _tablesize -= dpiter->second; - _entry_size.erase(dpiter); + auto const& name = k.first; + auto const& entrysize = k.second; - auto lbound = _dynamic_map.lower_bound(k); - auto ubound = _dynamic_map.upper_bound(k); + _tablesize -= entrysize; + + auto lbound = _dynamic_map.lower_bound(name); + auto ubound = _dynamic_map.upper_bound(name); for (auto iter = lbound; iter != ubound; iter++) { - auto const& v = iter->second; - if (v.second == t) { + auto const& v = iter->second; // pair(value, entry) + auto const& val = v.first; + auto const& ent = v.second; + if (ent == t) { + if (_df) { + basic_stream bs; + bs.printf("evict entry[%zi] %s=%s", entry, name.c_str(), val.c_str()); + _df(&bs); + } _dynamic_map.erase(iter); break; } @@ -178,6 +282,11 @@ return_t http_header_compression_session::evict() { return ret; } +return_t http_header_compression_session::duplicate(const std::string& name, const std::string& value) { + return_t ret = errorcode_t::success; + return ret; +} + void http_header_compression_session::set_capacity(uint32 capacity) { /** * RFC 9113 6.5.2. Defined Settings diff --git a/sdk/net/http/http3/qpack.hpp b/sdk/net/http/http3/qpack.hpp index 1d78604..2e3d8de 100644 --- a/sdk/net/http/http3/qpack.hpp +++ b/sdk/net/http/http3/qpack.hpp @@ -153,6 +153,12 @@ class qpack_session : public http_header_compression_session { public: qpack_session(); + /** + * @brief duplicate + * @param const std::string& name [in] + * @param const std::string& value [in] + */ + virtual return_t duplicate(const std::string& name, const std::string& value); /** * @brief QPACK query function * @param int cmd [in] see header_compression_cmd_t diff --git a/sdk/net/http/http3/qpack_encoder.cpp b/sdk/net/http/http3/qpack_encoder.cpp index 27da1e3..93e682c 100644 --- a/sdk/net/http/http3/qpack_encoder.cpp +++ b/sdk/net/http/http3/qpack_encoder.cpp @@ -40,15 +40,31 @@ return_t qpack_encoder::encode(http_header_compression_session* session, binary_ if (match_result_t::all_matched == state) { flags |= qpack_static; } - if (qpack_indexing & flags) { + if ((all_matched_dynamic == state) && (qpack_indexing & flags)) { /** * RFC 9204 + * 2.1.1.1. Avoiding Prohibited Insertions + * + * <-- Newer Entries Older Entries --> + * (Larger Indices) (Smaller Indices) + * +--------+---------------------------------+----------+ + * | Unused | Referenceable | Draining | + * | Space | Entries | Entries | + * +--------+---------------------------------+----------+ + * ^ ^ ^ + * | | | + * Insertion Point Draining Index Dropping + * Point + * + * Figure 1: Draining Dynamic Table Entries + * * 4.3. Encoder Instructions * 4.3.4. Duplicate * B.4. Duplicate Instruction, Stream Cancellation - * an encoded field section referencing the dynamic table entries including the duplicated entry + * - an encoded field section referencing the dynamic table entries including the duplicated entry */ ret = errorcode_t::already_exist; + session->duplicate(name, value); duplicate(target, index); _tobe_sync++; } else { @@ -58,7 +74,7 @@ return_t qpack_encoder::encode(http_header_compression_session* session, binary_ if (qpack_postbase_index & flags) { encode_index(target, flags, postbase); } else { - encode_index(target, flags, postbase); + encode_index(target, flags, index); } } break; @@ -127,8 +143,12 @@ return_t qpack_encoder::sync(http_header_compression_session* session, binary_t& * 4.5.1.1. Required Insert Count */ if (session) { - size_t respsize = 0; + size_t capacity = session->get_capacity(); + size_t ric = _tobe_sync; size_t base = 0; + size_t eic = 0; + bool sign = true; + size_t deltabase = 0; if (qpack_postbase_index & flags) { base = 0; @@ -136,13 +156,13 @@ return_t qpack_encoder::sync(http_header_compression_session* session, binary_t& base = _tobe_sync; } - qpack_section_prefix_t fsp(session->get_capacity(), _tobe_sync, base); + qpack_ric2eic(capacity, ric, base, eic, sign, deltabase); binary_t temp; - temp.push_back(fsp.eic); - uint8 mask = fsp.sign() ? 0x80 : 0x00; + temp.push_back(eic); + uint8 mask = sign ? 0x80 : 0x00; uint8 prefix = 7; - encode_int(temp, mask, prefix, fsp.delta); + encode_int(temp, mask, prefix, deltabase); target.insert(target.begin(), temp.begin(), temp.end()); _tobe_sync = 0; diff --git a/sdk/net/http/http3/qpack_session.cpp b/sdk/net/http/http3/qpack_session.cpp index 1c35867..a67e582 100644 --- a/sdk/net/http/http3/qpack_session.cpp +++ b/sdk/net/http/http3/qpack_session.cpp @@ -14,10 +14,10 @@ namespace hotplace { namespace net { -// studying - qpack_session::qpack_session() : http_header_compression_session() { _separate = true; } +return_t qpack_session::duplicate(const std::string& name, const std::string& value) { return insert(name, value); } + return_t qpack_session::query(int cmd, void* req, size_t reqsize, void* resp, size_t& respsize) { return_t ret = errorcode_t::success; __try2 { diff --git a/sdk/net/http/http_server_builder.cpp b/sdk/net/http/http_server_builder.cpp index d5ddd69..7e4f830 100644 --- a/sdk/net/http/http_server_builder.cpp +++ b/sdk/net/http/http_server_builder.cpp @@ -84,6 +84,11 @@ http_server_builder& http_server_builder::enable_h2(bool enable) { return *this; } +http_server_builder& http_server_builder::enable_h3(bool enable) { + get_server_conf().set(netserver_config_t::serverconf_enable_h3, enable ? 1 : 0); + return *this; +} + http_server_builder& http_server_builder::set_handler(http_server_handler_t handler, void* user_context) { _handler = handler; _user_context = user_context; diff --git a/sdk/net/http/http_server_builder.hpp b/sdk/net/http/http_server_builder.hpp index 557f326..2e53720 100644 --- a/sdk/net/http/http_server_builder.hpp +++ b/sdk/net/http/http_server_builder.hpp @@ -60,6 +60,7 @@ class http_server_builder { http_server_builder& enable_ipv6(bool enable); http_server_builder& enable_h2(bool enable); + http_server_builder& enable_h3(bool enable); http_server_builder& set_handler(http_server_handler_t handler, void* user_context = nullptr); diff --git a/test/hpack/sample.cpp b/test/hpack/sample.cpp index 8ad9d07..e9e861b 100644 --- a/test/hpack/sample.cpp +++ b/test/hpack/sample.cpp @@ -290,10 +290,9 @@ void test_rfc7541_c_3() { const OPTION& option = cmdline->value(); hpack hp; - hpack_session session; // dynamic table - basic_stream bs; - + hpack_session session; // dynamic table hpack_session session_receiver; // dynamic table + basic_stream bs; // C.3.1. First Request // :method: GET @@ -317,7 +316,6 @@ void test_rfc7541_c_3() { "2e63 6f6d "; _test_case.assert(hp.get_binary() == base16_decode_rfc(expect1), __FUNCTION__, "RFC 7541 C.3.1 First Request"); - // [ 1] (s = 57) :authority: www.example.com // Table size: 57 @@ -386,10 +384,9 @@ void test_rfc7541_c_4() { const OPTION& option = cmdline->value(); hpack hp; - hpack_session session; // dynamic table - basic_stream bs; - + hpack_session session; // dynamic table hpack_session session_receiver; // dynamic table + basic_stream bs; // C.4.1. First Request hp.set_encoder(&*encoder) @@ -463,7 +460,7 @@ void test_rfc7541_c_5() { const OPTION& option = cmdline->value(); hpack hp; - hpack_session session; // dynamic table + hpack_session session; // dynamic table hpack_session session_receiver; // dynamic table basic_stream bs; @@ -552,7 +549,7 @@ void test_rfc7541_c_6() { const OPTION& option = cmdline->value(); hpack hp; - hpack_session session; // dynamic table + hpack_session session; // dynamic table hpack_session session_receiver; // dynamic table basic_stream bs; diff --git a/test/httpserver3/sample.cpp b/test/httpserver3/sample.cpp index 202b694..d802821 100644 --- a/test/httpserver3/sample.cpp +++ b/test/httpserver3/sample.cpp @@ -3,35 +3,305 @@ * @file {file} * @author Soo Han, Kim (princeb612.kr@gmail.com) * @desc + * simple https server implementation + * @sa See in the following order : tcpserver1, tcpserver2, tlsserver, httpserver1, httpauth, httpserver2 * * Revision History * Date Name Description + * + * @comments + * debug w/ curl + * curl -v -k https://localhost:9000 --http2 + * curl -v -k https://localhost:9000 --http2 --http2-prior-knowledge */ -#include -#include -#include +#include +#include + +#include #include using namespace hotplace; -using namespace hotplace::crypto; using namespace hotplace::io; +using namespace hotplace::crypto; using namespace hotplace::net; test_case _test_case; t_shared_instance _logger; +#define FILENAME_RUN _T (".run") + typedef struct _OPTION { + int port; + int port_tls; int verbose; - _OPTION() : verbose(0) { + _OPTION() : port(8080), port_tls(9000), verbose(0) {} +} OPTION; + +t_shared_instance > _cmdline; +t_shared_instance _http_server; +critical_section print_lock; + +void cprint(const char* text, ...) { + basic_stream bs; + critical_section_guard guard(print_lock); + console_color _concolor; + + bs << _concolor.turnon().set_fgcolor(console_color_t::cyan); + va_list ap; + va_start(ap, text); + bs.vprintf(text, ap); + va_end(ap); + bs << _concolor.turnoff(); + + _logger->writeln(bs); +} + +void print(const char* text, ...) { + // valgrind + critical_section_guard guard(print_lock); + va_list ap; + va_start(ap, text); + vprintf(text, ap); + va_end(ap); + fflush(stdout); +} + +void api_response_html_handler(network_session*, http_request* request, http_response* response, http_router* router) { + response->compose(200, "text/html", "page - ok"); +} + +void api_response_json_handler(network_session*, http_request* request, http_response* response, http_router* router) { + response->compose(200, "application/json", R"({"result":"ok"})"); +} + +return_t consume_routine(uint32 type, uint32 data_count, void* data_array[], CALLBACK_CONTROL* callback_control, void* user_context) { + return_t ret = errorcode_t::success; + network_session_socket_t* session_socket = (network_session_socket_t*)data_array[0]; + char* buf = (char*)data_array[1]; + size_t bufsize = (size_t)data_array[2]; + network_session* session = (network_session*)data_array[3]; + http_request* request = (http_request*)data_array[4]; + + binary_t bin; + basic_stream bs; + std::string message; + + const OPTION& option = _cmdline->value(); + + switch (type) { + case mux_connect: + // cprint("connect %i", session_socket->event_socket); + break; + case mux_read: + // cprint("read %i", session_socket->event_socket); + if (request) { + http_response response(request); + if (option.verbose) { + response.trace([](stream_t* s) -> void { print("\e[1;37m%.*s\e[0m", (unsigned int)s->size(), s->data()); }); + } + _http_server->get_http_router().route(session, request, &response); + response.respond(session); + } + break; + case mux_disconnect: + // cprint("disconnect %i", session_socket->event_socket); + break; + } + + return ret; +} + +return_t simple_http2_server(void*) { + const OPTION& option = _cmdline->value(); + + return_t ret = errorcode_t::success; + http_server_builder builder; + + FILE* fp = fopen(FILENAME_RUN, "w"); + + fclose(fp); + + __try2 { + builder + .enable_http(false) // disable http scheme + .set_port_http(option.port) + .enable_https(true) // enable https scheme + .set_port_https(option.port_tls) + .set_tls_certificate("server.crt", "server.key") + .set_tls_cipher_list("TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:TLS_AES_128_CCM_8_SHA256:TLS_AES_128_CCM_SHA256") + .set_tls_verify_peer(0) // self-signed certificate + .enable_ipv4(true) // enable IPv4 + .enable_ipv6(false) // disable IPv6 + .enable_h2(true) // enable HTTP/2 + .set_handler(consume_routine); + builder.get_server_conf() + .set(netserver_config_t::serverconf_concurrent_tls_accept, 1) + .set(netserver_config_t::serverconf_concurrent_network, 2) + .set(netserver_config_t::serverconf_concurrent_consume, 2); + if (option.verbose) { + auto trace_handler = [](stream_t* s) -> void { print("%.*s", (unsigned int)s->size(), s->data()); }; + builder.trace(trace_handler); + builder.get_server_conf().set(netserver_config_t::serverconf_trace_ns, 1).set(netserver_config_t::serverconf_trace_h2, 1); + } + _http_server.make_share(builder.build()); + + _http_server->get_http_protocol().set_constraints(protocol_constraints_t::protocol_packet_size, 1 << 14); + _http_server->get_http2_protocol().set_constraints(protocol_constraints_t::protocol_packet_size, 1 << 14); + + // Basic Authentication (realm) + std::string basic_realm = "Hello World"; + // Digest Access Authentication (realm/algorithm/qop/userhash) + std::string digest_access_realm = "happiness"; + std::string digest_access_realm2 = "testrealm@host.com"; + std::string digest_access_alg = "SHA-256-sess"; + std::string digest_access_alg2 = "SHA-512-256-sess"; + std::string digest_access_qop = "auth"; + bool digest_access_userhash = true; + // Bearer Authentication (realm) + std::string bearer_realm = "hotplace"; + // OAuth 2.0 (realm) + std::string oauth2_realm = "somewhere over the rainbow"; + basic_stream endpoint_url; + basic_stream cb_url; + endpoint_url << "https://localhost:" << option.port_tls; + cb_url << endpoint_url << "/client/cb"; + + std::function default_handler = + [&](network_session* session, http_request* request, http_response* response, http_router* router) -> void { + basic_stream bs; + bs << request->get_http_uri().get_uri(); + response->compose(200, "text/html", "
%s
", bs.c_str()); + }; + std::function error_handler = + [&](network_session* session, http_request* request, http_response* response, http_router* router) -> void { + basic_stream bs; + bs << request->get_http_uri().get_uri(); + response->compose(200, "text/html", "404 Not Found
%s
", bs.c_str()); + }; + std::function cb_handler = + [&](network_session* session, http_request* request, http_response* response, http_router* router) -> void { + skey_value& kv = request->get_http_uri().get_query_keyvalue(); + std::string code = kv.get("code"); + std::string access_token = kv.get("access_token"); + std::string error = kv.get("error"); + if (error.empty()) { + if (code.size()) { + // Authorization Code Grant + http_client client; + http_request req; + http_response* resp = nullptr; + basic_stream bs; + bs << "/auth/token?grant_type=authorization_code&code=" << code << "&redirect_uri=" << cb_url << "&client_id=s6BhdRkqt3"; + + req.compose(http_method_t::HTTP_POST, bs.c_str(), ""); // token endpoint + req.get_http_header().add("Authorization", "Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW"); // s6BhdRkqt3:gX1fBat3bV + + client.set_url(endpoint_url.c_str()); + client.set_wto(10 * 1000); + client.request(req, &resp); + if (resp) { + *response = *resp; + resp->release(); + } + } else if (access_token.size()) { + // Implitcit Grant + response->compose(200, "text/plain", ""); + } + } else { + response->compose(401, "text/html", "Unauthorized"); + } + }; + + _http_server->get_http_router() + .get_html_documents() + .add_documents_root("/", ".") + .add_content_type(".css", "text/css") + .add_content_type(".html", "text/html") + .add_content_type(".ico", "image/image/vnd.microsoft.icon") + .add_content_type(".jpeg", "image/jpeg") + .add_content_type(".json", "text/json") + .set_default_document("index.html"); + + _http_server->get_http_router() + .add("/api/html", api_response_html_handler) + .add("/api/json", api_response_json_handler) + .add("/api/test", default_handler) + // .add(404, error_handler) + // basic authentication + .add("/auth/basic", default_handler, new basic_authentication_provider(basic_realm)) + // digest access authentication + .add("/auth/digest", default_handler, + new digest_access_authentication_provider(digest_access_realm, digest_access_alg, digest_access_qop, digest_access_userhash)) + .add("/auth/digest2", default_handler, new digest_access_authentication_provider(digest_access_realm2, "", digest_access_qop)) + // bearer authentication + .add("/auth/bearer", default_handler, new bearer_authentication_provider(bearer_realm)) + // callback + .add("/client/cb", cb_handler); + + _http_server->get_http_router() + .get_oauth2_provider() + .add(new oauth2_authorization_code_grant_provider) + .add(new oauth2_implicit_grant_provider) + .add(new oauth2_resource_owner_password_credentials_grant_provider) + .add(new oauth2_client_credentials_grant_provider) + .add(new oauth2_unsupported_provider) + .set(oauth2_authorization_endpoint, "/auth/authorize") + .set(oauth2_token_endpoint, "/auth/token") + .set(oauth2_signpage, "/auth/sign") + .set(oauth2_signin, "/auth/signin") + .set_token_endpoint_authentication(new basic_authentication_provider(oauth2_realm)) + .apply(_http_server->get_http_router()); + + http_authentication_resolver& resolver = _http_server->get_http_router().get_authenticate_resolver(); + + resolver.get_basic_credentials(basic_realm).add("user", "password"); + resolver.get_basic_credentials(oauth2_realm).add("s6BhdRkqt3", "gX1fBat3bV"); // RFC 6749 Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW + resolver.get_digest_credentials(digest_access_realm).add(digest_access_realm, digest_access_alg, "user", "password"); + resolver.get_digest_credentials(digest_access_realm2).add(digest_access_realm2, digest_access_alg2, "Mufasa", "Circle Of Life"); + resolver.get_bearer_credentials(bearer_realm).add("clientid", "token"); + + resolver.get_oauth2_credentials().insert("s6BhdRkqt3", "gX1fBat3bV", "user", "testapp", cb_url.c_str(), std::list()); + resolver.get_custom_credentials().add("user", "password"); + + _http_server->start(); + + while (true) { + msleep(1000); + +#if defined __linux__ + int chk = access(FILENAME_RUN, F_OK); + if (errorcode_t::success != chk) { + break; + } +#elif defined _WIN32 || defined _WIN64 + uint32 dwAttrib = GetFileAttributes(FILENAME_RUN); + if (INVALID_FILE_ATTRIBUTES == dwAttrib) { + break; + } +#endif + } + + _http_server->stop(); + } + __finally2 { // do nothing } -} OPTION; -t_shared_instance> _cmdline; + + return ret; +} void run_server() { - // todo + thread thread1(simple_http2_server, nullptr); + return_t ret = errorcode_t::success; + + __try2 { + _test_case.begin("http/2 powered by http_server"); + + thread1.start(); + } + __finally2 { thread1.wait(-1); } } int main(int argc, char** argv) { @@ -40,7 +310,10 @@ int main(int argc, char** argv) { #endif _cmdline.make_share(new t_cmdline_t