Project homepage Mailing List  Warmcat.com  API Docs  Github Mirror 
    npro  
 Modern all-safe Rust Network Protocol library supporting h1, h2, h3, ws, wt sans-IO and with socket IO + tls
git clone https://npro.rs/repo/npro
Author[]Andy Green <andy@warmcat.com> 2026-10-04 06:24 UTC
Committer[]Andy Green <andy@warmcat.com> 2026-10-05 06:32 UTC
Tree753f3f943a307ee96c36f4f66eddc4c16ba1ef44   Raw Patch
 
npro-test: hold the state machines to C's table and C's trace
npro-test: hold the state machines to C's table and C's trace

Copy C's state machine oracles into crates/npro-test/states/, from C
2dc2a33c, with scripts/sync-c-states.sh to refresh them: the rows of
C's event table, verbatim, and the 373 distinct edges C's whole ctest
suite takes, built with LWS_WITH_STATE_TRACE and LWS_WITH_STATE_CHECK
(248 tests, all passing, so every edge is one the table allows), sorted
and without each connection's tag.  A few edges depend on timing, so
runs can differ by an edge or two; the README says so.

tests/states.rs has a second model of C's machines, written in C's
terms (names, the state word's fields, C's setters and check), reading
its rows from the copy of C's table.  Every state npro's machines reach
from a birth, 3,022 of them walked exhaustively, is driven with every
event, with and without each role a site can give, and with the socket's
usability changed, through both: 604,400 cases, which must agree on
refusing, on the machines after, on the setter and on the trace line.
Then each of C's 202 edges between npro's roles must be one npro takes,
and the structural invariants must hold in every reachable state.

Checked by planting bugs in the port: a wrong row, a missing row, a
setter keeping the transport phase or a restarted connection's dead
socket, the close going backwards, no unusable-socket rule, and tracing
edges C does not.  Each fails the tests.

