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
 
root / crates / npro-test / transcripts / ws-server-no-version.json
Author[]Andy Green <andy@warmcat.com> 2026-10-05 20:22 UTC
Committer[]Andy Green <andy@warmcat.com> 2026-10-06 03:36 UTC
Treef2ff55abe716b0a3e6122ca3c75271adde424b2c   Raw Patch
 
npro-test: the permessage-deflate transcripts replay
npro-test: the permessage-deflate transcripts replay

The client replay offers permessage-deflate in the ws-client-pmd-* cases,
quiet as C's are, since what it would send is deflated, and takes the
server's answer: ws-client-pmd-rsv2, -rsv1-continuation and -rsv1-ping
replay byte for byte, masked close and all.  The server replay takes it
on C's sansio-pmd vhost: ws-server-pmd-rsv1-continuation replays, the 101
saying it, the plain first frame given the app, the RSV1 continuation
refused.  npro-test builds npro-ws with pmd.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019kg5Eemy68ZaqDBcUJQG6J
diff --git a/crates/npro-test/Cargo.toml b/crates/npro-test/Cargo.toml index 100fc95..d5a7ad5 100644 --- a/crates/npro-test/Cargo.toml +++ b/crates/npro-test/Cargo.toml @@ -12,7 +12,7 @@ repository.workspace = true [dev-dependencies] npro-core = { path = "../npro-core", features = ["replay"] } npro-h1 = { path = "../npro-h1" } -npro-ws = { path = "../npro-ws" } +npro-ws = { path = "../npro-ws", features = ["pmd"] } [lints] workspace = true diff --git a/crates/npro-test/tests/ws_client_replay.rs b/crates/npro-test/tests/ws_client_replay.rs index e00d207..6122ed6 100644 --- a/crates/npro-test/tests/ws_client_replay.rs +++ b/crates/npro-test/tests/ws_client_replay.rs @@ -7,7 +7,10 @@ //! the response is checked as C checks it, and the connection is then //! npro-ws', masking from the same stream. The app is C's //! `callback_client`: once established it sends `Hello`, unless the case -//! has it quiet. +//! has it quiet. The `ws-client-pmd-*` cases also offer permessage-deflate, +//! which the server takes; they are quiet, as C's are, since what the +//! client would send is deflated, and those bytes are the deflater's +//! business. //! //! What npro writes after each `rx` must be the transcript's `tx` bytes, //! the messages it hands the app its `app_rx` bytes, and it must ask to be @@ -24,17 +27,36 @@ mod ws_client_replay { use npro_test::{StepKind, Transcript, vendored}; use npro_ws::conn::{AsClient, Event, Kind, Ws}; use npro_ws::handshake::{ClientKey, MAX_REQUEST_LINES}; + use npro_ws::pmd; - /// The ws client transcripts this phase replays, and whether the app - /// is quiet: not the permessage-deflate ones, the next phase's, nor the - /// digest retry, which needs digest auth. - const CASES: [(&str, bool); 6] = [ - ("ws-client", false), - ("ws-client-interim", false), - ("ws-client-ping-close", true), - ("ws-client-huge-frame", false), - ("ws-client-rsv1-no-ext", false), - ("ws-client-rsv2", false), + /// A case: its transcript, whether the app is quiet, and the + /// extensions offered. + struct Case { + name: &'static str, + quiet: bool, + extensions: Option<&'static [u8]>, + } + + const fn case(name: &'static str, quiet: bool, extensions: Option<&'static [u8]>) -> Case { + Case { + name, + quiet, + extensions, + } + } + + /// The ws client transcripts: all but the digest retry, which needs + /// digest auth. + const CASES: [Case; 9] = [ + case("ws-client", false, None), + case("ws-client-interim", false, None), + case("ws-client-ping-close", true, None), + case("ws-client-huge-frame", false, None), + case("ws-client-rsv1-no-ext", false, None), + case("ws-client-rsv2", false, None), + case("ws-client-pmd-rsv2", true, Some(pmd::OFFER)), + case("ws-client-pmd-rsv1-continuation", true, Some(pmd::OFFER)), + case("ws-client-pmd-rsv1-ping", true, Some(pmd::OFFER)), ]; /// The subprotocols offered. @@ -82,6 +104,7 @@ mod ws_client_replay { client: Box<Client<Vec<u8>>>, key: ClientKey, random: Option<SeededRandom>, + extensions: Option<&'static [u8]>, }, Ws(Box<Ws<AsClient<SeededRandom>>>), } @@ -99,6 +122,7 @@ mod ws_client_replay { client, key, random, + extensions, } => { let rx = client.rx(&input[at..]).unwrap(); at = at.checked_add(rx.consumed).unwrap(); @@ -106,10 +130,18 @@ mod ws_client_replay { if rx.event == Some(H1Event::Response) { assert!(client.is_upgraded()); let checked = key - .check(client.status(), client.response(), Some(OFFERED), None) + .check( + client.status(), + client.response(), + Some(OFFERED), + *extensions, + ) .unwrap(); assert_eq!(checked.protocol, Some(OFFERED)); let mut ws = Ws::client(random.take().unwrap()); + if let Some(said) = checked.extensions { + ws = ws.with_pmd(pmd::client_accept(said).unwrap()); + } app.established(&mut ws); *conn = Conn::Ws(Box::new(ws)); continue; @@ -139,13 +171,15 @@ mod ws_client_replay { } } - fn replay(t: &Transcript, quiet: bool) { + fn replay(t: &Transcript, c: &Case) { // C's run was seeded: its random is in the transcript let seed = t.seed.unwrap_or_else(|| panic!("{}: not seeded", t.case)); let mut random = SeededRandom::new(seed.get()); let key = ClientKey::new(&mut random).unwrap(); let mut lines = [0u8; MAX_REQUEST_LINES + 32]; - let lines_len = key.request_lines(Some(OFFERED), None, &mut lines).unwrap(); + let lines_len = key + .request_lines(Some(OFFERED), c.extensions, &mut lines) + .unwrap(); let mut client = Client::new( vec![0u8; DEFAULT_CAPACITY], Request { @@ -177,9 +211,10 @@ mod ws_client_replay { client: Box::new(client), key, random: Some(random), + extensions: c.extensions, }; let mut app = ClientApp { - quiet, + quiet: c.quiet, ..ClientApp::default() }; while let Some(step) = steps.next() { @@ -224,12 +259,12 @@ mod ws_client_replay { #[cfg_attr(miri, ignore = "reads the transcripts: native runs keep it")] fn cs_ws_client_transcripts_replay_byte_for_byte() { let all = vendored().unwrap(); - for (case, quiet) in CASES { + for c in &CASES { let t = all .iter() - .find(|t| t.case == case) - .unwrap_or_else(|| panic!("no transcript {case}")); - replay(t, quiet); + .find(|t| t.case == c.name) + .unwrap_or_else(|| panic!("no transcript {}", c.name)); + replay(t, c); } } } diff --git a/crates/npro-test/tests/ws_server_replay.rs b/crates/npro-test/tests/ws_server_replay.rs index 3ea0d95..a037e44 100644 --- a/crates/npro-test/tests/ws_server_replay.rs +++ b/crates/npro-test/tests/ws_server_replay.rs @@ -1,7 +1,8 @@ //! npro's ws server replays C's ws server transcripts byte for byte. //! //! Each transcript is one connection to C's `sansio` vhost, whose ws -//! subprotocols are `http`, its default, and `echo`. npro's h1 server takes +//! subprotocols are `http`, its default, and `echo`, or to its `sansio-pmd` +//! vhost, the same with permessage-deflate. npro's h1 server takes //! the request; one asking for `Upgrade: websocket` goes to npro-ws's //! handshake, which answers a refusal with C's status page and an accepted //! upgrade with C's 101, after which the connection is npro-ws'. The app is @@ -28,10 +29,10 @@ mod ws_server_replay { use npro_test::{StepKind, Transcript, vendored}; use npro_ws::conn::{Event, Kind, Ws}; use npro_ws::handshake::{self, MAX_101}; + use npro_ws::pmd; - /// The ws server transcripts this half of the phase replays: not the - /// permessage-deflate ones, which are the next phase's. - const CASES: [&str; 10] = [ + /// The ws server transcripts. + const CASES: [&str; 11] = [ "h1-ws-server", "ws-server-version-8", "ws-server-no-version", @@ -42,6 +43,7 @@ mod ws_server_replay { "ws-server-close-partial", "ws-server-close-when-flushed", "ws-server-huge-frame", + "ws-server-pmd-rsv1-continuation", ]; /// The `sansio` vhost's ws subprotocols, the first its default. @@ -121,12 +123,23 @@ mod ws_server_replay { app.at = 0; return Answer::H1; } + // the vhost with permessage-deflate takes it if it is offered + let pmd = (t.first(Token::Host) == Some(b"sansio-pmd".as_slice())) + .then(|| pmd::server_accept(t).unwrap()) + .flatten(); match handshake::server(t, &PROTOCOLS, Some(0)) { Ok(a) => { + let lines = pmd + .as_ref() + .map_or(&[][..], pmd::ServerAccepted::header_lines); let mut first = [0u8; MAX_101]; let n = - handshake::response_101(&a, PROTOCOLS[a.protocol], b"", &mut first).unwrap(); - Answer::Upgraded(Box::new(Ws::server(&first[..n]))) + handshake::response_101(&a, PROTOCOLS[a.protocol], lines, &mut first).unwrap(); + let mut ws = Ws::server(&first[..n]); + if let Some(p) = pmd { + ws = ws.with_pmd(p.params()); + } + Answer::Upgraded(Box::new(ws)) } Err(r) => { s.refuse_upgrade(r.status(), r.header()).unwrap();
Page fetched 0s ago, creation time: 3ms (vhost etag hits: 0%, cache hits: 0%)