Skip to content

Commit ecc410b

Browse files
committed
Merge FEATURE-v3-protocol-limits: typed capacities and effective inspection
2 parents 8507abd + 8742450 commit ecc410b

50 files changed

Lines changed: 2344 additions & 338 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎Makefile.am‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -618,3 +618,5 @@ check-v3-compiled-operations: $(top_builddir)/src/libhttpserver.la
618618
python3 $(top_srcdir)/scripts/check-v3-compiled-operations.py $(top_builddir)/src/.libs/libhttpserver.a
619619

620620
.PHONY: check-v3-compiled-operations
621+
622+
EXTRA_DIST += docs/v3-protocol-limits.md scripts/check-v3-protocol-limits.py test/installed/v3_protocol_limits_consumer.cpp

‎docs/native-tls-build.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,3 +287,7 @@ shared/static consumers in managed/external modes. Supported TLS/H2/UDP-H3
287287
configurations bind and complete exchanges; missing credentials and invalid
288288
configurations retain their separate pre-bind rejection checks. Historical
289289
TASK-179 and TASK-182 records describe their original execution boundaries.
290+
291+
Protocol capacity `resolve_limits()` adds build availability to semantic
292+
`validate()`; native `effective_limits()` reads the cached startup policy without
293+
requiring published credentials. See [capacity policy](v3-protocol-limits.md).

‎docs/v3-api.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,3 +138,15 @@ live application charges return to baseline.
138138
139139
See `examples/v3_http3.cpp` for one route registry serving TCP H1/H2 and UDP H3,
140140
with an RFC9220 echo route and either loop mode.
141+
142+
## Protocol capacity policy
143+
144+
`server_options::http2()`, `http3()` and `quic()` expose independent optional
145+
value profiles from `<httpserver/server/protocol_limits.hpp>`.
146+
`resolve_limits(effective_protocol_limits&) const` is pure and build-aware;
147+
`validate()` remains semantic-only. `native_server::effective_limits(...) const`
148+
returns the immutable local startup policy before listen and after stop. Both
149+
output methods preserve output on failure. Shared budgets remain authoritative.
150+
See [native protocol capacities](v3-protocol-limits.md) for defaults, bounds,
151+
compression/retention envelopes and the distinction between enforcement, initial
152+
advertisement and peer-bounded encoder use.

