Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
0b3cf76
test(network): require typed session.status response
seonghobae Aug 30, 2026
99226e2
test(network): format session.status response regression
seonghobae Aug 30, 2026
cdd2018
feat(network): parse typed session.status response
seonghobae Aug 30, 2026
77c6aec
feat(network): export session.status result boundary
seonghobae Aug 30, 2026
922eb7e
test(network): harden session.status response projection
seonghobae Aug 30, 2026
ea20fd8
style(network): apply canonical session.status test formatting
seonghobae Aug 30, 2026
361ad17
style(network): apply canonical session.status response formatting
seonghobae Aug 30, 2026
bbe5ebd
test(network): exercise validated session.status projection shapes
seonghobae Aug 30, 2026
27ac412
test(network): keep unicode session.status fixture UTF-8
seonghobae Aug 30, 2026
c146151
test(network): close session.status coverage gaps
seonghobae Aug 30, 2026
13ac096
fix(network): make session status unicode decoding total
seonghobae Aug 30, 2026
a31983a
test(network): cover multi-member BiDi metadata objects
seonghobae Aug 30, 2026
cd73ba1
fix(network): remove unreachable UTF-8 cursor fallbacks
seonghobae Aug 30, 2026
3740701
test(network): cover malformed session status result keys
seonghobae Aug 30, 2026
70cb5f7
test(network): cover empty session.status result
seonghobae Aug 30, 2026
288d21c
test(network): cover empty status result in unit build
seonghobae Aug 30, 2026
b7d36b9
test(network): retain BiDi remote error code
seonghobae Aug 30, 2026
92a9251
fix(network): retain BiDi remote error code
seonghobae Aug 30, 2026
95110fd
fix(network): preserve parser coverage tests
seonghobae Aug 30, 2026
b27b967
fix(network): cover validated error evidence fallback
seonghobae Aug 30, 2026
7455799
fix(network): defer BiDi error correlation after validation
seonghobae Aug 30, 2026
367810c
fix(network): restore focused BiDi response repair boundary
seonghobae Aug 30, 2026
e24e723
fix(network): isolate validated BiDi error correlation path
seonghobae Aug 30, 2026
8dd0e2c
style(network): apply canonical rustfmt diagnostics
seonghobae Aug 30, 2026
4e824ab
test(network): expose response correlation kind
seonghobae Sep 1, 2026
3732f73
merge(network): converge status response on typed correlation
seonghobae Sep 1, 2026
9394b9a
fix(network): bind status response correlation kind
seonghobae Sep 1, 2026
0911385
fix(network): align public status correlation test
seonghobae Sep 2, 2026
1262df0
style(network): apply exact session status rustfmt
seonghobae Sep 2, 2026
e30cf49
merge(parent): refresh session status response base
seonghobae Sep 4, 2026
cbddf50
merge(parent): refresh session status response base
seonghobae Sep 5, 2026
0eab23d
merge: restack typed status on current command
seonghobae Sep 5, 2026
ec433b8
fix(network): adopt verified status-command parent
seonghobae Sep 5, 2026
7e85a7e
test: reject session.status reply from replacement connection
seonghobae Sep 6, 2026
6b102c1
test: apply canonical status-provenance formatting
seonghobae Sep 6, 2026
5aa7d1a
merge: adopt session.status sender provenance
seonghobae Sep 6, 2026
9307eca
fix: bind session.status responses to connection
seonghobae Sep 6, 2026
0b68198
test: carry status response connection evidence
seonghobae Sep 6, 2026
cafa950
test: retain status connection provenance in hostile cases
seonghobae Sep 6, 2026
05cbc5c
test: require exact status response connection provenance
seonghobae Sep 6, 2026
f979972
merge: adopt session.status sender provenance
seonghobae Sep 6, 2026
0e1e47c
test: exercise received status boundary
seonghobae Sep 6, 2026
aadba11
docs: record session.status connection provenance
seonghobae Sep 6, 2026
3a8c01f
merge: adopt current session.status connection provenance prerequisite
seonghobae Sep 6, 2026
615d840
fix: format session.status connection provenance boundary
seonghobae Sep 6, 2026
f0f2e20
merge: adopt session.status parent ownership correction
seonghobae Sep 6, 2026
74536b2
test(network): cover sealed reader and correlation rejection paths
seonghobae Sep 7, 2026
9bdd116
test(network): use consumed transport evidence in rejection fixture
seonghobae Sep 7, 2026
804a7a5
test(network): exercise missing provenance through public status replies
seonghobae Sep 7, 2026
d5c7aea
test(network): preserve pending status across unroutable replies
seonghobae Sep 7, 2026
bbdc6ac
refactor(network): share non-consuming response routing validation
seonghobae Sep 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ All notable changes to OriginWeave are documented in this file. The format follo