npro-test takes npro-core as a dev-dependency for this; its other test
targets expect unused_crate_dependencies, with why.  The heavy tests are
left to native runs under Miri.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019kg5Eemy68ZaqDBcUJQG6J
diff --git a/Cargo.lock b/Cargo.lock index 3c2e2c6..44fd6df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,3 +21,6 @@ dependencies = [ [[package]] name = "npro-test" version = "0.0.2" +dependencies = [ + "npro-core", +] diff --git a/crates/npro-test/Cargo.toml b/crates/npro-test/Cargo.toml index 8b26318..315e51e 100644 --- a/crates/npro-test/Cargo.toml +++ b/crates/npro-test/Cargo.toml @@ -9,5 +9,8 @@ license.workspace = true homepage.workspace = true repository.workspace = true +[dev-dependencies] +npro-core = { path = "../npro-core" } + [lints] workspace = true diff --git a/crates/npro-test/src/lib.rs b/crates/npro-test/src/lib.rs index 5f2f67c..80d302b 100644 --- a/crates/npro-test/src/lib.rs +++ b/crates/npro-test/src/lib.rs @@ -8,6 +8,13 @@ //! this crate's `transcripts/` directory. #![forbid(unsafe_code)] +#![cfg_attr( + test, + expect( + unused_crate_dependencies, + reason = "npro-core is a dev-dependency for the state tests in tests/, which the unit tests do not use" + ) +)] mod transcript; diff --git a/crates/npro-test/states/C-COMMIT b/crates/npro-test/states/C-COMMIT new file mode 100644 index 0000000..277d0a9 --- /dev/null +++ b/crates/npro-test/states/C-COMMIT @@ -0,0 +1 @@ +2dc2a33c572e6131298898cb6c1ba73c1a9f456e client: mux queue deadline and idle check only exist with a mux role diff --git a/crates/npro-test/states/README.md b/crates/npro-test/states/README.md new file mode 100644 index 0000000..832a178 --- /dev/null +++ b/crates/npro-test/states/README.md @@ -0,0 +1,33 @@ +# C's connection state machines, as oracles + +npro's connection state machines (`npro_core::state`) are C lws' +(`lib/sansio/wsi-state.c`, specified in C's +`READMEs/README.wsi-state-machines.md`). These files, copied from the C +tree at the commit in `C-COMMIT`, are what `tests/states.rs` holds them to. + +| file | what it is | +|---|---| +| `wsi-event-edges.txt` | the rows of C's event table, `lws_wsi_event_edges[]`, verbatim | +| `edges.txt` | every distinct state edge C's ctest suite took, from a build with `LWS_WITH_STATE_TRACE`, sorted, with each connection's tag removed | +| `C-COMMIT` | the C commit both came from | + +The test drives every state npro's machines can reach from a connection's +birth with every event, both through npro and through a model of C's +machines written in C's terms that reads its rows from +`wsi-event-edges.txt`, and requires them to agree: on refusing it, on the +machines after, on the setter, and on the trace line. Then every edge in +`edges.txt` between npro's roles must be one npro takes. + +The C suite ran with `LWS_WITH_STATE_CHECK` too, which aborts on any edge +the table does not allow, and passed: the observed edges are all the +table's. `edges.txt` holds every role's edges, not only npro's, so that +the roles arriving later are checked against the same run. + +## Refreshing + +```sh +scripts/sync-c-states.sh ~/libwebsockets +``` + +builds C with the trace and the check, runs its ctest suite, and replaces +these files. Say in the commit what moved, and why npro follows it. diff --git a/crates/npro-test/states/edges.txt b/crates/npro-test/states/edges.txt new file mode 100644 index 0000000..21b112a --- /dev/null +++ b/crates/npro-test/states/edges.txt @@ -0,0 +1,373 @@ +LRS (none)/-:(zero) -> (none)/-:UNCONNECTED role_transition +LRS (none)/-:(zero) -> listen/-:UNCONNECTED role_transition +LRS (none)/-:(zero) -> netlink/-:UNCONNECTED role_transition +LRS (none)/-:(zero) -> pipe/-:UNCONNECTED role_transition +LRS (none)/-:(zero) -> raw-file/-:UNCONNECTED role_transition +LRS (none)/-:UNCONNECTED -> (none)/S:UNCONNECTED role_transition ev=SERVER_SIDE +LRS (none)/-:UNCONNECTED -> h1/C:UNCONNECTED role_transition ev=CLIENT_BIND +LRS (none)/-:UNCONNECTED -> raw-skt/C:UNCONNECTED role_transition ev=CLIENT_BIND +LRS (none)/S:UNCONNECTED -> h1/S:HEADERS role_transition ev=ADOPTED +LRS (none)/S:UNCONNECTED -> h1/S:SSL_INIT role_transition ev=ADOPTED_TLS +LRS (none)/S:UNCONNECTED -> h2/C:UNCONNECTED role_transition ev=MUX_INSERTED +LRS (none)/S:UNCONNECTED -> h2/S:H2_AWAIT_PREFACE role_transition ev=H2_SELECTED +LRS (none)/S:UNCONNECTED -> h2/S:UNCONNECTED role_transition ev=MUX_INSERTED +LRS (none)/S:UNCONNECTED -> raw-file/S:ESTABLISHED role_transition ev=ADOPTED +LRS (none)/S:UNCONNECTED -> raw-skt/S:ESTABLISHED role_transition ev=ADOPTED +LRS (none)/S:UNCONNECTED -> raw-skt/S:SSL_INIT role_transition ev=ADOPTED_TLS +LRS h1/C:DEAD_SOCKET -> h1/C:DEAD_SOCKET+told set_close ev=USER_TOLD +LRS h1/C:DEAD_SOCKET+failed -> h1/C:DEAD_SOCKET+failed+unusable set_unusable +LRS h1/C:DEAD_SOCKET+failed+unusable -> h1/C:DEAD_SOCKET+failed set_unusable +LRS h1/C:DEAD_SOCKET+restarting -> h1/C:DEAD_SOCKET+restarting+unusable set_unusable +LRS h1/C:DEAD_SOCKET+restarting+unusable -> h1/C:UNCONNECTED role_transition ev=RESTART +LRS h1/C:DEAD_SOCKET+told -> h1/C:DEAD_SOCKET+told+unusable set_unusable +LRS h1/C:DEAD_SOCKET+told+unusable -> h1/C:DEAD_SOCKET+told set_unusable +LRS h1/C:ESTABLISHED -> h1/C:ESTABLISHED+closing set_close ev=CLOSE_ENTERED +LRS h1/C:ESTABLISHED -> h1/C:IDLING set_state ev=TXN_COMPLETED +LRS h1/C:ESTABLISHED -> h1/C:UNCONNECTED+failed set_transport ev=CONN_FAILED +LRS h1/C:ESTABLISHED -> h1/C:UNCONNECTED+restarting set_transport ev=RETARGET +LRS h1/C:ESTABLISHED -> h1/C:WAITING_SERVER_REPLY set_state ev=RESP_INTERIM +LRS h1/C:ESTABLISHED+closing -> h1/C:DEAD_SOCKET set_close ev=SOCKET_GONE +LRS h1/C:H1C_ISSUE_HANDSHAKE -> h1/C:ISSUE_HTTP_BODY set_state ev=REQ_HDRS_SENT_BODY +LRS h1/C:H1C_ISSUE_HANDSHAKE -> h1/C:UNCONNECTED+failed set_transport ev=CONN_FAILED +LRS h1/C:H1C_ISSUE_HANDSHAKE -> h1/C:WAITING_SERVER_REPLY set_state ev=REQ_HDRS_SENT +LRS h1/C:H1C_ISSUE_HANDSHAKE -> h1/C:WAITING_SSL set_transport ev=TLS_START +LRS h1/C:H1C_ISSUE_HANDSHAKE -> h2/C:H2_AWAIT_PREFACE role_transition ev=H2_SELECTED +LRS h1/C:H1C_ISSUE_HANDSHAKE2 -> h1/C:ISSUE_HTTP_BODY set_state ev=REQ_HDRS_SENT_BODY +LRS h1/C:H1C_ISSUE_HANDSHAKE2 -> h1/C:WAITING_SERVER_REPLY set_state ev=REQ_HDRS_SENT +LRS h1/C:H1C_ISSUE_HANDSHAKE2 -> h2/C:H2_WAITING_TO_SEND_HEADERS role_transition ev=MUX_STREAM_ADOPTED +LRS h1/C:H2_WAITING_TO_SEND_HEADERS -> h1/C:H1C_ISSUE_HANDSHAKE2 set_state ev=REQ_ISSUE +LRS h1/C:H2_WAITING_TO_SEND_HEADERS -> h1/C:H2_WAITING_TO_SEND_HEADERS+closing set_close ev=CLOSE_ENTERED +LRS h1/C:H2_WAITING_TO_SEND_HEADERS -> h1/C:H2_WAITING_TO_SEND_HEADERS+unusable set_unusable +LRS h1/C:H2_WAITING_TO_SEND_HEADERS+closing -> h1/C:DEAD_SOCKET set_close ev=SOCKET_GONE +LRS h1/C:H2_WAITING_TO_SEND_HEADERS+unusable -> h1/C:UNCONNECTED+failed+unusable set_transport ev=CONN_FAILED +LRS h1/C:IDLING -> h1/C:IDLING+closing set_close ev=CLOSE_ENTERED +LRS h1/C:IDLING -> h1/C:UNCONNECTED+failed set_transport ev=CONN_FAILED +LRS h1/C:IDLING+closing -> h1/C:DEAD_SOCKET set_close ev=SOCKET_GONE +LRS h1/C:ISSUE_HTTP_BODY -> h1/C:WAITING_SERVER_REPLY set_state ev=REQ_BODY_SENT +LRS h1/C:UNCONNECTED -> h1/C:H1C_ISSUE_HANDSHAKE2 set_state ev=TRANSPORT_UP +LRS h1/C:UNCONNECTED -> h1/C:H2_WAITING_TO_SEND_HEADERS set_state ev=QUEUED +LRS h1/C:UNCONNECTED -> h1/C:WAITING_CONNECT set_transport ev=CONNECT_START +LRS h1/C:UNCONNECTED -> h1/C:WAITING_DNS set_transport ev=DNS_START +LRS h1/C:UNCONNECTED -> h2/C:H2_WAITING_TO_SEND_HEADERS role_transition ev=MUX_STREAM_ADOPTED +LRS h1/C:UNCONNECTED+failed -> h1/C:UNCONNECTED+failed+closing set_close ev=CLOSE_ENTERED +LRS h1/C:UNCONNECTED+failed -> h1/C:UNCONNECTED+failed+unusable set_unusable +LRS h1/C:UNCONNECTED+failed+closing -> h1/C:DEAD_SOCKET+failed set_close ev=SOCKET_GONE +LRS h1/C:UNCONNECTED+failed+closing+unusable -> h1/C:DEAD_SOCKET+failed+unusable set_close ev=SOCKET_GONE +LRS h1/C:UNCONNECTED+failed+unusable -> h1/C:UNCONNECTED+failed+closing+unusable set_close ev=CLOSE_ENTERED +LRS h1/C:UNCONNECTED+restarting -> h1/C:UNCONNECTED+restarting+closing set_close ev=CLOSE_ENTERED +LRS h1/C:UNCONNECTED+restarting+closing -> h1/C:DEAD_SOCKET+restarting set_close ev=SOCKET_GONE +LRS h1/C:WAITING_CONNECT -> h1/C:H1C_ISSUE_HANDSHAKE set_state ev=SOCKET_CONNECTED +LRS h1/C:WAITING_CONNECT -> h1/C:UNCONNECTED+failed set_transport ev=CONN_FAILED +LRS h1/C:WAITING_CONNECT -> h1/C:WAITING_CONNECT+closing set_close ev=CLOSE_ENTERED +LRS h1/C:WAITING_CONNECT -> h1/C:WAITING_PROXY_REPLY set_transport ev=PROXY_CONNECT_SENT +LRS h1/C:WAITING_CONNECT+closing -> h1/C:WAITING_CONNECT+closing+unusable set_unusable +LRS h1/C:WAITING_CONNECT+closing+unusable -> h1/C:UNCONNECTED+failed+closing+unusable set_transport ev=CONN_FAILED +LRS h1/C:WAITING_DNS -> h1/C:UNCONNECTED set_transport ev=DNS_RETRY +LRS h1/C:WAITING_DNS -> h1/C:UNCONNECTED+failed set_transport ev=CONN_FAILED +LRS h1/C:WAITING_DNS -> h1/C:WAITING_CONNECT set_transport ev=CONNECT_START +LRS h1/C:WAITING_DNS -> h1/C:WAITING_DNS+unusable set_unusable +LRS h1/C:WAITING_PROXY_REPLY -> h1/C:H1C_ISSUE_HANDSHAKE set_state ev=SOCKET_CONNECTED +LRS h1/C:WAITING_SERVER_REPLY -> h1/C:ESTABLISHED set_state ev=RESP_HDRS +LRS h1/C:WAITING_SERVER_REPLY -> h1/C:UNCONNECTED+failed set_transport ev=CONN_FAILED +LRS h1/C:WAITING_SERVER_REPLY -> h1/C:WAITING_SERVER_REPLY+closing set_close ev=CLOSE_ENTERED +LRS h1/C:WAITING_SERVER_REPLY -> h1/C:WAITING_SERVER_REPLY+unusable set_unusable +LRS h1/C:WAITING_SERVER_REPLY -> ws/C:ESTABLISHED role_transition ev=WS_UPGRADED +LRS h1/C:WAITING_SERVER_REPLY+closing -> h1/C:DEAD_SOCKET set_close ev=SOCKET_GONE +LRS h1/C:WAITING_SERVER_REPLY+unusable -> h1/C:UNCONNECTED+failed+unusable set_transport ev=CONN_FAILED +LRS h1/C:WAITING_SSL -> h1/C:ISSUE_HTTP_BODY set_state ev=REQ_HDRS_SENT_BODY +LRS h1/C:WAITING_SSL -> h1/C:WAITING_SERVER_REPLY set_state ev=REQ_HDRS_SENT +LRS h1/C:WAITING_SSL -> h1/C:WAITING_SSL+unusable set_unusable +LRS h1/C:WAITING_SSL -> h2/C:H2_AWAIT_PREFACE role_transition ev=H2_SELECTED +LRS h1/C:WAITING_SSL+unusable -> h1/C:UNCONNECTED+failed+unusable set_transport ev=CONN_FAILED +LRS h1/S:BODY -> h1/S:BODY+closing set_close ev=CLOSE_ENTERED +LRS h1/S:BODY -> h1/S:DISCARD_BODY set_state ev=BODY_DISCARD +LRS h1/S:BODY -> h1/S:DOING_TRANSACTION set_state ev=BODY_COMPLETE +LRS h1/S:BODY+closing -> h1/S:SHUTDOWN set_close ev=CLOSE_STAGED +LRS h1/S:DEAD_SOCKET -> h1/S:DEAD_SOCKET+told set_close ev=USER_TOLD +LRS h1/S:DEAD_SOCKET -> h1/S:DEAD_SOCKET+unusable set_unusable +LRS h1/S:DEAD_SOCKET+told -> h1/S:DEAD_SOCKET+told+unusable set_unusable +LRS h1/S:DEAD_SOCKET+told+unusable -> h1/S:DEAD_SOCKET+told set_unusable +LRS h1/S:DEAD_SOCKET+unusable -> h1/S:DEAD_SOCKET set_unusable +LRS h1/S:DEAD_SOCKET+unusable -> h1/S:DEAD_SOCKET+told+unusable set_close ev=USER_TOLD +LRS h1/S:DISCARD_BODY -> h1/S:TXN_COMPLETED set_state ev=TXN_COMPLETED +LRS h1/S:DOING_TRANSACTION -> h1/S:BODY set_state ev=BODY_BEGIN +LRS h1/S:DOING_TRANSACTION -> h1/S:DISCARD_BODY set_state ev=BODY_DISCARD +LRS h1/S:DOING_TRANSACTION -> h1/S:DOING_TRANSACTION+closing set_close ev=CLOSE_ENTERED +LRS h1/S:DOING_TRANSACTION -> h1/S:DOING_TRANSACTION+unusable set_unusable +LRS h1/S:DOING_TRANSACTION -> h1/S:ISSUING_FILE set_state ev=FILE_BEGIN +LRS h1/S:DOING_TRANSACTION -> h1/S:TXN_COMPLETED set_state ev=TXN_COMPLETED +LRS h1/S:DOING_TRANSACTION -> h1/S:TXN_COMPLETING set_state ev=TXN_COMPLETING +LRS h1/S:DOING_TRANSACTION+closing -> h1/S:SHUTDOWN set_close ev=CLOSE_STAGED +LRS h1/S:DOING_TRANSACTION+closing+unusable -> h1/S:DEAD_SOCKET+unusable set_close ev=SOCKET_GONE +LRS h1/S:DOING_TRANSACTION+unusable -> h1/S:DOING_TRANSACTION+closing+unusable set_close ev=CLOSE_ENTERED +LRS h1/S:ESTABLISHED -> h1/S:DOING_TRANSACTION set_state ev=ACTION_BEGIN +LRS h1/S:ESTABLISHED -> h1/S:ESTABLISHED+closing set_close ev=CLOSE_ENTERED +LRS h1/S:ESTABLISHED -> h1/S:ISSUING_FILE set_state ev=FILE_BEGIN +LRS h1/S:ESTABLISHED -> h1/S:TXN_COMPLETED set_state ev=TXN_COMPLETED +LRS h1/S:ESTABLISHED+closing -> h1/S:SHUTDOWN set_close ev=CLOSE_STAGED +LRS h1/S:H1_UPGRADE -> h1/S:DISCARD_BODY set_state ev=BODY_DISCARD +LRS h1/S:H1_UPGRADE -> h1/S:ESTABLISHED set_state ev=REQ_PLAIN_HTTP +LRS h1/S:H1_UPGRADE -> h1/S:H1_UPGRADE+closing set_close ev=CLOSE_ENTERED +LRS h1/S:H1_UPGRADE -> h1/S:TXN_COMPLETED set_state ev=TXN_COMPLETED +LRS h1/S:H1_UPGRADE -> h1/S:TXN_COMPLETING set_state ev=TXN_COMPLETING +LRS h1/S:H1_UPGRADE -> h2/S:H2_AWAIT_PREFACE role_transition ev=H2_SELECTED +LRS h1/S:H1_UPGRADE -> ws/S:ESTABLISHED role_transition ev=WS_UPGRADED +LRS h1/S:H1_UPGRADE+closing -> h1/S:SHUTDOWN set_close ev=CLOSE_STAGED +LRS h1/S:HEADERS -> h1/S:H1_UPGRADE set_state ev=REQ_HDRS_COMPLETE +LRS h1/S:HEADERS -> h1/S:HEADERS+closing set_close ev=CLOSE_ENTERED +LRS h1/S:HEADERS -> h1/S:HEADERS+unusable set_unusable +LRS h1/S:HEADERS -> h2/S:H2_AWAIT_PREFACE role_transition ev=H2_SELECTED +LRS h1/S:HEADERS+closing -> h1/S:DEAD_SOCKET set_close ev=SOCKET_GONE +LRS h1/S:HEADERS+closing -> h1/S:SHUTDOWN set_close ev=CLOSE_STAGED +LRS h1/S:HEADERS+closing+unusable -> h1/S:DEAD_SOCKET+unusable set_close ev=SOCKET_GONE +LRS h1/S:HEADERS+unusable -> h1/S:HEADERS+closing+unusable set_close ev=CLOSE_ENTERED +LRS h1/S:ISSUING_FILE -> h1/S:ESTABLISHED set_state ev=FILE_COMPLETE +LRS h1/S:ISSUING_FILE -> h1/S:ISSUING_FILE+unusable set_unusable +LRS h1/S:ISSUING_FILE+closing+unusable -> h1/S:DEAD_SOCKET+unusable set_close ev=SOCKET_GONE +LRS h1/S:ISSUING_FILE+unusable -> h1/S:ISSUING_FILE+closing+unusable set_close ev=CLOSE_ENTERED +LRS h1/S:SHUTDOWN -> h1/S:DEAD_SOCKET set_close ev=SOCKET_GONE +LRS h1/S:SHUTDOWN -> h1/S:SHUTDOWN+unusable set_unusable +LRS h1/S:SHUTDOWN+unusable -> h1/S:DEAD_SOCKET+unusable set_close ev=SOCKET_GONE +LRS h1/S:SSL_ACK_PENDING -> h1/S:HEADERS set_state ev=TRANSPORT_UP +LRS h1/S:SSL_ACK_PENDING -> h1/S:SSL_ACK_PENDING+closing set_close ev=CLOSE_ENTERED +LRS h1/S:SSL_ACK_PENDING -> h1/S:SSL_ACK_PENDING+unusable set_unusable +LRS h1/S:SSL_ACK_PENDING+closing -> h1/S:SHUTDOWN set_close ev=CLOSE_STAGED +LRS h1/S:SSL_ACK_PENDING+closing+unusable -> h1/S:DEAD_SOCKET+unusable set_close ev=SOCKET_GONE +LRS h1/S:SSL_ACK_PENDING+unusable -> h1/S:SSL_ACK_PENDING+closing+unusable set_close ev=CLOSE_ENTERED +LRS h1/S:SSL_INIT -> h1/S:SSL_ACK_PENDING set_transport ev=TLS_ACCEPT_PENDING +LRS h1/S:TXN_COMPLETED -> h1/S:HEADERS set_state ev=TXN_DRAINED +LRS h1/S:TXN_COMPLETING -> h1/S:TXN_COMPLETED set_state ev=TXN_COMPLETED +LRS h2/C:BODY -> h2/C:BODY+closing set_close ev=CLOSE_ENTERED +LRS h2/C:BODY -> h2/C:BODY+unusable set_unusable +LRS h2/C:BODY+closing -> h2/C:DEAD_SOCKET set_close ev=SOCKET_GONE +LRS h2/C:BODY+closing+unusable -> h2/C:DEAD_SOCKET+unusable set_close ev=SOCKET_GONE +LRS h2/C:BODY+unusable -> h2/C:BODY+closing+unusable set_close ev=CLOSE_ENTERED +LRS h2/C:DEAD_SOCKET -> h2/C:DEAD_SOCKET+told set_close ev=USER_TOLD +LRS h2/C:DEAD_SOCKET -> h2/C:DEAD_SOCKET+unusable set_unusable +LRS h2/C:DEAD_SOCKET+failed -> h2/C:DEAD_SOCKET+failed+unusable set_unusable +LRS h2/C:DEAD_SOCKET+failed+unusable -> h2/C:DEAD_SOCKET+failed set_unusable +LRS h2/C:DEAD_SOCKET+restarting -> h2/C:DEAD_SOCKET+restarting+unusable set_unusable +LRS h2/C:DEAD_SOCKET+restarting+unusable -> h1/C:UNCONNECTED role_transition ev=RESTART +LRS h2/C:DEAD_SOCKET+told -> h2/C:DEAD_SOCKET+told+unusable set_unusable +LRS h2/C:DEAD_SOCKET+told+unusable -> h2/C:DEAD_SOCKET+told set_unusable +LRS h2/C:DEAD_SOCKET+unusable -> h2/C:DEAD_SOCKET set_unusable +LRS h2/C:DEAD_SOCKET+unusable -> h2/C:DEAD_SOCKET+told+unusable set_close ev=USER_TOLD +LRS h2/C:ESTABLISHED -> h2/C:BODY set_state ev=BODY_BEGIN +LRS h2/C:ESTABLISHED -> h2/C:ESTABLISHED+closing set_close ev=CLOSE_ENTERED +LRS h2/C:ESTABLISHED -> h2/C:ESTABLISHED+unusable set_unusable +LRS h2/C:ESTABLISHED -> h2/C:IDLING set_state ev=LAST_STREAM_CLOSED +LRS h2/C:ESTABLISHED -> h2/C:UNCONNECTED+failed set_transport ev=CONN_FAILED +LRS h2/C:ESTABLISHED -> h2/C:UNCONNECTED+restarting set_transport ev=RETARGET +LRS h2/C:ESTABLISHED+closing -> h2/C:DEAD_SOCKET set_close ev=SOCKET_GONE +LRS h2/C:ESTABLISHED+closing+unusable -> h2/C:DEAD_SOCKET+unusable set_close ev=SOCKET_GONE +LRS h2/C:ESTABLISHED+unusable -> h2/C:ESTABLISHED+closing+unusable set_close ev=CLOSE_ENTERED +LRS h2/C:H2_AWAIT_PREFACE -> h2/C:H2_WAITING_TO_SEND_HEADERS set_state ev=H2_PREFACE_SENT +LRS h2/C:H2_WAITING_TO_SEND_HEADERS -> h2/C:ESTABLISHED set_state ev=MUX_MIGRATED +LRS h2/C:H2_WAITING_TO_SEND_HEADERS -> h2/C:H2_WAITING_TO_SEND_HEADERS+closing set_close ev=CLOSE_ENTERED +LRS h2/C:H2_WAITING_TO_SEND_HEADERS -> h2/C:H2_WAITING_TO_SEND_HEADERS+unusable set_unusable +LRS h2/C:H2_WAITING_TO_SEND_HEADERS -> h2/C:ISSUE_HTTP_BODY set_state ev=REQ_HDRS_SENT_BODY +LRS h2/C:H2_WAITING_TO_SEND_HEADERS -> h2/C:WAITING_SERVER_REPLY set_state ev=REQ_HDRS_SENT +LRS h2/C:H2_WAITING_TO_SEND_HEADERS+closing -> h2/C:DEAD_SOCKET set_close ev=SOCKET_GONE +LRS h2/C:H2_WAITING_TO_SEND_HEADERS+closing+unusable -> h2/C:DEAD_SOCKET+unusable set_close ev=SOCKET_GONE +LRS h2/C:H2_WAITING_TO_SEND_HEADERS+unusable -> h2/C:H2_WAITING_TO_SEND_HEADERS+closing+unusable set_close ev=CLOSE_ENTERED +LRS h2/C:IDLING -> h2/C:ESTABLISHED set_state ev=CONN_REUSED +LRS h2/C:IDLING -> h2/C:IDLING+closing set_close ev=CLOSE_ENTERED +LRS h2/C:IDLING+closing -> h2/C:DEAD_SOCKET set_close ev=SOCKET_GONE +LRS h2/C:ISSUE_HTTP_BODY -> h2/C:WAITING_SERVER_REPLY set_state ev=REQ_BODY_SENT +LRS h2/C:UNCONNECTED -> h2/C:H2_WAITING_TO_SEND_HEADERS role_transition ev=MUX_STREAM_ADOPTED +LRS h2/C:UNCONNECTED+failed -> h2/C:UNCONNECTED+failed+closing set_close ev=CLOSE_ENTERED +LRS h2/C:UNCONNECTED+failed+closing -> h2/C:DEAD_SOCKET+failed set_close ev=SOCKET_GONE +LRS h2/C:UNCONNECTED+restarting -> h2/C:UNCONNECTED+restarting+closing set_close ev=CLOSE_ENTERED +LRS h2/C:UNCONNECTED+restarting+closing -> h2/C:DEAD_SOCKET+restarting set_close ev=SOCKET_GONE +LRS h2/C:WAITING_SERVER_REPLY -> h2/C:ESTABLISHED set_state ev=RESP_HDRS +LRS h2/C:WAITING_SERVER_REPLY -> h2/C:WAITING_SERVER_REPLY+closing set_close ev=CLOSE_ENTERED +LRS h2/C:WAITING_SERVER_REPLY -> h2/C:WAITING_SERVER_REPLY+unusable set_unusable +LRS h2/C:WAITING_SERVER_REPLY -> ws/Ce:ESTABLISHED role_transition ev=WS_UPGRADED +LRS h2/C:WAITING_SERVER_REPLY+closing -> h2/C:DEAD_SOCKET set_close ev=SOCKET_GONE +LRS h2/C:WAITING_SERVER_REPLY+closing+unusable -> h2/C:DEAD_SOCKET+unusable set_close ev=SOCKET_GONE +LRS h2/C:WAITING_SERVER_REPLY+unusable -> h2/C:WAITING_SERVER_REPLY+closing+unusable set_close ev=CLOSE_ENTERED +LRS h2/S:BODY -> h2/S:BODY+closing set_close ev=CLOSE_ENTERED +LRS h2/S:BODY -> h2/S:ESTABLISHED set_state ev=BODY_COMPLETE +LRS h2/S:BODY+closing -> h2/S:DEAD_SOCKET set_close ev=SOCKET_GONE +LRS h2/S:DEAD_SOCKET -> h2/S:DEAD_SOCKET+told set_close ev=USER_TOLD +LRS h2/S:DEAD_SOCKET -> h2/S:DEAD_SOCKET+unusable set_unusable +LRS h2/S:DEAD_SOCKET+told -> h2/S:DEAD_SOCKET+told+unusable set_unusable +LRS h2/S:DEAD_SOCKET+told+unusable -> h2/S:DEAD_SOCKET+told set_unusable +LRS h2/S:DEAD_SOCKET+unusable -> h2/S:DEAD_SOCKET set_unusable +LRS h2/S:DEAD_SOCKET+unusable -> h2/S:DEAD_SOCKET+told+unusable set_close ev=USER_TOLD +LRS h2/S:DEFERRING_ACTION -> h2/S:DEFERRING_ACTION+closing set_close ev=CLOSE_ENTERED +LRS h2/S:DEFERRING_ACTION -> h2/S:DEFERRING_ACTION+unusable set_unusable +LRS h2/S:DEFERRING_ACTION -> h2/S:ESTABLISHED set_state ev=ACTION_DEFERRED_RUN +LRS h2/S:DEFERRING_ACTION+closing -> h2/S:DEAD_SOCKET set_close ev=SOCKET_GONE +LRS h2/S:DEFERRING_ACTION+closing+unusable -> h2/S:DEAD_SOCKET+unusable set_close ev=SOCKET_GONE +LRS h2/S:DEFERRING_ACTION+unusable -> h2/S:DEFERRING_ACTION+closing+unusable set_close ev=CLOSE_ENTERED +LRS h2/S:DOING_TRANSACTION -> h2/S:DOING_TRANSACTION+closing set_close ev=CLOSE_ENTERED +LRS h2/S:DOING_TRANSACTION+closing -> h2/S:DEAD_SOCKET set_close ev=SOCKET_GONE +LRS h2/S:ESTABLISHED -> h2/S:BODY set_state ev=BODY_BEGIN +LRS h2/S:ESTABLISHED -> h2/S:DOING_TRANSACTION set_state ev=ACTION_BEGIN +LRS h2/S:ESTABLISHED -> h2/S:ESTABLISHED+closing set_close ev=CLOSE_ENTERED +LRS h2/S:ESTABLISHED -> h2/S:ESTABLISHED+unusable set_unusable +LRS h2/S:ESTABLISHED -> h2/S:ISSUING_FILE set_state ev=FILE_BEGIN +LRS h2/S:ESTABLISHED -> h2/S:TXN_COMPLETING set_state ev=TXN_COMPLETING +LRS h2/S:ESTABLISHED -> ws/Se:ESTABLISHED role_transition ev=WS_UPGRADED +LRS h2/S:ESTABLISHED+closing -> h2/S:DEAD_SOCKET set_close ev=SOCKET_GONE +LRS h2/S:ESTABLISHED+closing -> h2/S:SHUTDOWN set_close ev=CLOSE_STAGED +LRS h2/S:ESTABLISHED+closing+unusable -> h2/S:DEAD_SOCKET+unusable set_close ev=SOCKET_GONE +LRS h2/S:ESTABLISHED+unusable -> h2/S:ESTABLISHED+closing+unusable set_close ev=CLOSE_ENTERED +LRS h2/S:H2_AWAIT_PREFACE -> h2/S:H2_AWAIT_PREFACE+closing set_close ev=CLOSE_ENTERED +LRS h2/S:H2_AWAIT_PREFACE -> h2/S:H2_AWAIT_PREFACE+unusable set_unusable +LRS h2/S:H2_AWAIT_PREFACE -> h2/S:H2_AWAIT_SETTINGS set_state ev=H2_PREFACE_RX +LRS h2/S:H2_AWAIT_PREFACE+closing -> h2/S:SHUTDOWN set_close ev=CLOSE_STAGED +LRS h2/S:H2_AWAIT_PREFACE+closing+unusable -> h2/S:DEAD_SOCKET+unusable set_close ev=SOCKET_GONE +LRS h2/S:H2_AWAIT_PREFACE+unusable -> h2/S:H2_AWAIT_PREFACE+closing+unusable set_close ev=CLOSE_ENTERED +LRS h2/S:H2_AWAIT_SETTINGS -> h2/S:ESTABLISHED set_state ev=H2_SETTINGS_ACKED +LRS h2/S:HEADERS -> h2/S:DEFERRING_ACTION set_state ev=REQ_HDRS_COMPLETE +LRS h2/S:HEADERS -> h2/S:HEADERS+closing set_close ev=CLOSE_ENTERED +LRS h2/S:HEADERS -> h2/S:HEADERS+unusable set_unusable +LRS h2/S:HEADERS -> h2/S:ISSUING_FILE set_state ev=FILE_BEGIN +LRS h2/S:HEADERS+closing -> h2/S:DEAD_SOCKET set_close ev=SOCKET_GONE +LRS h2/S:HEADERS+closing+unusable -> h2/S:DEAD_SOCKET+unusable set_close ev=SOCKET_GONE +LRS h2/S:HEADERS+unusable -> h2/S:HEADERS+closing+unusable set_close ev=CLOSE_ENTERED +LRS h2/S:ISSUING_FILE -> h2/S:ESTABLISHED set_state ev=FILE_COMPLETE +LRS h2/S:ISSUING_FILE -> h2/S:ISSUING_FILE+closing set_close ev=CLOSE_ENTERED +LRS h2/S:ISSUING_FILE -> h2/S:ISSUING_FILE+unusable set_unusable +LRS h2/S:ISSUING_FILE+closing -> h2/S:DEAD_SOCKET set_close ev=SOCKET_GONE +LRS h2/S:ISSUING_FILE+closing+unusable -> h2/S:DEAD_SOCKET+unusable set_close ev=SOCKET_GONE +LRS h2/S:ISSUING_FILE+unusable -> h2/S:ISSUING_FILE+closing+unusable set_close ev=CLOSE_ENTERED +LRS h2/S:SHUTDOWN -> h2/S:DEAD_SOCKET set_close ev=SOCKET_GONE +LRS h2/S:SHUTDOWN -> h2/S:SHUTDOWN+unusable set_unusable +LRS h2/S:SHUTDOWN+unusable -> h2/S:DEAD_SOCKET+unusable set_close ev=SOCKET_GONE +LRS h2/S:TXN_COMPLETING -> h2/S:TXN_COMPLETING+closing set_close ev=CLOSE_ENTERED +LRS h2/S:TXN_COMPLETING -> h2/S:TXN_COMPLETING+unusable set_unusable +LRS h2/S:TXN_COMPLETING+closing -> h2/S:DEAD_SOCKET set_close ev=SOCKET_GONE +LRS h2/S:TXN_COMPLETING+closing+unusable -> h2/S:DEAD_SOCKET+unusable set_close ev=SOCKET_GONE +LRS h2/S:TXN_COMPLETING+unusable -> h2/S:TXN_COMPLETING+closing+unusable set_close ev=CLOSE_ENTERED +LRS h2/S:UNCONNECTED -> h2/S:HEADERS set_state ev=STREAM_OPENED +LRS listen/-:DEAD_SOCKET -> listen/-:DEAD_SOCKET+unusable set_unusable +LRS listen/-:DEAD_SOCKET+unusable -> listen/-:DEAD_SOCKET set_unusable +LRS listen/-:UNCONNECTED -> listen/-:UNCONNECTED+closing set_close ev=CLOSE_ENTERED +LRS listen/-:UNCONNECTED -> listen/-:UNCONNECTED+unusable set_unusable +LRS listen/-:UNCONNECTED+closing -> listen/-:DEAD_SOCKET set_close ev=SOCKET_GONE +LRS listen/-:UNCONNECTED+closing+unusable -> listen/-:DEAD_SOCKET+unusable set_close ev=SOCKET_GONE +LRS listen/-:UNCONNECTED+unusable -> listen/-:UNCONNECTED+closing+unusable set_close ev=CLOSE_ENTERED +LRS netlink/-:DEAD_SOCKET -> netlink/-:DEAD_SOCKET+unusable set_unusable +LRS netlink/-:DEAD_SOCKET+unusable -> netlink/-:DEAD_SOCKET set_unusable +LRS netlink/-:UNCONNECTED -> netlink/-:UNCONNECTED+closing set_close ev=CLOSE_ENTERED +LRS netlink/-:UNCONNECTED+closing -> netlink/-:DEAD_SOCKET set_close ev=SOCKET_GONE +LRS pipe/-:DEAD_SOCKET -> pipe/-:DEAD_SOCKET+unusable set_unusable +LRS pipe/-:DEAD_SOCKET+unusable -> pipe/-:DEAD_SOCKET set_unusable +LRS pipe/-:UNCONNECTED -> pipe/-:UNCONNECTED+closing set_close ev=CLOSE_ENTERED +LRS pipe/-:UNCONNECTED+closing -> pipe/-:DEAD_SOCKET set_close ev=SOCKET_GONE +LRS raw-file/-:ESTABLISHED -> raw-file/-:ESTABLISHED+unusable set_unusable +LRS raw-file/-:ESTABLISHED+unusable -> raw-file/-:ESTABLISHED set_unusable +LRS raw-file/-:UNCONNECTED -> raw-file/-:ESTABLISHED role_transition ev=ADOPTED +LRS raw-file/S:ESTABLISHED -> raw-file/S:ESTABLISHED+unusable set_unusable +LRS raw-file/S:ESTABLISHED+unusable -> raw-file/S:ESTABLISHED set_unusable +LRS raw-skt/C:DEAD_SOCKET+failed -> raw-skt/C:DEAD_SOCKET+failed+unusable set_unusable +LRS raw-skt/C:DEAD_SOCKET+failed+unusable -> raw-skt/C:DEAD_SOCKET+failed set_unusable +LRS raw-skt/C:DEAD_SOCKET+told -> raw-skt/C:DEAD_SOCKET+told+unusable set_unusable +LRS raw-skt/C:DEAD_SOCKET+told+unusable -> raw-skt/C:DEAD_SOCKET+told set_unusable +LRS raw-skt/C:DEAD_SOCKET+unusable -> raw-skt/C:DEAD_SOCKET+told+unusable set_close ev=USER_TOLD +LRS raw-skt/C:ESTABLISHED -> raw-skt/C:ESTABLISHED+closing set_close ev=CLOSE_ENTERED +LRS raw-skt/C:ESTABLISHED -> raw-skt/C:ESTABLISHED+unusable set_unusable +LRS raw-skt/C:ESTABLISHED+closing -> raw-skt/C:ESTABLISHED+closing+unusable set_unusable +LRS raw-skt/C:ESTABLISHED+closing+unusable -> raw-skt/C:DEAD_SOCKET+unusable set_close ev=SOCKET_GONE +LRS raw-skt/C:ESTABLISHED+unusable -> raw-skt/C:ESTABLISHED+closing+unusable set_close ev=CLOSE_ENTERED +LRS raw-skt/C:UNCONNECTED -> raw-skt/C:WAITING_CONNECT set_transport ev=CONNECT_START +LRS raw-skt/C:UNCONNECTED -> raw-skt/C:WAITING_DNS set_transport ev=DNS_START +LRS raw-skt/C:UNCONNECTED+failed -> raw-skt/C:UNCONNECTED+failed+closing set_close ev=CLOSE_ENTERED +LRS raw-skt/C:UNCONNECTED+failed+closing -> raw-skt/C:UNCONNECTED+failed+closing+unusable set_unusable +LRS raw-skt/C:UNCONNECTED+failed+closing+unusable -> raw-skt/C:DEAD_SOCKET+failed+unusable set_close ev=SOCKET_GONE +LRS raw-skt/C:WAITING_CONNECT -> raw-skt/C:ESTABLISHED set_state ev=TRANSPORT_UP +LRS raw-skt/C:WAITING_CONNECT -> raw-skt/C:UNCONNECTED+failed set_transport ev=CONN_FAILED +LRS raw-skt/C:WAITING_CONNECT -> raw-skt/C:WAITING_PROXY_REPLY set_transport ev=PROXY_CONNECT_SENT +LRS raw-skt/C:WAITING_CONNECT -> raw-skt/C:WAITING_SSL set_transport ev=TLS_START +LRS raw-skt/C:WAITING_DNS -> raw-skt/C:WAITING_CONNECT set_transport ev=CONNECT_START +LRS raw-skt/C:WAITING_PROXY_REPLY -> raw-skt/C:ESTABLISHED set_state ev=TRANSPORT_UP +LRS raw-skt/C:WAITING_PROXY_REPLY -> raw-skt/C:WAITING_SSL set_transport ev=TLS_START +LRS raw-skt/C:WAITING_SSL -> raw-skt/C:ESTABLISHED set_state ev=TRANSPORT_UP +LRS raw-skt/S:DEAD_SOCKET+told -> raw-skt/S:DEAD_SOCKET+told+unusable set_unusable +LRS raw-skt/S:DEAD_SOCKET+told+unusable -> raw-skt/S:DEAD_SOCKET+told set_unusable +LRS raw-skt/S:DEAD_SOCKET+unusable -> raw-skt/S:DEAD_SOCKET+told+unusable set_close ev=USER_TOLD +LRS raw-skt/S:ESTABLISHED -> raw-skt/S:ESTABLISHED+closing set_close ev=CLOSE_ENTERED +LRS raw-skt/S:ESTABLISHED -> raw-skt/S:FLUSHING_BEFORE_CLOSE set_close ev=CLOSE_WHEN_FLUSHED +LRS raw-skt/S:ESTABLISHED+closing -> raw-skt/S:ESTABLISHED+closing+unusable set_unusable +LRS raw-skt/S:ESTABLISHED+closing+unusable -> raw-skt/S:DEAD_SOCKET+unusable set_close ev=SOCKET_GONE +LRS raw-skt/S:FLUSHING_BEFORE_CLOSE -> raw-skt/S:FLUSHING_BEFORE_CLOSE set_close ev=CLOSE_ENTERED +LRS raw-skt/S:FLUSHING_BEFORE_CLOSE -> raw-skt/S:FLUSHING_BEFORE_CLOSE+unusable set_unusable +LRS raw-skt/S:FLUSHING_BEFORE_CLOSE+unusable -> raw-skt/S:DEAD_SOCKET+unusable set_close ev=SOCKET_GONE +LRS raw-skt/S:SSL_ACK_PENDING -> raw-skt/S:ESTABLISHED set_state ev=TRANSPORT_UP +LRS raw-skt/S:SSL_INIT -> raw-skt/S:SSL_ACK_PENDING set_transport ev=TLS_ACCEPT_PENDING +LRS ws/C:AWAITING_CLOSE_ACK -> ws/C:DEAD_SOCKET set_close ev=SOCKET_GONE +LRS ws/C:DEAD_SOCKET -> ws/C:DEAD_SOCKET+told set_close ev=USER_TOLD +LRS ws/C:DEAD_SOCKET+told -> ws/C:DEAD_SOCKET+told+unusable set_unusable +LRS ws/C:DEAD_SOCKET+told+unusable -> ws/C:DEAD_SOCKET+told set_unusable +LRS ws/C:DEAD_SOCKET+unusable -> ws/C:DEAD_SOCKET+told+unusable set_close ev=USER_TOLD +LRS ws/C:ESTABLISHED -> ws/C:ESTABLISHED+closing set_close ev=CLOSE_ENTERED +LRS ws/C:ESTABLISHED -> ws/C:ESTABLISHED+unusable set_unusable +LRS ws/C:ESTABLISHED -> ws/C:RETURNED_CLOSE set_close ev=WS_PEER_CLOSE +LRS ws/C:ESTABLISHED+closing -> ws/C:DEAD_SOCKET set_close ev=SOCKET_GONE +LRS ws/C:ESTABLISHED+closing -> ws/C:WAITING_TO_SEND_CLOSE set_close ev=WS_CLOSE_INITIATED +LRS ws/C:ESTABLISHED+closing+unusable -> ws/C:DEAD_SOCKET+unusable set_close ev=SOCKET_GONE +LRS ws/C:ESTABLISHED+unusable -> ws/C:ESTABLISHED+closing+unusable set_close ev=CLOSE_ENTERED +LRS ws/C:FLUSHING_BEFORE_CLOSE -> ws/C:DEAD_SOCKET set_close ev=SOCKET_GONE +LRS ws/C:RETURNED_CLOSE -> ws/C:FLUSHING_BEFORE_CLOSE set_close ev=CLOSE_FLUSH +LRS ws/C:WAITING_TO_SEND_CLOSE -> ws/C:AWAITING_CLOSE_ACK set_close ev=WS_CLOSE_SENT +LRS ws/C:WAITING_TO_SEND_CLOSE -> ws/C:WAITING_TO_SEND_CLOSE+unusable set_unusable +LRS ws/C:WAITING_TO_SEND_CLOSE+unusable -> ws/C:DEAD_SOCKET+unusable set_close ev=SOCKET_GONE +LRS ws/Ce:AWAITING_CLOSE_ACK -> ws/Ce:DEAD_SOCKET set_close ev=SOCKET_GONE +LRS ws/Ce:DEAD_SOCKET -> ws/Ce:DEAD_SOCKET+told set_close ev=USER_TOLD +LRS ws/Ce:DEAD_SOCKET+told -> ws/Ce:DEAD_SOCKET+told+unusable set_unusable +LRS ws/Ce:DEAD_SOCKET+told+unusable -> ws/Ce:DEAD_SOCKET+told set_unusable +LRS ws/Ce:DEAD_SOCKET+unusable -> ws/Ce:DEAD_SOCKET+told+unusable set_close ev=USER_TOLD +LRS ws/Ce:ESTABLISHED -> ws/Ce:ESTABLISHED+closing set_close ev=CLOSE_ENTERED +LRS ws/Ce:ESTABLISHED -> ws/Ce:ESTABLISHED+unusable set_unusable +LRS ws/Ce:ESTABLISHED -> ws/Ce:RETURNED_CLOSE set_close ev=WS_PEER_CLOSE +LRS ws/Ce:ESTABLISHED+closing -> ws/Ce:WAITING_TO_SEND_CLOSE set_close ev=WS_CLOSE_INITIATED +LRS ws/Ce:ESTABLISHED+closing+unusable -> ws/Ce:DEAD_SOCKET+unusable set_close ev=SOCKET_GONE +LRS ws/Ce:ESTABLISHED+unusable -> ws/Ce:ESTABLISHED+closing+unusable set_close ev=CLOSE_ENTERED +LRS ws/Ce:RETURNED_CLOSE -> ws/Ce:DEAD_SOCKET set_close ev=SOCKET_GONE +LRS ws/Ce:WAITING_TO_SEND_CLOSE -> ws/Ce:AWAITING_CLOSE_ACK set_close ev=WS_CLOSE_SENT +LRS ws/Ce:WAITING_TO_SEND_CLOSE -> ws/Ce:WAITING_TO_SEND_CLOSE+unusable set_unusable +LRS ws/Ce:WAITING_TO_SEND_CLOSE+unusable -> ws/Ce:DEAD_SOCKET+unusable set_close ev=SOCKET_GONE +LRS ws/S:AWAITING_CLOSE_ACK -> ws/S:AWAITING_CLOSE_ACK+unusable set_unusable +LRS ws/S:AWAITING_CLOSE_ACK -> ws/S:SHUTDOWN set_close ev=CLOSE_STAGED +LRS ws/S:AWAITING_CLOSE_ACK+unusable -> ws/S:DEAD_SOCKET+unusable set_close ev=SOCKET_GONE +LRS ws/S:DEAD_SOCKET -> ws/S:DEAD_SOCKET+told set_close ev=USER_TOLD +LRS ws/S:DEAD_SOCKET+told -> ws/S:DEAD_SOCKET+told+unusable set_unusable +LRS ws/S:DEAD_SOCKET+told+unusable -> ws/S:DEAD_SOCKET+told set_unusable +LRS ws/S:DEAD_SOCKET+unusable -> ws/S:DEAD_SOCKET+told+unusable set_close ev=USER_TOLD +LRS ws/S:ESTABLISHED -> ws/S:ESTABLISHED+closing set_close ev=CLOSE_ENTERED +LRS ws/S:ESTABLISHED -> ws/S:ESTABLISHED+unusable set_unusable +LRS ws/S:ESTABLISHED -> ws/S:FLUSHING_BEFORE_CLOSE set_close ev=CLOSE_WHEN_FLUSHED +LRS ws/S:ESTABLISHED -> ws/S:RETURNED_CLOSE set_close ev=WS_PEER_CLOSE +LRS ws/S:ESTABLISHED+closing -> ws/S:DEAD_SOCKET set_close ev=SOCKET_GONE +LRS ws/S:ESTABLISHED+closing -> ws/S:FLUSHING_BEFORE_CLOSE set_close ev=CLOSE_FLUSH +LRS ws/S:ESTABLISHED+closing -> ws/S:WAITING_TO_SEND_CLOSE set_close ev=WS_CLOSE_INITIATED +LRS ws/S:ESTABLISHED+closing+unusable -> ws/S:DEAD_SOCKET+unusable set_close ev=SOCKET_GONE +LRS ws/S:ESTABLISHED+unusable -> ws/S:ESTABLISHED+closing+unusable set_close ev=CLOSE_ENTERED +LRS ws/S:FLUSHING_BEFORE_CLOSE -> ws/S:FLUSHING_BEFORE_CLOSE set_close ev=CLOSE_ENTERED +LRS ws/S:FLUSHING_BEFORE_CLOSE -> ws/S:SHUTDOWN set_close ev=CLOSE_STAGED +LRS ws/S:RETURNED_CLOSE -> ws/S:FLUSHING_BEFORE_CLOSE set_close ev=CLOSE_FLUSH +LRS ws/S:SHUTDOWN -> ws/S:DEAD_SOCKET set_close ev=SOCKET_GONE +LRS ws/S:SHUTDOWN -> ws/S:SHUTDOWN+unusable set_unusable +LRS ws/S:SHUTDOWN+unusable -> ws/S:DEAD_SOCKET+unusable set_close ev=SOCKET_GONE +LRS ws/S:WAITING_TO_SEND_CLOSE -> ws/S:AWAITING_CLOSE_ACK set_close ev=WS_CLOSE_SENT +LRS ws/S:WAITING_TO_SEND_CLOSE -> ws/S:WAITING_TO_SEND_CLOSE+unusable set_unusable +LRS ws/S:WAITING_TO_SEND_CLOSE+unusable -> ws/S:DEAD_SOCKET+unusable set_close ev=SOCKET_GONE +LRS ws/Se:AWAITING_CLOSE_ACK -> ws/Se:DEAD_SOCKET set_close ev=SOCKET_GONE +LRS ws/Se:DEAD_SOCKET -> ws/Se:DEAD_SOCKET+told set_close ev=USER_TOLD +LRS ws/Se:DEAD_SOCKET+told -> ws/Se:DEAD_SOCKET+told+unusable set_unusable +LRS ws/Se:DEAD_SOCKET+told+unusable -> ws/Se:DEAD_SOCKET+told set_unusable +LRS ws/Se:DEAD_SOCKET+unusable -> ws/Se:DEAD_SOCKET+told+unusable set_close ev=USER_TOLD +LRS ws/Se:ESTABLISHED -> ws/Se:ESTABLISHED+closing set_close ev=CLOSE_ENTERED +LRS ws/Se:ESTABLISHED -> ws/Se:ESTABLISHED+unusable set_unusable +LRS ws/Se:ESTABLISHED -> ws/Se:RETURNED_CLOSE set_close ev=WS_PEER_CLOSE +LRS ws/Se:ESTABLISHED+closing -> ws/Se:WAITING_TO_SEND_CLOSE set_close ev=WS_CLOSE_INITIATED +LRS ws/Se:ESTABLISHED+closing+unusable -> ws/Se:DEAD_SOCKET+unusable set_close ev=SOCKET_GONE +LRS ws/Se:ESTABLISHED+unusable -> ws/Se:ESTABLISHED+closing+unusable set_close ev=CLOSE_ENTERED +LRS ws/Se:FLUSHING_BEFORE_CLOSE -> ws/Se:FLUSHING_BEFORE_CLOSE+unusable set_unusable +LRS ws/Se:FLUSHING_BEFORE_CLOSE+unusable -> ws/Se:DEAD_SOCKET+unusable set_close ev=SOCKET_GONE +LRS ws/Se:RETURNED_CLOSE -> ws/Se:DEAD_SOCKET set_close ev=SOCKET_GONE +LRS ws/Se:RETURNED_CLOSE -> ws/Se:FLUSHING_BEFORE_CLOSE set_close ev=CLOSE_FLUSH +LRS ws/Se:WAITING_TO_SEND_CLOSE -> ws/Se:AWAITING_CLOSE_ACK set_close ev=WS_CLOSE_SENT diff --git a/crates/npro-test/states/wsi-event-edges.txt b/crates/npro-test/states/wsi-event-edges.txt new file mode 100644 index 0000000..ef8634d --- /dev/null +++ b/crates/npro-test/states/wsi-event-edges.txt @@ -0,0 +1,343 @@ + /* + * the transport finished: a server starts on the request, an h1 client + * on sending one, an mqtt client on its CONNECT, the rest are up + */ + { "h1", "C", LRS_UNCONNECTED, LWS_WSIEV_TRANSPORT_UP, NULL, NULL, LRS_H1C_ISSUE_HANDSHAKE2 }, /* inherited from an idle leader */ + { "h1", "C", LRS_WAITING_SSL, LWS_WSIEV_TRANSPORT_UP, NULL, NULL, LRS_H1C_ISSUE_HANDSHAKE2 }, + { "h1", "C", LRS_WAITING_CONNECT, LWS_WSIEV_TRANSPORT_UP, NULL, NULL, LRS_H1C_ISSUE_HANDSHAKE2 }, + { "h1", "C", LRS_H1C_ISSUE_HANDSHAKE, LWS_WSIEV_TRANSPORT_UP, NULL, NULL, LRS_H1C_ISSUE_HANDSHAKE2 }, + { "h1", "C", LRS_H1C_ISSUE_HANDSHAKE2, LWS_WSIEV_TRANSPORT_UP, NULL, NULL, LRS_H1C_ISSUE_HANDSHAKE2 }, + { "h1", "C", LRS_H2_WAITING_TO_SEND_HEADERS, LWS_WSIEV_TRANSPORT_UP, NULL, NULL, LRS_H2_WAITING_TO_SEND_HEADERS }, + { "h2", "C", LRS_H2_WAITING_TO_SEND_HEADERS, LWS_WSIEV_TRANSPORT_UP, NULL, NULL, LRS_H2_WAITING_TO_SEND_HEADERS }, + { "mqtt", "C", LRS_UNCONNECTED, LWS_WSIEV_TRANSPORT_UP, NULL, NULL, LRS_MQTTC_IDLE }, + { "mqtt", "C", LRS_WAITING_CONNECT, LWS_WSIEV_TRANSPORT_UP, NULL, NULL, LRS_MQTTC_IDLE }, + { "mqtt", "C", LRS_WAITING_SSL, LWS_WSIEV_TRANSPORT_UP, NULL, NULL, LRS_MQTTC_IDLE }, + { "mqtt", "C", LRS_WAITING_SOCKS_CONNECT_REPLY, LWS_WSIEV_TRANSPORT_UP, NULL, NULL, LRS_MQTTC_IDLE }, + { "mqtt", "C", LRS_WAITING_PROXY_REPLY, LWS_WSIEV_TRANSPORT_UP, NULL, NULL, LRS_MQTTC_IDLE }, + { "raw-skt", "C", LRS_WAITING_CONNECT, LWS_WSIEV_TRANSPORT_UP, NULL, NULL, LRS_ESTABLISHED }, + { "raw-skt", "C", LRS_WAITING_SSL, LWS_WSIEV_TRANSPORT_UP, NULL, NULL, LRS_ESTABLISHED }, + { "raw-skt", "C", LRS_WAITING_SOCKS_CONNECT_REPLY, LWS_WSIEV_TRANSPORT_UP, NULL, NULL, LRS_ESTABLISHED }, + { "raw-skt", "C", LRS_WAITING_PROXY_REPLY, LWS_WSIEV_TRANSPORT_UP, NULL, NULL, LRS_ESTABLISHED }, + /* the raw proxy's onward connections are raw clients too */ + { "raw-proxy", "C", LRS_WAITING_CONNECT, LWS_WSIEV_TRANSPORT_UP, NULL, NULL, LRS_ESTABLISHED }, + { "raw-proxy", "C", LRS_WAITING_SSL, LWS_WSIEV_TRANSPORT_UP, NULL, NULL, LRS_ESTABLISHED }, + { "raw-proxy", "C", LRS_WAITING_SOCKS_CONNECT_REPLY, LWS_WSIEV_TRANSPORT_UP, NULL, NULL, LRS_ESTABLISHED }, + { "raw-proxy", "C", LRS_WAITING_PROXY_REPLY, LWS_WSIEV_TRANSPORT_UP, NULL, NULL, LRS_ESTABLISHED }, + { "quic", "C", LRS_WAITING_SSL, LWS_WSIEV_TRANSPORT_UP, NULL, NULL, LRS_ESTABLISHED }, + { "quic", "C", LRS_WAITING_CONNECT, LWS_WSIEV_TRANSPORT_UP, NULL, NULL, LRS_ESTABLISHED }, + { "quic", "S", LRS_SSL_INIT, LWS_WSIEV_TRANSPORT_UP, NULL, NULL, LRS_ESTABLISHED }, + { "h1", "S", LRS_SSL_ACK_PENDING, LWS_WSIEV_TRANSPORT_UP, NULL, NULL, LRS_HEADERS }, + { "h1", "S", LRS_AWAITING_SSL_ACCEPT, LWS_WSIEV_TRANSPORT_UP, NULL, NULL, LRS_HEADERS }, + { "h1", "S", LRS_SSL_INIT, LWS_WSIEV_TRANSPORT_UP, NULL, NULL, LRS_HEADERS }, + { "*", "S", LRS_SSL_ACK_PENDING, LWS_WSIEV_TRANSPORT_UP, NULL, NULL, LRS_ESTABLISHED }, + { "*", "S", LRS_AWAITING_SSL_ACCEPT, LWS_WSIEV_TRANSPORT_UP, NULL, NULL, LRS_ESTABLISHED }, + { "*", "S", LRS_SSL_INIT, LWS_WSIEV_TRANSPORT_UP, NULL, NULL, LRS_ESTABLISHED }, + /* the non-tls fallback already established the wsi before the accept path reports the transport up */ + { "*", "S", LRS_ESTABLISHED, LWS_WSIEV_TRANSPORT_UP, NULL, NULL, LRS_ESTABLISHED }, + /* 0-RTT: the ALPN migration ran inside the handshake, so handshake-done finds the new nwsi established */ + { "quic", "*", LRS_ESTABLISHED, LWS_WSIEV_TRANSPORT_UP, NULL, NULL, LRS_ESTABLISHED }, + + /* an h1 client's tcp is up before its tls */ + { "h1", "C", LRS_WAITING_CONNECT, LWS_WSIEV_SOCKET_CONNECTED, NULL, NULL, LRS_H1C_ISSUE_HANDSHAKE }, + /* a socks or http CONNECT tunnel coming up is the socket connecting, for the protocol */ + { "h1", "C", LRS_WAITING_SOCKS_CONNECT_REPLY, LWS_WSIEV_SOCKET_CONNECTED, NULL, NULL, LRS_H1C_ISSUE_HANDSHAKE }, + { "h1", "C", LRS_WAITING_PROXY_REPLY, LWS_WSIEV_SOCKET_CONNECTED, NULL, NULL, LRS_H1C_ISSUE_HANDSHAKE }, + { "h1", "C", LRS_H1C_ISSUE_HANDSHAKE2, LWS_WSIEV_SOCKET_CONNECTED, NULL, NULL, LRS_H1C_ISSUE_HANDSHAKE2 }, + + /* a client request queued on, or issued by, a connection */ + /* any client role may queue behind a leader: raw-skt and mqtt do too */ + { "*", "C", LRS_UNCONNECTED, LWS_WSIEV_QUEUED, NULL, NULL, LRS_H2_WAITING_TO_SEND_HEADERS }, + { "*", "C", LRS_H1C_ISSUE_HANDSHAKE2, LWS_WSIEV_QUEUED, NULL, NULL, LRS_H2_WAITING_TO_SEND_HEADERS }, + { "h1", "C", LRS_H2_WAITING_TO_SEND_HEADERS, LWS_WSIEV_REQ_ISSUE, NULL, NULL, LRS_H1C_ISSUE_HANDSHAKE2 }, + { "quic", "C", LRS_H2_WAITING_TO_SEND_HEADERS, LWS_WSIEV_REQ_ISSUE, NULL, NULL, LRS_H1C_ISSUE_HANDSHAKE2 }, /* h3 queue pickup */ + { "h1", "C", LRS_ESTABLISHED, LWS_WSIEV_REQ_ISSUE, NULL, NULL, LRS_H1C_ISSUE_HANDSHAKE2 }, + { "h1", "C", LRS_IDLING, LWS_WSIEV_REQ_ISSUE, NULL, NULL, LRS_H1C_ISSUE_HANDSHAKE2 }, + { "h1", "C", LRS_WAITING_SERVER_REPLY, LWS_WSIEV_REQ_ISSUE, NULL, NULL, LRS_H1C_ISSUE_HANDSHAKE2 }, + + /* the request goes out, then its response is pending */ + { "h1", "C", LRS_WAITING_SSL, LWS_WSIEV_REQ_HDRS_SENT, NULL, NULL, LRS_WAITING_SERVER_REPLY }, + { "h1", "C", LRS_WAITING_CONNECT, LWS_WSIEV_REQ_HDRS_SENT, NULL, NULL, LRS_WAITING_SERVER_REPLY }, + { "h1", "C", LRS_H1C_ISSUE_HANDSHAKE, LWS_WSIEV_REQ_HDRS_SENT, NULL, NULL, LRS_WAITING_SERVER_REPLY }, + { "h1", "C", LRS_H1C_ISSUE_HANDSHAKE2, LWS_WSIEV_REQ_HDRS_SENT, NULL, NULL, LRS_WAITING_SERVER_REPLY }, + { "h2", "C", LRS_H2_WAITING_TO_SEND_HEADERS, LWS_WSIEV_REQ_HDRS_SENT, NULL, NULL, LRS_WAITING_SERVER_REPLY }, + { "h3", "C", LRS_H2_WAITING_TO_SEND_HEADERS, LWS_WSIEV_REQ_HDRS_SENT, NULL, NULL, LRS_WAITING_SERVER_REPLY }, + { "h1", "C", LRS_WAITING_SSL, LWS_WSIEV_REQ_HDRS_SENT_BODY, NULL, NULL, LRS_ISSUE_HTTP_BODY }, + { "h1", "C", LRS_WAITING_CONNECT, LWS_WSIEV_REQ_HDRS_SENT_BODY, NULL, NULL, LRS_ISSUE_HTTP_BODY }, + { "h1", "C", LRS_H1C_ISSUE_HANDSHAKE, LWS_WSIEV_REQ_HDRS_SENT_BODY, NULL, NULL, LRS_ISSUE_HTTP_BODY }, + { "h1", "C", LRS_H1C_ISSUE_HANDSHAKE2, LWS_WSIEV_REQ_HDRS_SENT_BODY, NULL, NULL, LRS_ISSUE_HTTP_BODY }, + { "h2", "C", LRS_H2_WAITING_TO_SEND_HEADERS, LWS_WSIEV_REQ_HDRS_SENT_BODY, NULL, NULL, LRS_ISSUE_HTTP_BODY }, + { "h3", "C", LRS_H2_WAITING_TO_SEND_HEADERS, LWS_WSIEV_REQ_HDRS_SENT_BODY, NULL, NULL, LRS_ISSUE_HTTP_BODY }, + { "*", "C", LRS_ISSUE_HTTP_BODY, LWS_WSIEV_REQ_BODY_SENT, NULL, NULL, LRS_WAITING_SERVER_REPLY }, + { "h1", "C", LRS_ESTABLISHED, LWS_WSIEV_RESP_INTERIM, NULL, NULL, LRS_WAITING_SERVER_REPLY }, + /* a ws client takes no response headers but the 101's: it is still waiting */ + { "h1", "C", LRS_WAITING_SERVER_REPLY, LWS_WSIEV_RESP_INTERIM, NULL, NULL, LRS_WAITING_SERVER_REPLY }, + + /* the response is done: idle, or an idle connection is picked up */ + { "h1", "C", LRS_ESTABLISHED, LWS_WSIEV_TXN_COMPLETED, NULL, NULL, LRS_IDLING }, + { "*", "C", LRS_IDLING, LWS_WSIEV_TXN_COMPLETED, NULL, NULL, LRS_IDLING }, + { "h2", "C", LRS_ESTABLISHED, LWS_WSIEV_LAST_STREAM_CLOSED, NULL, NULL, LRS_IDLING }, + { "quic", "C", LRS_ESTABLISHED, LWS_WSIEV_LAST_STREAM_CLOSED, NULL, NULL, LRS_IDLING }, + { "h2", "C", LRS_IDLING, LWS_WSIEV_CONN_REUSED, NULL, NULL, LRS_ESTABLISHED }, + { "quic", "C", LRS_IDLING, LWS_WSIEV_CONN_REUSED, NULL, NULL, LRS_ESTABLISHED }, + + /* mqtt client handshake */ + { "mqtt", "C", LRS_MQTTC_IDLE, LWS_WSIEV_MQTT_CONNECT_SENT, NULL, NULL, LRS_MQTTC_AWAIT_CONNACK }, + { "mqtt", "C", LRS_MQTTC_AWAIT_CONNACK, LWS_WSIEV_MQTT_CONNACK, NULL, NULL, LRS_ESTABLISHED }, + + /* h2 connection preface */ + { "h2", "S", LRS_H2_AWAIT_PREFACE, LWS_WSIEV_H2_PREFACE_RX, NULL, NULL, LRS_H2_AWAIT_SETTINGS }, + { "h2", "S", LRS_H2_AWAIT_SETTINGS, LWS_WSIEV_H2_SETTINGS_ACKED, NULL, NULL, LRS_ESTABLISHED }, + + /* request headers: h1 decides on the upgrade, mux streams defer the action */ + { "h1", "S", LRS_HEADERS, LWS_WSIEV_REQ_HDRS_COMPLETE, NULL, NULL, LRS_H1_UPGRADE }, + { "h1", "S", LRS_ESTABLISHED, LWS_WSIEV_REQ_HDRS_COMPLETE, NULL, NULL, LRS_H1_UPGRADE }, + { "h2", "S", LRS_HEADERS, LWS_WSIEV_REQ_HDRS_COMPLETE, NULL, NULL, LRS_DEFERRING_ACTION }, + { "h2", "S", LRS_ESTABLISHED, LWS_WSIEV_REQ_HDRS_COMPLETE, NULL, NULL, LRS_DEFERRING_ACTION }, + { "h3", "S", LRS_HEADERS, LWS_WSIEV_REQ_HDRS_COMPLETE, NULL, NULL, LRS_DEFERRING_ACTION }, + { "h3", "S", LRS_ESTABLISHED, LWS_WSIEV_REQ_HDRS_COMPLETE, NULL, NULL, LRS_DEFERRING_ACTION }, + { "h1", "S", LRS_H1_UPGRADE, LWS_WSIEV_REQ_PLAIN_HTTP, NULL, NULL, LRS_ESTABLISHED }, + { "h2", "S", LRS_DEFERRING_ACTION, LWS_WSIEV_ACTION_DEFERRED_RUN, NULL, NULL, LRS_ESTABLISHED }, + { "h3", "S", LRS_DEFERRING_ACTION, LWS_WSIEV_ACTION_DEFERRED_RUN, NULL, NULL, LRS_ESTABLISHED }, + + /* acting on the request */ + { "h1", "S", LRS_ESTABLISHED, LWS_WSIEV_ACTION_BEGIN, NULL, NULL, LRS_DOING_TRANSACTION }, + { "h2", "S", LRS_HEADERS, LWS_WSIEV_ACTION_BEGIN, NULL, NULL, LRS_DOING_TRANSACTION }, + { "h2", "S", LRS_ESTABLISHED, LWS_WSIEV_ACTION_BEGIN, NULL, NULL, LRS_DOING_TRANSACTION }, + { "h3", "S", LRS_HEADERS, LWS_WSIEV_ACTION_BEGIN, NULL, NULL, LRS_DOING_TRANSACTION }, + { "h3", "S", LRS_ESTABLISHED, LWS_WSIEV_ACTION_BEGIN, NULL, NULL, LRS_DOING_TRANSACTION }, + + /* request body */ + { "h1", "S", LRS_ESTABLISHED, LWS_WSIEV_BODY_BEGIN, NULL, NULL, LRS_BODY }, + { "h1", "S", LRS_DOING_TRANSACTION, LWS_WSIEV_BODY_BEGIN, NULL, NULL, LRS_BODY }, + { "h2", "*", LRS_ESTABLISHED, LWS_WSIEV_BODY_BEGIN, NULL, NULL, LRS_BODY }, + { "h2", "*", LRS_HEADERS, LWS_WSIEV_BODY_BEGIN, NULL, NULL, LRS_BODY }, + { "h3", "*", LRS_ESTABLISHED, LWS_WSIEV_BODY_BEGIN, NULL, NULL, LRS_BODY }, + { "h3", "S", LRS_DOING_TRANSACTION, LWS_WSIEV_BODY_BEGIN, NULL, NULL, LRS_BODY }, /* action ran, body follows: as h1 */ + { "h2", "S", LRS_DOING_TRANSACTION, LWS_WSIEV_BODY_BEGIN, NULL, NULL, LRS_BODY }, /* same on h2 */ + /* + * an h1 body is complete before its answer is: the connection's next + * request, pipelined behind it, waits parked for the transaction + */ + { "h1", "S", LRS_BODY, LWS_WSIEV_BODY_COMPLETE, NULL, NULL, LRS_DOING_TRANSACTION }, + { "h2", "S", LRS_BODY, LWS_WSIEV_BODY_COMPLETE, NULL, NULL, LRS_ESTABLISHED }, + { "h3", "S", LRS_BODY, LWS_WSIEV_BODY_COMPLETE, NULL, NULL, LRS_ESTABLISHED }, + /* the body completion callback may itself have started serving a file (C-522) */ + { "h2", "S", LRS_ISSUING_FILE, LWS_WSIEV_BODY_COMPLETE, NULL, NULL, LRS_ISSUING_FILE }, + { "h3", "S", LRS_ISSUING_FILE, LWS_WSIEV_BODY_COMPLETE, NULL, NULL, LRS_ISSUING_FILE }, + { "h2", "S", LRS_AWAITING_FILE_READ, LWS_WSIEV_BODY_COMPLETE, NULL, NULL, LRS_AWAITING_FILE_READ }, + { "h3", "S", LRS_AWAITING_FILE_READ, LWS_WSIEV_BODY_COMPLETE, NULL, NULL, LRS_AWAITING_FILE_READ }, + { "h1", "S", LRS_BODY, LWS_WSIEV_BODY_DISCARD, NULL, NULL, LRS_DISCARD_BODY }, + /* the user may complete the transaction before the body was ever delivered */ + { "h1", "S", LRS_ESTABLISHED, LWS_WSIEV_BODY_DISCARD, NULL, NULL, LRS_DISCARD_BODY }, + { "h1", "S", LRS_DOING_TRANSACTION, LWS_WSIEV_BODY_DISCARD, NULL, NULL, LRS_DISCARD_BODY }, + { "h1", "S", LRS_H1_UPGRADE, LWS_WSIEV_BODY_DISCARD, NULL, NULL, LRS_DISCARD_BODY }, + { "h1", "S", LRS_ISSUING_FILE, LWS_WSIEV_BODY_DISCARD, NULL, NULL, LRS_DISCARD_BODY }, + /* the completion runs for mux streams too: there DISCARD_BODY drops the stash and closes the stream */ + { "h2", "S", LRS_ESTABLISHED, LWS_WSIEV_BODY_DISCARD, NULL, NULL, LRS_DISCARD_BODY }, + { "h2", "S", LRS_DOING_TRANSACTION, LWS_WSIEV_BODY_DISCARD, NULL, NULL, LRS_DISCARD_BODY }, + { "h2", "S", LRS_DEFERRING_ACTION, LWS_WSIEV_BODY_DISCARD, NULL, NULL, LRS_DISCARD_BODY }, + { "h3", "S", LRS_ESTABLISHED, LWS_WSIEV_BODY_DISCARD, NULL, NULL, LRS_DISCARD_BODY }, + { "h3", "S", LRS_DOING_TRANSACTION, LWS_WSIEV_BODY_DISCARD, NULL, NULL, LRS_DISCARD_BODY }, + { "h3", "S", LRS_DEFERRING_ACTION, LWS_WSIEV_BODY_DISCARD, NULL, NULL, LRS_DISCARD_BODY }, + { "h2", "S", LRS_HEADERS, LWS_WSIEV_BODY_DISCARD, NULL, NULL, LRS_DISCARD_BODY }, + { "h3", "S", LRS_HEADERS, LWS_WSIEV_BODY_DISCARD, NULL, NULL, LRS_DISCARD_BODY }, + { "h2", "S", LRS_BODY, LWS_WSIEV_BODY_DISCARD, NULL, NULL, LRS_DISCARD_BODY }, + { "h3", "S", LRS_BODY, LWS_WSIEV_BODY_DISCARD, NULL, NULL, LRS_DISCARD_BODY }, + { "h2", "S", LRS_ISSUING_FILE, LWS_WSIEV_BODY_DISCARD, NULL, NULL, LRS_DISCARD_BODY }, + { "h3", "S", LRS_ISSUING_FILE, LWS_WSIEV_BODY_DISCARD, NULL, NULL, LRS_DISCARD_BODY }, + /* ...or abandoned with a read of the file out on a worker, reaped by the completion */ + { "h1", "S", LRS_AWAITING_FILE_READ, LWS_WSIEV_BODY_DISCARD, NULL, NULL, LRS_DISCARD_BODY }, + { "h2", "S", LRS_AWAITING_FILE_READ, LWS_WSIEV_BODY_DISCARD, NULL, NULL, LRS_DISCARD_BODY }, + { "h3", "S", LRS_AWAITING_FILE_READ, LWS_WSIEV_BODY_DISCARD, NULL, NULL, LRS_DISCARD_BODY }, + + /* the h1 transaction ends and the connection is reused */ + /* + * the user completed it with its response still queued: completion + * waits for that to go, from wherever the transaction had got to + */ + { "h1", "S", LRS_ESTABLISHED, LWS_WSIEV_TXN_COMPLETING, NULL, NULL, LRS_TXN_COMPLETING }, + { "h1", "S", LRS_BODY, LWS_WSIEV_TXN_COMPLETING, NULL, NULL, LRS_TXN_COMPLETING }, + { "h1", "S", LRS_DISCARD_BODY, LWS_WSIEV_TXN_COMPLETING, NULL, NULL, LRS_TXN_COMPLETING }, + { "h1", "S", LRS_DOING_TRANSACTION, LWS_WSIEV_TXN_COMPLETING, NULL, NULL, LRS_TXN_COMPLETING }, + { "h1", "S", LRS_H1_UPGRADE, LWS_WSIEV_TXN_COMPLETING, NULL, NULL, LRS_TXN_COMPLETING }, + { "h1", "S", LRS_TXN_COMPLETED, LWS_WSIEV_TXN_COMPLETING, NULL, NULL, LRS_TXN_COMPLETING }, + { "h1", "S", LRS_ISSUING_FILE, LWS_WSIEV_TXN_COMPLETING, NULL, NULL, LRS_TXN_COMPLETING }, + { "h1", "S", LRS_TXN_COMPLETING, LWS_WSIEV_TXN_COMPLETING, NULL, NULL, LRS_TXN_COMPLETING }, + { "h2", "S", LRS_HEADERS, LWS_WSIEV_TXN_COMPLETING, NULL, NULL, LRS_TXN_COMPLETING }, + { "h2", "S", LRS_DEFERRING_ACTION, LWS_WSIEV_TXN_COMPLETING, NULL, NULL, LRS_TXN_COMPLETING }, + { "h2", "S", LRS_ESTABLISHED, LWS_WSIEV_TXN_COMPLETING, NULL, NULL, LRS_TXN_COMPLETING }, + { "h2", "S", LRS_DOING_TRANSACTION, LWS_WSIEV_TXN_COMPLETING, NULL, NULL, LRS_TXN_COMPLETING }, + { "h2", "S", LRS_BODY, LWS_WSIEV_TXN_COMPLETING, NULL, NULL, LRS_TXN_COMPLETING }, + { "h2", "S", LRS_DISCARD_BODY, LWS_WSIEV_TXN_COMPLETING, NULL, NULL, LRS_TXN_COMPLETING }, + { "h2", "S", LRS_ISSUING_FILE, LWS_WSIEV_TXN_COMPLETING, NULL, NULL, LRS_TXN_COMPLETING }, + { "h2", "S", LRS_TXN_COMPLETING, LWS_WSIEV_TXN_COMPLETING, NULL, NULL, LRS_TXN_COMPLETING }, + { "h3", "S", LRS_HEADERS, LWS_WSIEV_TXN_COMPLETING, NULL, NULL, LRS_TXN_COMPLETING }, + { "h3", "S", LRS_DEFERRING_ACTION, LWS_WSIEV_TXN_COMPLETING, NULL, NULL, LRS_TXN_COMPLETING }, + { "h3", "S", LRS_ESTABLISHED, LWS_WSIEV_TXN_COMPLETING, NULL, NULL, LRS_TXN_COMPLETING }, + { "h3", "S", LRS_DOING_TRANSACTION, LWS_WSIEV_TXN_COMPLETING, NULL, NULL, LRS_TXN_COMPLETING }, + { "h3", "S", LRS_BODY, LWS_WSIEV_TXN_COMPLETING, NULL, NULL, LRS_TXN_COMPLETING }, + { "h3", "S", LRS_DISCARD_BODY, LWS_WSIEV_TXN_COMPLETING, NULL, NULL, LRS_TXN_COMPLETING }, + { "h3", "S", LRS_ISSUING_FILE, LWS_WSIEV_TXN_COMPLETING, NULL, NULL, LRS_TXN_COMPLETING }, + { "h3", "S", LRS_TXN_COMPLETING, LWS_WSIEV_TXN_COMPLETING, NULL, NULL, LRS_TXN_COMPLETING }, + /* + * a file being served, a read of it out on a worker, that the app + * completed from under (an h2 / h3 body completion, its own timer): + * the completion waits for the answer queued so far as any other + */ + { "h1", "S", LRS_AWAITING_FILE_READ, LWS_WSIEV_TXN_COMPLETING, NULL, NULL, LRS_TXN_COMPLETING }, + { "h2", "S", LRS_AWAITING_FILE_READ, LWS_WSIEV_TXN_COMPLETING, NULL, NULL, LRS_TXN_COMPLETING }, + { "h3", "S", LRS_AWAITING_FILE_READ, LWS_WSIEV_TXN_COMPLETING, NULL, NULL, LRS_TXN_COMPLETING }, + /* ...and once it has, it completes, or first discards an unread body */ + { "h1", "S", LRS_TXN_COMPLETING, LWS_WSIEV_TXN_COMPLETED, NULL, NULL, LRS_TXN_COMPLETED }, + { "h1", "S", LRS_TXN_COMPLETING, LWS_WSIEV_BODY_DISCARD, NULL, NULL, LRS_DISCARD_BODY }, + { "h2", "S", LRS_TXN_COMPLETING, LWS_WSIEV_BODY_DISCARD, NULL, NULL, LRS_DISCARD_BODY }, + { "h3", "S", LRS_TXN_COMPLETING, LWS_WSIEV_BODY_DISCARD, NULL, NULL, LRS_DISCARD_BODY }, + + { "h1", "S", LRS_ESTABLISHED, LWS_WSIEV_TXN_COMPLETED, NULL, NULL, LRS_TXN_COMPLETED }, + { "h1", "S", LRS_BODY, LWS_WSIEV_TXN_COMPLETED, NULL, NULL, LRS_TXN_COMPLETED }, + { "h1", "S", LRS_DISCARD_BODY, LWS_WSIEV_TXN_COMPLETED, NULL, NULL, LRS_TXN_COMPLETED }, + { "h1", "S", LRS_DOING_TRANSACTION, LWS_WSIEV_TXN_COMPLETED, NULL, NULL, LRS_TXN_COMPLETED }, + { "h1", "S", LRS_H1_UPGRADE, LWS_WSIEV_TXN_COMPLETED, NULL, NULL, LRS_TXN_COMPLETED }, /* upgrade refused */ + { "h1", "S", LRS_TXN_COMPLETED, LWS_WSIEV_TXN_COMPLETED, NULL, NULL, LRS_TXN_COMPLETED }, + { "h1", "S", LRS_ISSUING_FILE, LWS_WSIEV_TXN_COMPLETED, NULL, NULL, LRS_TXN_COMPLETED }, /* completed from the file-complete callback */ + { "h1", "S", LRS_AWAITING_FILE_READ, LWS_WSIEV_TXN_COMPLETED, NULL, NULL, LRS_TXN_COMPLETED }, /* or abandoned with a read out on a worker */ + { "h1", "S", LRS_TXN_COMPLETED, LWS_WSIEV_TXN_DRAINED, NULL, NULL, LRS_HEADERS }, + + /* serving a file */ + { "*", "S", LRS_ESTABLISHED, LWS_WSIEV_FILE_BEGIN, NULL, NULL, LRS_ISSUING_FILE }, + { "h2", "S", LRS_HEADERS, LWS_WSIEV_FILE_BEGIN, NULL, NULL, LRS_ISSUING_FILE }, /* h2c upgrade stream 1 */ + { "*", "S", LRS_DOING_TRANSACTION, LWS_WSIEV_FILE_BEGIN, NULL, NULL, LRS_ISSUING_FILE }, + { "*", "S", LRS_BODY, LWS_WSIEV_FILE_BEGIN, NULL, NULL, LRS_ISSUING_FILE }, /* served from HTTP_BODY_COMPLETION */ + { "h1", "S", LRS_H1_UPGRADE, LWS_WSIEV_FILE_BEGIN, NULL, NULL, LRS_ISSUING_FILE }, /* served from HTTP_CONFIRM_UPGRADE */ + { "*", "S", LRS_ISSUING_FILE, LWS_WSIEV_FILE_READ_QUEUED, NULL, NULL, LRS_AWAITING_FILE_READ }, + { "*", "S", LRS_AWAITING_FILE_READ, LWS_WSIEV_FILE_READ_DONE, NULL, NULL, LRS_ISSUING_FILE }, + { "*", "S", LRS_ISSUING_FILE, LWS_WSIEV_FILE_COMPLETE, NULL, NULL, LRS_ESTABLISHED }, + + /* ---- role changes ---- */ + + /* birth on a server, adoption, the client bind, a restart */ + { "(none)", "-", LRS_UNCONNECTED, LWS_WSIEV_SERVER_SIDE, NULL, "S", LRS_UNCONNECTED }, + { "*", "*", LRS_UNCONNECTED, LWS_WSIEV_ADOPTED_TLS, "?", NULL, LRS_SSL_INIT }, + { "*", "*", LRS_UNCONNECTED, LWS_WSIEV_ADOPTED, "h1", NULL, LRS_HEADERS }, + { "*", "*", LRS_UNCONNECTED, LWS_WSIEV_ADOPTED, "?", NULL, LRS_ESTABLISHED }, + { "*", "*", LRS_UNCONNECTED, LWS_WSIEV_CLIENT_BIND, "?", "C", LRS_UNCONNECTED }, + { "*", "C", ANY, LWS_WSIEV_RESTART, "?", "C", LRS_UNCONNECTED }, + + /* mux children: a fresh child is its parent's; a server's opened stream reads its request */ + { "(none)", "*", LRS_UNCONNECTED, LWS_WSIEV_MUX_INSERTED, "P", "P", LRS_UNCONNECTED }, + { "h2", "S", LRS_UNCONNECTED, LWS_WSIEV_STREAM_OPENED, NULL, NULL, LRS_HEADERS }, + { "h2", "C", LRS_UNCONNECTED, LWS_WSIEV_STREAM_OPENED, NULL, NULL, LRS_UNCONNECTED }, + { "quic", "S", LRS_UNCONNECTED, LWS_WSIEV_STREAM_OPENED, "h3", NULL, LRS_HEADERS }, + { "quic", "C", LRS_UNCONNECTED, LWS_WSIEV_STREAM_OPENED, "h3", NULL, LRS_ESTABLISHED }, + { "quic", "*", LRS_UNCONNECTED, LWS_WSIEV_STREAM_OPENED, "quic", NULL, LRS_ESTABLISHED }, + + /* a client stream is let onto its connection: h2 / h3 send a request, mqtt is up */ + { "*", "*", LRS_UNCONNECTED, LWS_WSIEV_MUX_STREAM_ADOPTED, "h2", "P", LRS_H2_WAITING_TO_SEND_HEADERS }, + { "*", "*", LRS_H1C_ISSUE_HANDSHAKE2, LWS_WSIEV_MUX_STREAM_ADOPTED, "h2", "P", LRS_H2_WAITING_TO_SEND_HEADERS }, + { "*", "*", LRS_H2_WAITING_TO_SEND_HEADERS, LWS_WSIEV_MUX_STREAM_ADOPTED, "h2", "P", LRS_H2_WAITING_TO_SEND_HEADERS }, + { "*", "*", LRS_UNCONNECTED, LWS_WSIEV_MUX_STREAM_ADOPTED, "h3", "P", LRS_H2_WAITING_TO_SEND_HEADERS }, + { "*", "*", LRS_H1C_ISSUE_HANDSHAKE2, LWS_WSIEV_MUX_STREAM_ADOPTED, "h3", "P", LRS_H2_WAITING_TO_SEND_HEADERS }, + { "*", "*", LRS_WAITING_CONNECT, LWS_WSIEV_MUX_STREAM_ADOPTED, "h3", "P", LRS_H2_WAITING_TO_SEND_HEADERS }, + { "*", "*", LRS_WAITING_SSL, LWS_WSIEV_MUX_STREAM_ADOPTED, "h3", "P", LRS_H2_WAITING_TO_SEND_HEADERS }, + { "*", "*", LRS_H2_WAITING_TO_SEND_HEADERS, LWS_WSIEV_MUX_STREAM_ADOPTED, "h3", "P", LRS_H2_WAITING_TO_SEND_HEADERS }, + { "*", "*", LRS_UNCONNECTED, LWS_WSIEV_MUX_STREAM_ADOPTED, "mqtt", "P", LRS_ESTABLISHED }, + { "*", "*", LRS_H2_WAITING_TO_SEND_HEADERS, LWS_WSIEV_MUX_STREAM_ADOPTED, "mqtt", "P", LRS_ESTABLISHED }, + { "*", "*", LRS_ESTABLISHED, LWS_WSIEV_MUX_STREAM_ADOPTED, "mqtt", "P", LRS_ESTABLISHED }, + + /* our own h3 unidirectional streams, and a wsi taking over a quic connection */ + { "(none)", "*", LRS_UNCONNECTED, LWS_WSIEV_CONTROL_STREAM_OPENED, "h3", "L", LRS_ESTABLISHED }, + { "(none)", "*", LRS_UNCONNECTED, LWS_WSIEV_CONN_TAKEOVER, "?", "L", LRS_ESTABLISHED }, + + /* the quic handshake chose h3 (a client stream sends, a server stream reads) or not */ + { "*", "C", LRS_UNCONNECTED, LWS_WSIEV_ALPN_DONE, "h3", NULL, LRS_H2_WAITING_TO_SEND_HEADERS }, + { "*", "C", LRS_WAITING_CONNECT, LWS_WSIEV_ALPN_DONE, "h3", NULL, LRS_H2_WAITING_TO_SEND_HEADERS }, + { "quic", "C", ANY, LWS_WSIEV_ALPN_DONE, "h3", NULL, LRS_H2_WAITING_TO_SEND_HEADERS }, + { "quic", "S", ANY, LWS_WSIEV_ALPN_DONE, "h3", NULL, LRS_HEADERS }, + { "quic", "*", ANY, LWS_WSIEV_ALPN_DONE, "quic", NULL, LRS_ESTABLISHED }, + + /* + * the h2 client nwsi hands its own request to the sid-1 child and is + * from then on the carrier of streams only: established + */ + { "h2", "C", LRS_H2_WAITING_TO_SEND_HEADERS, LWS_WSIEV_MUX_MIGRATED, NULL, NULL, LRS_ESTABLISHED }, + + /* h2: chosen by alpn, h2c upgrade or prior knowledge; the client preface */ + { "h1", "S", LRS_HEADERS, LWS_WSIEV_H2_SELECTED, "h2", NULL, LRS_H2_AWAIT_PREFACE }, + { "h1", "S", LRS_H1_UPGRADE, LWS_WSIEV_H2_SELECTED, "h2", NULL, LRS_H2_AWAIT_PREFACE }, + { "(none)", "S", LRS_UNCONNECTED, LWS_WSIEV_H2_SELECTED, "h2", NULL, LRS_H2_AWAIT_PREFACE }, + { "h1", "C", LRS_H1C_ISSUE_HANDSHAKE, LWS_WSIEV_H2_SELECTED, "h2", NULL, LRS_H2_AWAIT_PREFACE }, + { "h1", "C", LRS_WAITING_SSL, LWS_WSIEV_H2_SELECTED, "h2", NULL, LRS_H2_AWAIT_PREFACE }, + { "h1", "C", LRS_UNCONNECTED, LWS_WSIEV_H2_SELECTED, "h2", NULL, LRS_H2_AWAIT_PREFACE }, + { "h2", "C", LRS_H2_AWAIT_PREFACE, LWS_WSIEV_H2_PREFACE_SENT, NULL, NULL, LRS_H2_WAITING_TO_SEND_HEADERS }, + + /* ws upgrade, on a server from the upgrade decision, on a client from the 101 */ + { "h1", "S", LRS_H1_UPGRADE, LWS_WSIEV_WS_UPGRADED, "ws", NULL, LRS_ESTABLISHED }, + { "h2", "S", LRS_ESTABLISHED, LWS_WSIEV_WS_UPGRADED, "ws", "Se", LRS_ESTABLISHED }, + { "h3", "S", LRS_ESTABLISHED, LWS_WSIEV_WS_UPGRADED, "ws", "Se", LRS_ESTABLISHED }, /* RFC 9220 */ + { "h1", "C", LRS_WAITING_SERVER_REPLY, LWS_WSIEV_WS_UPGRADED, "ws", NULL, LRS_ESTABLISHED }, + { "h2", "C", LRS_WAITING_SERVER_REPLY, LWS_WSIEV_WS_UPGRADED, "ws", "Ce", LRS_ESTABLISHED }, + + /* the client's response headers; webtransport; raw */ + { "*", "C", LRS_WAITING_SERVER_REPLY, LWS_WSIEV_RESP_HDRS, NULL, NULL, LRS_ESTABLISHED }, + /* a server may answer before the request body is finished (401, 413...) */ + { "*", "C", LRS_ISSUE_HTTP_BODY, LWS_WSIEV_RESP_HDRS, NULL, NULL, LRS_ESTABLISHED }, + { "h3", "C", LRS_WAITING_SERVER_REPLY, LWS_WSIEV_WT_SESSION, "wt", NULL, LRS_ESTABLISHED }, + { "h3", "S", ANY, LWS_WSIEV_WT_SESSION, "wt", NULL, LRS_ESTABLISHED }, + { "h3", "*", ANY, LWS_WSIEV_WT_STREAM, "wt", NULL, LRS_ESTABLISHED }, + { "(none)", "*", LRS_UNCONNECTED, LWS_WSIEV_WT_STREAM, "wt", "L", LRS_ESTABLISHED }, + { "*", "S", LRS_HEADERS, LWS_WSIEV_RAW_UPGRADED, "?", NULL, LRS_ESTABLISHED }, + { "*", "S", LRS_H1_UPGRADE, LWS_WSIEV_RAW_UPGRADED, "?", NULL, LRS_ESTABLISHED }, + /* the non-tls fallback on a tls listener, from the first byte peek in the accept */ + { "*", "S", LRS_SSL_INIT, LWS_WSIEV_RAW_UPGRADED, "?", NULL, LRS_ESTABLISHED }, + { "*", "S", LRS_SSL_ACK_PENDING, LWS_WSIEV_RAW_UPGRADED, "?", NULL, LRS_ESTABLISHED }, + /* a later request on a kept-alive connection, and a listener already in the raw role */ + { "*", "S", LRS_ESTABLISHED, LWS_WSIEV_RAW_UPGRADED, "?", NULL, LRS_ESTABLISHED }, + { "h1", "C", LRS_ESTABLISHED, LWS_WSIEV_RAW_UPGRADED, "raw-skt", NULL, LRS_ESTABLISHED }, + { "h1", "C", LRS_WAITING_SERVER_REPLY, LWS_WSIEV_RAW_UPGRADED, "raw-skt", NULL, LRS_ESTABLISHED }, + /* ---- transport machine ---- */ + + { "*", "C", LRS_UNCONNECTED, LWS_WSIEV_DNS_START, NULL, NULL, XT(LTS_WAITING_DNS) }, + { "*", "C", LRS_WAITING_DNS, LWS_WSIEV_DNS_RETRY, NULL, NULL, XT(LTS_NONE) }, + { "*", "C", LRS_UNCONNECTED, LWS_WSIEV_CONNECT_START, NULL, NULL, XT(LTS_WAITING_CONNECT) }, + { "*", "C", LRS_WAITING_DNS, LWS_WSIEV_CONNECT_START, NULL, NULL, XT(LTS_WAITING_CONNECT) }, + { "*", "C", LRS_WAITING_CONNECT, LWS_WSIEV_CONNECT_START, NULL, NULL, XT(LTS_WAITING_CONNECT) }, /* next address */ + { "*", "C", LRS_WAITING_SSL, LWS_WSIEV_CONNECT_START, NULL, NULL, XT(LTS_WAITING_CONNECT) }, /* quic to tcp */ + { "*", "C", LRS_WAITING_CONNECT, LWS_WSIEV_PROXY_CONNECT_SENT, NULL, NULL, XT(LTS_WAITING_PROXY_REPLY) }, + { "*", "C", LRS_WAITING_CONNECT, LWS_WSIEV_SOCKS_GREETING_SENT, NULL, NULL, XT(LTS_WAITING_SOCKS_GREETING_REPLY) }, + { "*", "C", LRS_WAITING_SOCKS_GREETING_REPLY, LWS_WSIEV_SOCKS_AUTH_SENT, NULL, NULL, XT(LTS_WAITING_SOCKS_AUTH_REPLY) }, + { "*", "C", LRS_WAITING_SOCKS_GREETING_REPLY, LWS_WSIEV_SOCKS_CONNECT_SENT, NULL, NULL, XT(LTS_WAITING_SOCKS_CONNECT_REPLY) }, + { "*", "C", LRS_WAITING_SOCKS_AUTH_REPLY, LWS_WSIEV_SOCKS_CONNECT_SENT, NULL, NULL, XT(LTS_WAITING_SOCKS_CONNECT_REPLY) }, + { "*", "C", LRS_WAITING_CONNECT, LWS_WSIEV_TLS_START, NULL, NULL, XT(LTS_WAITING_SSL) }, + { "*", "C", LRS_WAITING_PROXY_REPLY, LWS_WSIEV_TLS_START, NULL, NULL, XT(LTS_WAITING_SSL) }, + { "*", "C", LRS_WAITING_SOCKS_CONNECT_REPLY, LWS_WSIEV_TLS_START, NULL, NULL, XT(LTS_WAITING_SSL) }, + { "*", "C", LRS_H1C_ISSUE_HANDSHAKE, LWS_WSIEV_TLS_START, NULL, NULL, XT(LTS_WAITING_SSL) }, + { "*", "C", LRS_WAITING_SSL, LWS_WSIEV_TLS_START, NULL, NULL, XT(LTS_WAITING_SSL) }, /* more service */ + /* STARTTLS: an established raw client starts tls inside its protocol */ + { "raw-skt", "C", LRS_ESTABLISHED, LWS_WSIEV_TLS_START, NULL, NULL, XT(LTS_WAITING_SSL) }, + { "*", "S", LRS_SSL_INIT, LWS_WSIEV_TLS_ACCEPT_PENDING, NULL, NULL, XT(LTS_SSL_ACK_PENDING) }, + { "*", "S", LRS_SSL_ACK_PENDING, LWS_WSIEV_TLS_ACCEPT_PENDING, NULL, NULL, XT(LTS_SSL_ACK_PENDING) }, + { "*", "S", LRS_AWAITING_SSL_ACCEPT, LWS_WSIEV_TLS_ACCEPT_PENDING, NULL, NULL, XT(LTS_SSL_ACK_PENDING) }, + { "*", "S", LRS_SSL_INIT, LWS_WSIEV_TLS_ACCEPT_QUEUED, NULL, NULL, XT(LTS_AWAITING_SSL_ACCEPT) }, + { "*", "S", LRS_SSL_ACK_PENDING, LWS_WSIEV_TLS_ACCEPT_QUEUED, NULL, NULL, XT(LTS_AWAITING_SSL_ACCEPT) }, + { "*", "C", ANY, LWS_WSIEV_CONN_FAILED, NULL, NULL, XT(LTS_FAILED) }, + { "*", "C", ANY, LWS_WSIEV_RETARGET, NULL, NULL, XT(LTS_RESTARTING) }, + + /* ---- close machine: the polite ws close is specific, the rest can come from anywhere ---- */ + + { "ws", "*", LRS_ESTABLISHED, LWS_WSIEV_WS_CLOSE_INITIATED, NULL, NULL, XC(LCS_WAITING_TO_SEND_CLOSE) }, + { "ws", "*", LRS_WAITING_TO_SEND_CLOSE, LWS_WSIEV_WS_CLOSE_SENT, NULL, NULL, XC(LCS_AWAITING_CLOSE_ACK) }, + { "ws", "*", LRS_ESTABLISHED, LWS_WSIEV_WS_PEER_CLOSE, NULL, NULL, XC(LCS_RETURNED_CLOSE) }, + /* his CLOSE beat the one we were about to send: answer his and drop ours */ + { "ws", "*", LRS_WAITING_TO_SEND_CLOSE, LWS_WSIEV_WS_PEER_CLOSE, NULL, NULL, XC(LCS_RETURNED_CLOSE) }, + /* + * the close is entered: a flush the live connection had begun is now + * the close's own, else the close has begun with nothing to wait for + */ + { "*", "*", LRS_FLUSHING_BEFORE_CLOSE, LWS_WSIEV_CLOSE_ENTERED, NULL, NULL, XC(LCS_FLUSHING_BEFORE_CLOSE) }, + { "*", "*", ANY, LWS_WSIEV_CLOSE_ENTERED, NULL, NULL, XC(LCS_CLOSING) }, + { "*", "*", ANY, LWS_WSIEV_CLOSE_FLUSH, NULL, NULL, XC(LCS_FLUSHING_BEFORE_CLOSE) }, + { "*", "*", ANY, LWS_WSIEV_CLOSE_WHEN_FLUSHED, NULL, NULL, XC(LCS_CLOSE_WHEN_FLUSHED) }, + { "*", "S", ANY, LWS_WSIEV_CLOSE_STAGED, NULL, NULL, XC(LCS_SHUTDOWN) }, + { "*", "*", ANY, LWS_WSIEV_SOCKET_GONE, NULL, NULL, XC(LCS_DEAD_SOCKET) }, + { "*", "*", LRS_DEAD_SOCKET, LWS_WSIEV_USER_TOLD, NULL, NULL, XC(LCS_USER_TOLD) }, + diff --git a/crates/npro-test/tests/states.rs b/crates/npro-test/tests/states.rs new file mode 100644 index 0000000..bac4213 --- /dev/null +++ b/crates/npro-test/tests/states.rs @@ -0,0 +1,667 @@ +//! npro's connection state machines (`npro_core::state`) against C's. +//! +//! Two oracles, both copied from the C tree into `states/` (see its +//! README): +//! +//! - `wsi-event-edges.txt`, the rows of C's event table, verbatim. This +//! test has a second model of C's machines, written in C's terms (the +//! names, the fields of the state word, and C's setters), which reads its +//! rows from that file. Every state npro's machines can reach from a +//! connection's birth is driven with every event, with and without each +//! role a site can give, and with the socket's usability changed, through +//! both; they must agree on refusing it, on the machines after, on which +//! setter made the change, and on the trace line. +//! - `edges.txt`, every distinct edge C's ctest suite took, from C's +//! `LWS_WITH_STATE_TRACE`. Each edge between npro's roles must be one +//! npro's machines take. + +#![expect( + unused_crate_dependencies, + reason = "an integration test sees all of its crate's dependencies; this one uses only npro-core" +)] +#![expect( + clippy::std_instead_of_alloc, + reason = "a test, which has std: its collections are std's" +)] + +// the C model is test code throughout, held to clippy's rules for tests +#[cfg(test)] +mod states { + use std::collections::{BTreeSet, HashSet, VecDeque}; + use std::fs; + use std::path::Path; + + use npro_core::state::{ + Carrier, Close, Edge, Event, Live, Machines, Role, Side, Socket, Transport, + }; + + fn states_dir() -> &'static Path { + Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/states")) + } + + /// The roles npro has, as C names them. + const ROLES: [&str; 4] = ["(none)", "h1", "ws", "raw-skt"]; + + // ---- C's names for the machines' phases, from private-lib-sansio.h ---- + + /// `enum lws_transport_phase`, in order. + const LTS: [&str; 13] = [ + "NONE", + "WAITING_DNS", + "WAITING_CONNECT", + "WAITING_PROXY_REPLY", + "WAITING_SSL", + "WAITING_SOCKS_GREETING_REPLY", + "WAITING_SOCKS_CONNECT_REPLY", + "WAITING_SOCKS_AUTH_REPLY", + "SSL_INIT", + "SSL_ACK_PENDING", + "AWAITING_SSL_ACCEPT", + "FAILED", + "RESTARTING", + ]; + + /// `enum lws_carrier_phase`, in order. + const LCR: [&str; 11] = [ + "NONE", + "H1C_ISSUE_HANDSHAKE", + "H1C_ISSUE_HANDSHAKE2", + "WAITING_SERVER_REPLY", + "H2_AWAIT_PREFACE", + "H2_AWAIT_SETTINGS", + "H2_WAITING_TO_SEND_HEADERS", + "H1_UPGRADE", + "MQTTC_IDLE", + "MQTTC_AWAIT_CONNACK", + "ESTABLISHED", + ]; + + /// `enum lws_close_phase`, in order: the machine only goes forwards. + const LCS: [&str; 10] = [ + "NONE", + "CLOSE_WHEN_FLUSHED", + "CLOSING", + "WAITING_TO_SEND_CLOSE", + "RETURNED_CLOSE", + "AWAITING_CLOSE_ACK", + "FLUSHING_BEFORE_CLOSE", + "SHUTDOWN", + "DEAD_SOCKET", + "USER_TOLD", + ]; + + fn idx(names: &[&str], name: &str) -> usize { + names + .iter() + .position(|n| *n == name) + .unwrap_or_else(|| panic!("no phase {name}")) + } + + /// The transport phase an `LRS_` name stands for (`lws_lts_of_lrs()`). + fn lts_of(lrs: &str) -> Option<&'static str> { + LTS[1..=10].iter().copied().find(|n| *n == lrs) + } + + /// The carrier phase an `LRS_` name stands for (`lws_lcr_of_lrs()`). + fn lcr_of(lrs: &str) -> Option<&'static str> { + LCR[1..=9].iter().copied().find(|n| *n == lrs) + } + + // ---- C's state word ---- + + /// The fields of C's `wsistate`, and the role ops, by name. + #[derive(Clone, Debug, PartialEq, Eq)] + struct Word { + role: &'static str, + side: char, + transport: &'static str, + carrier: &'static str, + live: &'static str, + close: &'static str, + unusable: bool, + } + + impl Word { + /// `lwsi_state_of_word()`: what the connection reports. + fn reported(&self) -> &'static str { + if self.close != "NONE" && self.close != "CLOSING" { + return match self.close { + "CLOSE_WHEN_FLUSHED" => "FLUSHING_BEFORE_CLOSE", + "USER_TOLD" => "DEAD_SOCKET", + c => c, + }; + } + if self.transport != "NONE" { + return match self.transport { + "FAILED" | "RESTARTING" => "UNCONNECTED", + t => t, + }; + } + if self.carrier != "NONE" && self.carrier != "ESTABLISHED" { + return self.carrier; + } + self.live + } + + /// `lws_wsi_state_fmt()`. + fn fmt(&self) -> String { + let mut s = format!("{}/{}:{}", self.role, self.side, self.reported()); + if self.transport == "FAILED" { + s.push_str("+failed"); + } + if self.transport == "RESTARTING" { + s.push_str("+restarting"); + } + if self.close == "USER_TOLD" { + s.push_str("+told"); + } + if self.close == "CLOSING" { + s.push_str("+closing"); + } + if self.unusable { + s.push_str("+unusable"); + } + s + } + + /// `lws_wsi_set_state_ev()`. + fn set_state(&mut self, lrs: &'static str) { + self.transport = "NONE"; + match lcr_of(lrs) { + Some(lcr) if self.carrier != "ESTABLISHED" => self.carrier = lcr, + _ => { + self.carrier = if lrs == "UNCONNECTED" { + "NONE" + } else { + "ESTABLISHED" + }; + self.live = lrs; + } + } + } + + /// `lws_wsi_role_transition_ev()`. + fn role_transition(&mut self, role: &'static str, side: char, lrs: &'static str) { + let old = self.clone(); + let (lts, lcr) = (lts_of(lrs), lcr_of(lrs)); + + *self = Word { + role, + side, + transport: "NONE", + carrier: "NONE", + live: "UNCONNECTED", + close: "NONE", + unusable: false, + }; + if let Some(t) = lts { + self.transport = t; + } else if let Some(c) = lcr { + self.carrier = c; + } else if lrs != "UNCONNECTED" { + self.live = lrs; + self.carrier = "ESTABLISHED"; + } + if lts.is_some() || lcr.is_some() || lrs != "UNCONNECTED" { + self.unusable = old.unusable; + self.close = old.close; + } + } + + /// `lws_state_invariant()`. + fn invariant(&self) -> bool { + let r = self.reported(); + !(self.unusable + && matches!( + r, + "WAITING_TO_SEND_CLOSE" | "RETURNED_CLOSE" | "AWAITING_CLOSE_ACK" | "SHUTDOWN" + ) + || self.transport == "RESTARTING" && self.side != 'C' + || r == "RETURNED_CLOSE" && self.role != "ws" + || r == "SHUTDOWN" && (self.side == 'C' || self.role == "raw-skt")) + } + } + + // ---- C's event table ---- + + #[derive(Clone, Debug)] + enum RowTo { + Lrs(&'static str), + Lts(&'static str), + Lcs(&'static str), + } + + #[derive(Clone, Debug)] + struct Row { + role: String, + side: String, + from: Option<String>, + ev: String, + to_role: Option<String>, + to_side: Option<String>, + to: RowTo, + } + + /// C's `lws_lrs_names[]`, the names of the states a connection reports. + const LRS: [&str; 38] = [ + "UNCONNECTED", + "WAITING_DNS", + "WAITING_CONNECT", + "WAITING_PROXY_REPLY", + "WAITING_SSL", + "WAITING_SOCKS_GREETING_REPLY", + "WAITING_SOCKS_CONNECT_REPLY", + "WAITING_SOCKS_AUTH_REPLY", + "SSL_INIT", + "SSL_ACK_PENDING", + "H1_UPGRADE", + "WAITING_SERVER_REPLY", + "H2_AWAIT_PREFACE", + "H2_AWAIT_SETTINGS", + "TXN_COMPLETED", + "H2_WAITING_TO_SEND_HEADERS", + "DEFERRING_ACTION", + "IDLING", + "H1C_ISSUE_HANDSHAKE", + "H1C_ISSUE_HANDSHAKE2", + "ISSUE_HTTP_BODY", + "ISSUING_FILE", + "HEADERS", + "BODY", + "DISCARD_BODY", + "ESTABLISHED", + "DOING_TRANSACTION", + "WAITING_TO_SEND_CLOSE", + "RETURNED_CLOSE", + "AWAITING_CLOSE_ACK", + "FLUSHING_BEFORE_CLOSE", + "SHUTDOWN", + "DEAD_SOCKET", + "MQTTC_IDLE", + "MQTTC_AWAIT_CONNACK", + "AWAITING_FILE_READ", + "AWAITING_SSL_ACCEPT", + "TXN_COMPLETING", + ]; + + /// The name in `names` that is `s`: the table names nothing else. + fn named(names: &[&'static str], s: &str) -> &'static str { + names + .iter() + .copied() + .find(|n| *n == s) + .unwrap_or_else(|| panic!("no name {s} here")) + } + + /// The rows of `lws_wsi_event_edges[]`, from the copy of C's table. + fn rows() -> Vec<Row> { + let text = fs::read_to_string(states_dir().join("wsi-event-edges.txt")).unwrap(); + let mut rows = Vec::new(); + + for line in text.lines() { + let line = line.trim(); + if !line.starts_with('{') { + continue; + } + let body = &line[1..line.find('}').unwrap()]; + let f: Vec<&str> = body.split(',').map(str::trim).collect(); + assert_eq!(f.len(), 7, "row {line}"); + + let unquote = |s: &str| -> Option<String> { + (s != "NULL").then(|| s.trim_matches('"').to_owned()) + }; + let from = (f[2] != "ANY").then(|| f[2].strip_prefix("LRS_").unwrap().to_owned()); + let to = if let Some(t) = f[6].strip_prefix("XT(LTS_") { + RowTo::Lts(named(&LTS, t.trim_end_matches(')'))) + } else if let Some(c) = f[6].strip_prefix("XC(LCS_") { + RowTo::Lcs(named(&LCS, c.trim_end_matches(')'))) + } else { + RowTo::Lrs(named(&LRS, f[6].strip_prefix("LRS_").unwrap())) + }; + + rows.push(Row { + role: f[0].trim_matches('"').to_owned(), + side: f[1].trim_matches('"').to_owned(), + from, + ev: f[3].strip_prefix("LWS_WSIEV_").unwrap().to_owned(), + to_role: unquote(f[4]), + to_side: unquote(f[5]), + to, + }); + } + assert!(rows.len() > 200, "only {} rows read", rows.len()); + rows + } + + /// What C does with an event. + #[derive(Debug, PartialEq, Eq)] + enum COutcome { + /// It changes the word, by this setter. + Edge(Word, &'static str), + /// It refuses: no row, a close going back, or a broken invariant (which + /// `LWS_WITH_STATE_CHECK` aborts on). + Refused, + } + + /// `lws_wsi_event_x()`, with `LWS_WITH_STATE_CHECK`'s check of the result. + fn c_event(rows: &[Row], w: &Word, ev: &str, ops: Option<&'static str>) -> COutcome { + let from = w.reported(); + let w_side = w.side.to_string(); + + let Some(r) = rows.iter().find(|r| { + r.ev == ev + && r.from.as_deref().is_none_or(|f| f == from) + && (r.role == "*" || r.role == w.role) + && (r.side == "*" || r.side == w_side) + && match (r.to_role.as_deref(), ops) { + (Some("?"), o) => o.is_some(), + (_, None) => true, + (Some(t), Some(o)) => t != "P" && t == o, + (None, Some(_)) => false, + } + }) else { + return COutcome::Refused; + }; + + let mut to = w.clone(); + let how = match (&r.to, r.to_role.as_deref(), r.to_side.as_deref()) { + (RowTo::Lts(t), _, _) => { + to.transport = t; + "set_transport" + } + (RowTo::Lcs(c), _, _) => { + if idx(&LCS, c) < idx(&LCS, w.close) { + return COutcome::Refused; + } + to.close = c; + "set_close" + } + (RowTo::Lrs(lrs), None, None) => { + // set_state asserts a retargeted wsi has no live state + if w.transport == "RESTARTING" { + return COutcome::Refused; + } + to.set_state(lrs); + "set_state" + } + (RowTo::Lrs(lrs), role, side) => { + let role = match role { + None => w.role, + Some("?") => ops.unwrap(), + // a row to a role npro lacks fails here + Some(to_role) => named(&ROLES, to_role), + }; + let side = match side { + None => w.side, + Some(s) => { + assert!(matches!(s, "C" | "S"), "row to side {s}"); + s.chars().next().unwrap() + } + }; + to.role_transition(role, side, lrs); + "role_transition" + } + }; + + let attr_only = w.reported() == to.reported() && w.side == to.side && w.role == to.role; + if !attr_only && !to.invariant() { + return COutcome::Refused; + } + COutcome::Edge(to, how) + } + + /// Whether C's trace records an edge (`lws_wsi_state_changed()`). + fn c_traced(from: &Word, to: &Word) -> bool { + let attr_only = + from.reported() == to.reported() && from.side == to.side && from.role == to.role; + !attr_only + || from.unusable != to.unusable + || from.close != to.close + || from.transport != to.transport + } + + // ---- npro's machines, in C's names ---- + + fn word(m: Machines) -> Word { + Word { + role: m.role().name(), + side: m.side().letter(), + transport: match m.transport() { + Transport::None => "NONE", + Transport::WaitingDns => "WAITING_DNS", + Transport::WaitingConnect => "WAITING_CONNECT", + Transport::WaitingProxyReply => "WAITING_PROXY_REPLY", + Transport::WaitingSsl => "WAITING_SSL", + Transport::WaitingSocksGreetingReply => "WAITING_SOCKS_GREETING_REPLY", + Transport::WaitingSocksConnectReply => "WAITING_SOCKS_CONNECT_REPLY", + Transport::WaitingSocksAuthReply => "WAITING_SOCKS_AUTH_REPLY", + Transport::SslInit => "SSL_INIT", + Transport::SslAckPending => "SSL_ACK_PENDING", + Transport::AwaitingSslAccept => "AWAITING_SSL_ACCEPT", + Transport::Failed => "FAILED", + Transport::Restarting => "RESTARTING", + }, + carrier: match m.carrier() { + Carrier::None => "NONE", + Carrier::H1cIssueHandshake => "H1C_ISSUE_HANDSHAKE", + Carrier::H1cIssueHandshake2 => "H1C_ISSUE_HANDSHAKE2", + Carrier::WaitingServerReply => "WAITING_SERVER_REPLY", + Carrier::H2WaitingToSendHeaders => "H2_WAITING_TO_SEND_HEADERS", + Carrier::H1Upgrade => "H1_UPGRADE", + Carrier::Established => "ESTABLISHED", + }, + live: match m.live() { + Live::Unconnected => "UNCONNECTED", + Live::H1cIssueHandshake => "H1C_ISSUE_HANDSHAKE", + Live::H1cIssueHandshake2 => "H1C_ISSUE_HANDSHAKE2", + Live::WaitingServerReply => "WAITING_SERVER_REPLY", + Live::H2WaitingToSendHeaders => "H2_WAITING_TO_SEND_HEADERS", + Live::H1Upgrade => "H1_UPGRADE", + Live::IssueHttpBody => "ISSUE_HTTP_BODY", + Live::Headers => "HEADERS", + Live::Established => "ESTABLISHED", + Live::DoingTransaction => "DOING_TRANSACTION", + Live::Body => "BODY", + Live::DiscardBody => "DISCARD_BODY", + Live::IssuingFile => "ISSUING_FILE", + Live::AwaitingFileRead => "AWAITING_FILE_READ", + Live::TxnCompleting => "TXN_COMPLETING", + Live::TxnCompleted => "TXN_COMPLETED", + Live::Idling => "IDLING", + }, + close: LCS[match m.close() { + Close::None => 0, + Close::CloseWhenFlushed => 1, + Close::Closing => 2, + Close::WaitingToSendClose => 3, + Close::ReturnedClose => 4, + Close::AwaitingCloseAck => 5, + Close::FlushingBeforeClose => 6, + Close::Shutdown => 7, + Close::DeadSocket => 8, + Close::UserTold => 9, + }], + unusable: m.socket() == Socket::Unusable, + } + } + + /// The roles a site can give with an event: none, or one of npro's. + fn sites() -> [Option<Role>; 4] { + [None, Some(Role::H1), Some(Role::Ws), Some(Role::RawSkt)] + } + + /// Every state npro's machines reach from a birth, and the trace line of + /// every edge between them that C's trace would record. + fn reachable() -> (Vec<Machines>, BTreeSet<String>) { + let mut seen = HashSet::new(); + let mut order = Vec::new(); + let mut lines = BTreeSet::new(); + let mut queue = VecDeque::new(); + + lines.insert(Machines::birth().to_string()); + queue.push_back(Machines::new()); + seen.insert(Machines::new()); + + while let Some(m) = queue.pop_front() { + order.push(m); + let mut edges: Vec<Edge> = Vec::new(); + for ev in Event::ALL { + for site in sites() { + let mut n = m; + let r = match site { + None => n.event(ev), + Some(role) => n.event_as(ev, role), + }; + if let Ok(e) = r { + edges.push(e); + } + } + } + for s in [Socket::Usable, Socket::Unusable] { + let mut n = m; + let e = n.set_socket(s); + if e.from != Some(e.to) { + edges.push(e); + } + } + for e in edges { + if e.traced() { + lines.insert(e.to_string()); + } + if seen.insert(e.to) { + queue.push_back(e.to); + } + } + } + (order, lines) + } + + #[test] + #[cfg_attr( + miri, + ignore = "600,000 comparisons over 3,000 states: native runs keep it" + )] + fn npro_takes_every_event_as_c_does() { + let rows = rows(); + let (states, _) = reachable(); + let mut compared = 0usize; + + for m in &states { + let w = word(*m); + for ev in Event::ALL { + for site in sites() { + let mut n = *m; + let ours = match site { + None => n.event(ev), + Some(role) => n.event_as(ev, role), + }; + let theirs = c_event(&rows, &w, ev.name(), site.map(Role::name)); + let what = format!("{} ev={} site={site:?}", w.fmt(), ev.name()); + + match (ours, theirs) { + (Err(_), COutcome::Refused) => {} + (Ok(e), COutcome::Edge(to, how)) => { + assert_eq!(word(e.to), to, "{what}"); + assert_eq!(e.how.name(), how, "{what}"); + assert_eq!(e.traced(), c_traced(&w, &to), "{what}"); + assert_eq!( + e.to_string(), + format!("LRS {} -> {} {how} ev={}", w.fmt(), to.fmt(), ev.name()), + "{what}" + ); + assert_eq!(n, e.to, "{what}"); + } + (Ok(e), COutcome::Refused) => panic!("{what}: npro takes {e}, C refuses"), + (Err(r), COutcome::Edge(to, _)) => { + panic!("{what}: npro refuses ({r}), C goes to {}", to.fmt()) + } + } + compared += 1; + } + } + } + assert!(states.len() > 100, "only {} states reached", states.len()); + assert!(compared > 10_000, "only {compared} compared"); + } + + #[test] + #[cfg_attr(miri, ignore = "the walk of 3,000 states: native runs keep it")] + fn every_edge_c_takes_between_npros_roles_npro_takes() { + let (_, ours) = reachable(); + let text = fs::read_to_string(states_dir().join("edges.txt")).unwrap(); + + let ours_role = |s: &str| { + let (role, rest) = s.split_once('/').unwrap(); + let side = rest.split(':').next().unwrap(); + ROLES.contains(&role) && matches!(side, "-" | "C" | "S") + }; + + let mut checked = 0usize; + let mut missing = Vec::new(); + for line in text.lines() { + let f: Vec<&str> = line.split(' ').collect(); + // a birth's "from" has no role yet + let from_ok = f[1] == "(none)/-:(zero)" || ours_role(f[1]); + if !from_ok || !ours_role(f[3]) { + continue; + } + checked += 1; + if !ours.contains(line) { + missing.push(line); + } + } + assert!( + missing.is_empty(), + "C takes edges npro does not:\n{}", + missing.join("\n") + ); + assert!( + checked > 150, + "only {checked} of C's edges are between npro's roles" + ); + } + + #[test] + #[cfg_attr(miri, ignore = "the walk of 3,000 states: native runs keep it")] + fn the_invariants_hold_in_every_state_npro_reaches() { + let (states, _) = reachable(); + for m in &states { + let w = word(*m); + // C's lws_state_invariant() is about entering a state by an event, + // and the comparison with C holds npro to it there: a socket can + // still die under a close already in progress. These hold in + // every state: only a client restarts or has failed, only a ws + // peer's close is answered, only a server stages a shutdown, and + // never on a raw socket. + if matches!(m.transport(), Transport::Restarting | Transport::Failed) { + assert_eq!(m.side(), Side::Client, "{}", w.fmt()); + } + if m.close() == Close::ReturnedClose { + assert_eq!(m.role(), Role::Ws, "{}", w.fmt()); + } + if m.close() == Close::Shutdown { + assert_eq!(m.side(), Side::Server, "{}", w.fmt()); + assert_ne!(m.role(), Role::RawSkt, "{}", w.fmt()); + } + } + } + + #[test] + fn the_c_table_copy_has_rows_for_every_event_npro_has() { + let rows = rows(); + for ev in Event::ALL { + assert!( + rows.iter().any(|r| r.ev == ev.name()), + "no C row for {}", + ev.name() + ); + } + } + + #[test] + fn every_side_and_role_named() { + // the trace's names for what npro calls them + assert_eq!(Side::ALL.map(Side::letter), ['-', 'C', 'S']); + assert_eq!(Role::ALL.map(Role::name), ROLES); + } +} diff --git a/crates/npro-test/tests/vendored.rs b/crates/npro-test/tests/vendored.rs index d95ed0b..546d498 100644 --- a/crates/npro-test/tests/vendored.rs +++ b/crates/npro-test/tests/vendored.rs @@ -1,5 +1,10 @@ //! Every transcript copied from C reads, and says what its README says. +#![expect( + unused_crate_dependencies, + reason = "an integration test sees all of its crate's dependencies; this one uses only npro-test" +)] + use npro_test::{Side, StepKind, vendored}; #[test] diff --git a/scripts/sync-c-states.sh b/scripts/sync-c-states.sh new file mode 100755 index 0000000..3bf0340 --- /dev/null +++ b/scripts/sync-c-states.sh @@ -0,0 +1,63 @@ +#!/bin/sh +# +# Copy C lws' state machine oracles from a C checkout: +# +# scripts/sync-c-states.sh /path/to/libwebsockets [build dir] +# +# The rows of C's event table go to crates/npro-test/states/, verbatim, and +# the state edges C's ctest suite takes beside them, from a build with +# LWS_WITH_STATE_TRACE and LWS_WITH_STATE_CHECK (so the suite also aborts on +# any edge the table does not allow). The C commit is recorded in C-COMMIT. +# The checkout should be clean, as for sync-c-oracle.sh. +# +# The build goes to <checkout>/build-npro-states unless a build dir is +# given; it is reconfigured each time. It is a default build without h3, +# which needs gnutls; C's ctest-background.sh needs netstat or ss to see its +# test servers come up. The suite takes some minutes. + +set -eu + +if [ $# -lt 1 ] || [ $# -gt 2 ] || [ ! -f "$1/lib/sansio/wsi-state.c" ]; then + echo "usage: $0 <libwebsockets checkout> [build dir]" >&2 + exit 1 +fi + +c="$1" +b="${2:-$c/build-npro-states}" +here="$(cd "$(dirname "$0")/.." && pwd)" +dst="$here/crates/npro-test/states" + +if [ -n "$(git -C "$c" status --porcelain -- lib include CMakeLists.txt)" ]; then + echo "$c has uncommitted changes" >&2 + exit 1 +fi +if ! command -v netstat >/dev/null 2>&1 && ! command -v ss >/dev/null 2>&1; then + echo "C's test fixtures need netstat or ss" >&2 + exit 1 +fi + +cmake -S "$c" -B "$b" -DCMAKE_BUILD_TYPE=DEBUG \ + -DLWS_WITH_STATE_TRACE=ON -DLWS_WITH_STATE_CHECK=ON \ + -DLWS_WITH_HTTP3=OFF -DLWS_WITH_MINIMAL_EXAMPLES=ON \ + -DLWS_WITHOUT_EXTENSIONS=OFF -DLWS_WITH_ZLIB=ON \ + -DLWS_WITH_SYS_FAULT_INJECTION=ON +cmake --build "$b" -j "${SAI_PARALLEL:-4}" + +raw="$(mktemp)" +trap 'rm -f "$raw"' EXIT +(cd "$b" && LWS_STATE_TRACE_FILE="$raw" ctest -j "${SAI_PARALLEL:-4}" --timeout 180) + +# the table's rows, between its opening and its closing brace +awk '/^static const struct lws_wsi_event_edge lws_wsi_event_edges\[\] = \{/ { f = 1; next } + f && /^\};/ { exit } + f' "$c/lib/sansio/wsi-state.c" > "$dst/wsi-event-edges.txt" + +# each edge once, without the connection's tag: "LRS from -> to how [ev=X]" +sed -E -e 's/^(LRS [^ ]+ -> [^ ]+ [^ ]+) .*( ev=[A-Z0-9_]+)$/\1\2/' -e t \ + -e 's/^(LRS [^ ]+ -> [^ ]+ [^ ]+) .*$/\1/' "$raw" | + LC_ALL=C sort -u > "$dst/edges.txt" + +git -C "$c" log -1 --format='%H %s' > "$dst/C-COMMIT" + +echo "$(grep -c '^[[:space:]]*{' "$dst/wsi-event-edges.txt") rows and" \ + "$(wc -l < "$dst/edges.txt") edges from $(cut -c1-12 "$dst/C-COMMIT")"
Page fetched 0s ago, creation time: 30ms (vhost etag hits: 0%, cache hits: 0%)