‎docs/v3-protocol-limits.md‎

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
# Native protocol capacities
2+
3+
Include `<httpserver/server/protocol_limits.hpp>` directly, or use
4+
`<httpserver/server/configuration.hpp>`. `server_options::http2()`, `http3()`
5+
and `quic()` each return mutable/const references to an optional value profile.
6+
A missing profile keeps the existing native derivation. A present profile,
7+
including an empty one, requests explicit capacity validation. Individual unset
8+
members retain their defaults.
9+
10+
```cpp
11+
httpserver::server::server_options options;
12+
// Configure TLS, credentials, enabled protocols and matching listeners first.
13+
options.budgets().set(httpserver::server::resource::header_fields, 4096);
14+
options.budgets().set(httpserver::server::resource::quic_reassembly_bytes,
15+
16 * 1024 * 1024);
16+
options.http2().emplace().max_concurrent_requests = 192;
17+
options.http2()->decoder_table_bytes = 8192;
18+
options.http2()->encoder_table_bytes = 16384;
19+
options.http3().emplace().max_concurrent_requests = 24;
20+
options.http3()->max_blocked_headers = 32;
21+
options.quic().emplace().max_stream_records = 96;
22+
httpserver::server::effective_protocol_limits limits;
23+
auto result = options.resolve_limits(limits);
24+
if (result.ok()) {
25+
httpserver::server::native_server server(options);
26+
result = server.effective_limits(limits);
27+
}
28+
```
29+
30+
`header_capacity_options` holds `max_compressed_bytes`, `max_expanded_bytes`
31+
and `max_fields`, all optional `std::size_t` values. H2/H3 profiles contain
32+
`headers`, `max_concurrent_requests`, `decoder_table_bytes` and
33+
`encoder_table_bytes`. H3 additionally contains `max_blocked_headers` and
34+
`max_retained_header_bytes`. QUIC contains `max_stream_records`.
35+
36+
`resolve_limits(effective_protocol_limits&) const` performs semantic and storage
37+
planning, then checks compiled protocol/provider availability. It performs no I/O,
38+
credential acquisition, reservations, environment reads, binds or worker startup.
39+
`validate()` runs the same semantic/storage planner without the build availability
40+
gate; a valid TLS configuration can therefore validate in a TLS-off build while
41+
resolution returns `not_supported`. Startup completes those checks before binding
42+
any endpoint, then checks credentials/backend readiness.
43+
44+
`native_server::effective_limits(effective_protocol_limits&) const` reads the
45+
immutable plan resolved from the server's copied options. It works before listen,
46+
while serving or draining, after a failed listen, and after stop. Credential
47+
publication/replacement and mutations of the original options cannot change that
48+
report. Both methods leave the entire output unchanged on failure. Inspection is
49+
local startup policy; it does not inspect live connections or imply a successful
50+
handshake. Managed and external loops use the same plan.
51+
52+
H2 applies to enabled H2 with a TLS TCP listener. H3 and QUIC apply to enabled H3
53+
with an explicit TLS UDP listener. Inapplicable effective sections are absent.
54+
A present profile for an inapplicable/disabled protocol fails `invalid_argument`.
55+
Unsupported compiled transports fail build-aware resolution/startup with
56+
`not_supported`. An unused TLS provider selection leaves plaintext HTTP/1 available. Existing TLS profile/transport rules still apply.
57+
58+
## Defaults and bounds
59+
60+
| Capacity | Unset native policy |
61+
| --- | --- |
62+
| H2 concurrent requests | `min(128, streams budget)` |
63+
| H2 compressed header bytes | `min(65536, body_buffer_bytes / 16)` |
64+
| H2 expanded header bytes | `min(65536, header_bytes / 3)` |
65+
| H2 fields | `min(256, header_fields budget)` |
66+
| H2 decoder/encoder table bytes | 4096 each |
67+
| H3 concurrent requests | `min(16, streams budget)` |
68+
| H3 compressed/expanded bytes/fields | 8192 / 16384 / 128 |
69+
| H3 decoder/encoder table bytes | `min(4096, qpack_table_bytes budget)` each |
70+
| H3 blocked headers | `min(8, blocked_headers budget)` |
71+
| H3 retained compressed HEADERS bytes | 65536 |
72+
| QUIC retained stream records | 64 |
73+
| Initial QUIC bidi/uni stream credit | H3 request ceiling / 8 |
74+
| Advertised/native send datagram bytes | 1200 / 1200 |
75+
76+
Positive explicit request ceilings must fit the aggregate stream budget. H2's
77+
ceiling is 1,048,576; the historical128 is a default. H3 requests must also fit the
78+
retained ledger alongside three local and three peer control/QPACK roles. The
79+
ledger range is7..65,536; its actual prepaid storage can impose a smaller limit.
80+
Explicit H3 policy needs at least four aggregate stream seats for three peer
81+
critical roles and one request. Uni credit8 also permits unknown stream types;
82+
those records compete with requests and terminal records for the same ledger.
83+
84+
Header bytes and fields must be positive. H2 expanded bytes fit three copies in
85+
`header_bytes`; H3 needs five aggregate header-byte charges and an additional
86+
semantic field seat (`max_fields + 1`). Field resource maxima are65,536 for H2
87+
and65,535 for H3, subject to the configured budget. H2 compressed bytes fit the
88+
body-buffer budget after native owner, frame and one stream descriptor workspace. H3 compressed/decoded metadata, retained HEADERS, semantic
89+
copies, body/response buffers and descriptor arrays must fit its private prepaid
90+
workspace. At least one maximum compressed section must fit retained HEADERS.
91+
All arithmetic and wire conversions are checked; errors identify the field or
92+
storage/budget relationship. Explicit values are rejected instead of clamped.
93+
94+
Compression tables permit zero, with a resource maximum16,777,216 bytes. Both
95+
local directions must fit the same aggregate compression budget for an explicit
96+
policy. H2 includes `max(4096, decoder_table_bytes) + encoder_table_bytes`, because
97+
a reduced advertised decoder maximum becomes enforceable after SETTINGS ACK.
98+
The initial4096 allowance is reported separately as
99+
`effective_http2_limits::pre_ack_decoder_table_bytes`. Zero H2 encoder capacity
100+
emits the required table-size update. See [RFC9113 §4.3.1](https://www.rfc-editor.org/rfc/rfc9113.html#section-4.3.1).
101+
102+
H3 uses `decoder_table_bytes + encoder_table_bytes`. Decoder zero with a positive
103+
encoder reserves its output workspace; both zero still support static/literal
104+
sections without dynamic inserts. Blocked-header ceilings permit zero and are
105+
bounded by the aggregate blocked-header budget (maximum65,536). They are
106+
independent of request/record ceilings. Encoder ceilings in both protocols are
107+
local limits: actual use is additionally bounded by the peer's SETTINGS. See
108+
[RFC9204 §§3.2.3,5,7.3](https://www.rfc-editor.org/rfc/rfc9204.html).
109+
110+
Private H3 data/critical subdivisions grow for explicit policy when the root
111+
reassembly budget permits. Planning includes the retained flow ledger, native
112+
transport/framing descriptors, bridge records and scratch/receipt/information
113+
arrays, decoded fields, semantic copies, compression instruction/output work,
114+
recovery metadata/payload, queued datagrams, TLS/CRYPTO and critical roles. The
115+
UDP listener's131072-byte charge remains outside that prepaid envelope. Runtime
116+
subdivision reservations stop at the prepaid boundary; semantic resources still
117+
reach the shared ancestors. Completely unset policy preserves its old subdivision
118+
`min(393216, floor(quic_reassembly_bytes * 3 / 8))` for each pool and retains its
119+
existing runtime pressure behavior.
120+
121+
A successful resolution proves a bounded structural envelope and space for one
122+
maximum request alongside persistent records and critical state. It does not
123+
reserve all concurrent requests at their largest allowed sizes. Connections and
124+
streams still compete for shared budgets and can encounter ordinary resource
125+
refusal without changing the report.
126+
127+
## Enforcement, advertisement and retention
128+
129+
The report includes aggregate `budgets`, optional `http2`, `http3` and `quic`
130+
sections, and nested `initial_advertisement` values. H2 advertises the resolved
131+
request/decoder ceilings; H3 advertises its decoder/blocked ceilings and QUIC
132+
advertises initial request credit. Neither protocol advertises a header-list or
133+
field-section byte maximum. `max_header_list_bytes` and
134+
`max_field_section_bytes` remain absent even with explicit local header limits.
135+
Expanded size includes name/value bytes and32 bytes per field, as described in
136+
[RFC9114 §4.2.2](https://www.rfc-editor.org/rfc/rfc9114.html#section-4.2.2).
137+
138+
Normal request retirement replenishes QUIC MAX_STREAMS credit. Initial credit is
139+
not a lifetime request count. Terminal descriptors remain retained to prevent
140+
stream recreation; eventually the total record ledger is authoritative. Larger
141+
ledgers support more retirements while keeping this bound. See
142+
[RFC9000 §§4.6,18.2](https://www.rfc-editor.org/rfc/rfc9000.html).
143+
144+
The inspected QUIC datagram values are fixed read-only implementation policy.
145+
There is no datagram-size setter, PMTU growth or new key-update behavior here.
146+
Existing per-key usage guards remain in force; this API makes no promise about
147+
graceful connection-local key exhaustion.

‎docs/v3-source-layout.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,11 @@ Windows execution belongs to CI/the v3 PR.
354354
|---|---|---|
355355
| `src/detail/connection_engine_config.cpp` | `src/httpserver/detail/connection_engine.hpp` | Compiled operations extracted without algorithm changes |
356356
| `src/detail/io_poll_backend_timers.cpp` | `src/httpserver/detail/io_poll_backend.hpp`, `src/httpserver/detail/io_timer_sweep.hpp` | Compiled operations extracted without algorithm changes |
357+
| `src/httpserver/server/protocol_limits.hpp` | `src/httpserver/server/protocol_limits.hpp` | Installed capacity option/report value types; implementation in public options/server units |
358+
| `src/httpserver/detail/protocol_limits.hpp` | `src/httpserver/detail/protocol_limits.hpp` | Private immutable plan and planning declarations |
359+
| `src/detail/protocol_limits.cpp` | `src/httpserver/detail/protocol_limits.hpp` | Common semantic capacity resolution and compiled availability |
360+
| `src/detail/http3_capacity.cpp` | `src/httpserver/detail/protocol_limits.hpp` | Pure prepaid H3/QUIC envelope using owner accounting helpers |
361+
| `src/httpserver/detail/capacity_math.hpp` | `src/httpserver/detail/capacity_math.hpp` | Private checked arithmetic primitives |
357362

358363
## Retained inline definitions
359364

@@ -1033,3 +1038,8 @@ operation, including true templates and constant evaluation operations.
10331038
| `src/httpserver/detail/websocket_session_state.hpp` | `httpserver::websocket::detail::session_wait::bind_frame(httpserver::detail::task_frame_base *)` | trivial accessor/value operation |
10341039
| `src/httpserver/detail/websocket_session_state.hpp` | `httpserver::websocket::detail::session_wait::await_ready()` | trivial accessor/value operation |
10351040
| `src/httpserver/detail/websocket_session_state.hpp` | `httpserver::websocket::detail::session_wait::await_resume()` | trivial accessor/value operation |
1041+
| `src/httpserver/server/options.hpp` | `httpserver::server::server_options::http2()` (mutable/const) | trivial accessor/value operation |
1042+
| `src/httpserver/server/options.hpp` | `httpserver::server::server_options::http3()` (mutable/const) | trivial accessor/value operation |
1043+
| `src/httpserver/server/options.hpp` | `httpserver::server::server_options::quic()` (mutable/const) | trivial accessor/value operation |
1044+
| `src/httpserver/detail/capacity_math.hpp` | `httpserver::detail::capacity_add()` | small checked byte/arithmetic primitive |
1045+
| `src/httpserver/detail/capacity_math.hpp` | `httpserver::detail::capacity_multiply()` | small checked byte/arithmetic primitive |

‎examples/README.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,3 +386,7 @@ v3 development; no automatic v2 port is implied.
386386
- [turbo_mode.cpp](turbo_mode.cpp)
387387
- [url_registration.cpp](url_registration.cpp)
388388
- [websocket_echo.cpp](websocket_echo.cpp)
389+
390+
`v3_protocol_options` also demonstrates typed H2/H3/QUIC capacity profiles, pure
391+
`resolve_limits()` and immutable `effective_limits()` inspection with adequate
392+
shared budgets. See [protocol capacity policy](../docs/v3-protocol-limits.md).

‎examples/v3_protocol_options.cpp‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <iostream>
66
#include <fstream>
77
#include <memory>
8+
#include <string>
89
#include <httpserver/features.hpp>
910
#include <httpserver/server/server.hpp>
1011
#include <httpserver/server/tls_credentials.hpp>
@@ -41,13 +42,30 @@ bool demonstrate(const char* label, httpserver::http::protocol protocol, bool tl
4142
}
4243
options.add_listener({"127.0.0.1", 0, tls, credentials,
4344
protocol == http::protocol::http_3 ? srv::listener_transport::udp : srv::listener_transport::tcp});
45+
options.budgets().set(srv::resource::header_fields, 4096);
46+
options.budgets().set(srv::resource::quic_reassembly_bytes, 16 * 1024 * 1024);
47+
if (protocol == http::protocol::http_2) {
48+
options.http2().emplace().max_concurrent_requests = 192;
49+
options.http2()->decoder_table_bytes = 8192;
50+
options.http2()->encoder_table_bytes = 16384;
51+
}
52+
if (protocol == http::protocol::http_3) {
53+
options.http3().emplace().max_concurrent_requests = 24;
54+
options.http3()->max_blocked_headers = 32;
55+
options.quic().emplace().max_stream_records = 96;
56+
}
57+
srv::effective_protocol_limits limits;
58+
const auto resolved = options.resolve_limits(limits);
4459
const auto validated = options.validate();
4560
srv::native_server server(options);
61+
const auto inspected = server.effective_limits(limits);
62+
if (inspected.code() != resolved.code()) return false;
4663
const auto listened = server.listen();
4764
const auto port = server.get_bound_port(0);
4865
std::cout << label << " validate=" << outcome_name(validated)
4966
<< " listen=" << outcome_name(listened) << " port=" << port << '\n';
5067
server.stop();
68+
if (server.effective_limits(limits).code() != inspected.code()) return false;
5169
if (!tls || available) return validated.ok() && listened.ok() && port != 0;
5270
return listened.code() == http::outcome_code::not_supported && port == 0;
5371
}

0 commit comments

Comments
 (0)