### Added

- Regression checks now exercise fragmented browser replies, interleaved control messages, and rejected replies without losing a pending request. These checks do not establish browser readiness or release acceptance.
- The typed browser-status response stack now includes its verified command and opening-exchange prerequisites, including the release-record check that previously did not execute; parsing remains bounded and does not grant browser authority or prove operational readiness.
- Bounded RFC 6455 WebDriver BiDi opening-response validation on the exact peer-verified stream: it admits only HTTP/1.1 `101`, case-insensitive `Upgrade`/`Connection` tokens, and the client-key-correlated `Sec-WebSocket-Accept` value within monotonic time and header-size ceilings; it restores blocking mode and still does not implement WebSocket frames or grant browser/Agent authority.
- Typed outbound WebDriver BiDi `session.status` over the bounded client WebSocket stream: it serializes only the standards-defined method with empty params, preserves exact typed command-id correlation, rejects invalid frame deadlines before registration, retires only the just-registered id when a local masking-key preflight proves no command bytes were emitted, and keeps correlation outstanding after partial or ambiguous writes; frame-write success is not treated as command completion or browser/Agent authority.
- Bounded WebDriver BiDi loopback TCP transport that consumes one exact no-DNS connect target, retries only explicitly recoverable local transport failures within repository timeout and attempt ceilings, exposes the stream only after operating-system peer inspection and exact peer verification, supports a consuming handoff of the original stream with typed credential-free peer/session/TLS and bounded-attempt evidence, preserves typed causal errors, and performs no DNS, proxy/PAC, process authentication, TLS, WebSocket, BiDi message, browser-action, or Agent-authority step.
Expand Down
17 changes: 14 additions & 3 deletions crates/originweave-network/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@
//! the RFC 6455 opening exchange, provides bounded masked client writes and
//! unmasked server-frame reads, assembles bounded WebDriver BiDi text messages,
//! classifies complete local-end JSON envelopes, tracks bounded command-response
//! correlation, and sends one narrowly typed `session.status` command without
//! exposing generic JSON bodies or granting browser, TLS, policy, secret, or
//! Agent authority.
//! correlation, sends one narrowly typed `session.status` command, and admits its
//! required readiness result through one command-specific correlated parser without
//! exposing generic JSON bodies or granting browser, TLS, policy, secret, or Agent
//! authority.

#![forbid(unsafe_code)]
#![deny(missing_docs)]
Expand All @@ -19,7 +20,9 @@ mod connection;
mod webdriver_bidi_command_correlation;
mod webdriver_bidi_connection;
mod webdriver_bidi_json_envelope;
mod webdriver_bidi_received_message;
mod webdriver_bidi_session_status_command;
mod webdriver_bidi_session_status_response;
mod webdriver_bidi_websocket_frame;
mod webdriver_bidi_websocket_handshake;
mod webdriver_bidi_websocket_message;
Expand All @@ -46,9 +49,17 @@ pub use webdriver_bidi_json_envelope::{
MAX_WEBDRIVER_BIDI_JS_UINT, MAX_WEBDRIVER_BIDI_JSON_DEPTH, WebDriverBiDiJsonEnvelope,
WebDriverBiDiJsonEnvelopeError, WebDriverBiDiJsonEnvelopeKind,
};
pub use webdriver_bidi_received_message::{
WebDriverBiDiConnectionMessageRead, WebDriverBiDiConnectionMessageReadError,
WebDriverBiDiReceivedTextMessage, WebDriverBiDiWebSocketMessageReader,
};
pub use webdriver_bidi_session_status_command::{
WebDriverBiDiSessionStatusCommand, WebDriverBiDiSessionStatusCommandError,
};
pub use webdriver_bidi_session_status_response::{
MAX_WEBDRIVER_BIDI_SESSION_STATUS_MESSAGE_SIZE, WebDriverBiDiSessionStatusResponseError,
WebDriverBiDiSessionStatusResult,
};
pub use webdriver_bidi_websocket_frame::{
MAX_WEBSOCKET_FRAME_PAYLOAD_SIZE, MAX_WEBSOCKET_FRAME_TIMEOUT,
WebDriverBiDiWebSocketEstablished, WebDriverBiDiWebSocketFrame,
Expand Down
100 changes: 70 additions & 30 deletions crates/originweave-network/src/webdriver_bidi_command_correlation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,8 @@ pub enum WebDriverBiDiCorrelatedResponseOutcome {
///
/// This value carries only the matched command identifier and success/error classification. A
/// private process-local connection generation is retained when the command owner bound one before
/// I/O so a later response-provenance owner can compare transport evidence without accepting
/// caller-supplied provenance. It does not retain result bodies, error text, browser authority,
/// transport authority, or secrets.
/// I/O so later transport evidence can be compared without accepting caller-supplied provenance.
/// It does not retain result bodies, error text, browser authority, transport authority, or secrets.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct WebDriverBiDiCorrelatedResponse {
command_id: u64,
Expand Down Expand Up @@ -134,12 +133,13 @@ impl Error for WebDriverBiDiCommandCorrelationError {}
///
/// Register an id together with its exact typed command family only after the caller has committed
/// to that outbound command. Connection-owning command adapters may additionally bind the private
/// generation of the exact established transport before I/O. Generic success or correlatable error
/// responses consume the id exactly once through a matching typed consumer; a later slice that owns
/// received-connection evidence adds the connection-sensitive consuming boundary. This type
/// performs no I/O, retry, command serialization, browser authentication, or authority grant. Debug
/// output reports only the outstanding-count summary; command identifiers, families, and
/// generations remain private correlation state.
/// generation of the exact established transport before I/O. A success or correlatable error
/// response consumes the id exactly once only through a matching typed consumer. Events, null-id
/// errors, command-kind mismatches, missing connection provenance, and responses received on a
/// different verified connection leave outstanding state untouched. This type performs no I/O,
/// retry, command serialization, browser authentication, or authority grant. Debug output reports
/// only the outstanding-count summary; command identifiers, families, and generations remain
/// private correlation state.
#[derive(Default)]
pub struct WebDriverBiDiCommandCorrelation {
outstanding: BTreeMap<u64, OutstandingCommand>,
Expand Down Expand Up @@ -233,32 +233,29 @@ impl WebDriverBiDiCommandCorrelation {
/// Successful responses and error responses with ids consume exactly one matching command.
/// Unknown ids and command-kind mismatches fail without consuming state. Events and null-id
/// errors fail before touching the map. This generic path does not claim received-connection
/// provenance; connection-sensitive response handling belongs to its owning child slice.
/// provenance; connection-sensitive command owners must use their connection-bound path.
pub fn correlate_response_for(
&mut self,
envelope: &WebDriverBiDiJsonEnvelope,
expected_kind: WebDriverBiDiCommandKind,
) -> Result<WebDriverBiDiCorrelatedResponse, WebDriverBiDiCommandCorrelationError> {
match envelope.routing() {
WebDriverBiDiJsonEnvelopeRouting::Event => {
Err(WebDriverBiDiCommandCorrelationError::EventIsNotResponse)
}
WebDriverBiDiJsonEnvelopeRouting::CommandError { command_id: None } => {
Err(WebDriverBiDiCommandCorrelationError::UncorrelatableErrorResponse)
}
WebDriverBiDiJsonEnvelopeRouting::CommandError {
command_id: Some(command_id),
} => self.complete(
command_id,
expected_kind,
WebDriverBiDiCorrelatedResponseOutcome::Error,
),
WebDriverBiDiJsonEnvelopeRouting::CommandSuccess { command_id } => self.complete(
command_id,
expected_kind,
WebDriverBiDiCorrelatedResponseOutcome::Success,
),
}
let (command_id, outcome) = response_route(envelope)?;
self.complete(command_id, expected_kind, outcome)
}

pub(crate) fn correlate_response_for_connection(
&mut self,
envelope: &WebDriverBiDiJsonEnvelope,
expected_kind: WebDriverBiDiCommandKind,
received_connection_generation: WebDriverBiDiConnectionGeneration,
) -> Result<WebDriverBiDiCorrelatedResponse, WebDriverBiDiCommandCorrelationError> {
let (command_id, outcome) = response_route(envelope)?;
self.complete_on_connection(
command_id,
expected_kind,
outcome,
received_connection_generation,
)
}

fn require_command_kind(
Expand Down Expand Up @@ -294,6 +291,49 @@ impl WebDriverBiDiCommandCorrelation {
connection_generation: outstanding.connection_generation,
})
}

fn complete_on_connection(
&mut self,
command_id: u64,
expected_kind: WebDriverBiDiCommandKind,
outcome: WebDriverBiDiCorrelatedResponseOutcome,
received_connection_generation: WebDriverBiDiConnectionGeneration,
) -> Result<WebDriverBiDiCorrelatedResponse, WebDriverBiDiCommandCorrelationError> {
let outstanding = self.require_command_kind(command_id, expected_kind)?;
let expected_connection_generation = outstanding.connection_generation.ok_or(
WebDriverBiDiCommandCorrelationError::CommandConnectionProvenanceMissing { command_id },
)?;
if expected_connection_generation != received_connection_generation {
return Err(
WebDriverBiDiCommandCorrelationError::ResponseConnectionMismatch { command_id },
);
}
let _removed = self.outstanding.remove(&command_id);
Ok(WebDriverBiDiCorrelatedResponse {
command_id,
outcome,
connection_generation: Some(expected_connection_generation),
})
}
}

fn response_route(
envelope: &WebDriverBiDiJsonEnvelope,
) -> Result<(u64, WebDriverBiDiCorrelatedResponseOutcome), WebDriverBiDiCommandCorrelationError> {
match envelope.routing() {
WebDriverBiDiJsonEnvelopeRouting::Event => {
Err(WebDriverBiDiCommandCorrelationError::EventIsNotResponse)
}
WebDriverBiDiJsonEnvelopeRouting::CommandError { command_id: None } => {
Err(WebDriverBiDiCommandCorrelationError::UncorrelatableErrorResponse)
}
WebDriverBiDiJsonEnvelopeRouting::CommandError {
command_id: Some(command_id),
} => Ok((command_id, WebDriverBiDiCorrelatedResponseOutcome::Error)),
WebDriverBiDiJsonEnvelopeRouting::CommandSuccess { command_id } => {
Ok((command_id, WebDriverBiDiCorrelatedResponseOutcome::Success))
}
}
}

#[cfg(test)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,20 @@ use std::{
use originweave_core::WebDriverBiDiWebSocketEndpoint;

use crate::{
WebDriverBiDiCommandCorrelation, WebDriverBiDiCommandKind, WebDriverBiDiConnectionMessageRead,
WebDriverBiDiJsonEnvelope, WebDriverBiDiJsonEnvelopeError, WebDriverBiDiJsonEnvelopeKind,
WebDriverBiDiTcpConnectionPlan, WebDriverBiDiWebSocketClientKey,
WebDriverBiDiWebSocketHandshakePlan, WebDriverBiDiWebSocketMessageAssembler,
WebDriverBiDiWebSocketMessageAssembly,
WebDriverBiDiReceivedTextMessage, WebDriverBiDiSessionStatusResponseError,
WebDriverBiDiSessionStatusResult, WebDriverBiDiTcpConnectionPlan,
WebDriverBiDiWebSocketClientKey, WebDriverBiDiWebSocketHandshakePlan,
WebDriverBiDiWebSocketMessageReader,
};

const SESSION_ID: &str = "01234567-89ab-cdef-0123-456789abcdef";
const RFC6455_SAMPLE_KEY: &str = "dGhlIHNhbXBsZSBub25jZQ==";
const OPENING_RESPONSE: &[u8] = b"HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n\r\n";
const SUCCESS_MESSAGE: &[u8] =
br#"{"type":"success","id":7,"result":{"ready":true,"slash":"\/","upper":"\uABCD"}}"#;
const EMPTY_STATUS_RESULT: &[u8] = br#"{"type":"success","id":7,"result":{}}"#;

fn read_opening_request(stream: &mut TcpStream) -> io::Result<()> {
stream.set_read_timeout(Some(Duration::from_secs(2)))?;
Expand All @@ -38,9 +41,9 @@ fn read_opening_request(stream: &mut TcpStream) -> io::Result<()> {
Ok(())
}

fn parse_over_loopback(
fn read_text_over_loopback(
document: &'static [u8],
) -> Result<Result<WebDriverBiDiJsonEnvelope, WebDriverBiDiJsonEnvelopeError>, Box<dyn Error>> {
) -> Result<WebDriverBiDiReceivedTextMessage, Box<dyn Error>> {
if document.len() > 125 {
return Err(io::Error::other("unit JSON document exceeded one-byte frame length").into());
}
Expand All @@ -65,24 +68,30 @@ fn parse_over_loopback(
let established = WebDriverBiDiWebSocketHandshakePlan::new(connection, key)?
.write_opening_request(Duration::from_millis(500))?
.read_opening_response(Duration::from_millis(500))?;
let (_established, frame) = established.read_frame(Duration::from_millis(500))?;

let mut assembler = WebDriverBiDiWebSocketMessageAssembler::new();
let text = match assembler.push_frame(frame)? {
WebDriverBiDiWebSocketMessageAssembly::Text(text) => text,
let message = match WebDriverBiDiWebSocketMessageReader::new(established)
.read_next(Duration::from_millis(500))?
{
WebDriverBiDiConnectionMessageRead::Text { message, .. } => message,
other => {
return Err(io::Error::other(format!(
"validated text frame produced unexpected assembly state: {other:?}"
"validated text frame produced unexpected message state: {other:?}"
))
.into());
}
};
let parsed = WebDriverBiDiJsonEnvelope::parse(&text);

server
.join()
.map_err(|_| io::Error::other("JSON-envelope unit server panicked"))??;
Ok(parsed)
Ok(message)
}

fn parse_over_loopback(
document: &'static [u8],
) -> Result<Result<WebDriverBiDiJsonEnvelope, WebDriverBiDiJsonEnvelopeError>, Box<dyn Error>> {
let message = read_text_over_loopback(document)?;
Ok(WebDriverBiDiJsonEnvelope::parse(message.message()))
}

#[test]
Expand Down Expand Up @@ -138,3 +147,18 @@ fn public_json_envelope_unit_build_covers_fail_closed_json_edges() -> Result<(),
}
Ok(())
}

#[test]
fn public_session_status_empty_result_fails_closed_from_unit_build() -> Result<(), Box<dyn Error>> {
let message = read_text_over_loopback(EMPTY_STATUS_RESULT)?;
let mut correlation = WebDriverBiDiCommandCorrelation::new();
correlation.register_command_for(7, WebDriverBiDiCommandKind::SessionStatus)?;

let parsed = WebDriverBiDiSessionStatusResult::parse_and_correlate(&message, &mut correlation);
assert!(matches!(
parsed,
Err(WebDriverBiDiSessionStatusResponseError::MissingReady)
));
assert_eq!(correlation.outstanding_count(), 1);
Ok(())
}
Loading
Loading