Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
319372c
test(network): require session.status command send
seonghobae Aug 30, 2026
03511d9
test(network): format session.status RED regression
seonghobae Aug 30, 2026
748a5c5
feat(network): send typed BiDi session.status command
seonghobae Aug 30, 2026
4b6eeea
style(network): apply canonical session.status rustfmt
seonghobae Aug 30, 2026
2607a22
test(network): cover session.status fail-closed send paths
seonghobae Aug 30, 2026
491176c
test(network): remove vacuous session.status coverage branch
seonghobae Aug 30, 2026
9c8dd8b
test(network): keep production coverage scoped to production code
seonghobae Aug 30, 2026
bdc338b
style(network): apply canonical session.status test formatting
seonghobae Aug 30, 2026
c4c969f
fix(network): remove unstable coverage attribute
seonghobae Aug 30, 2026
45956f2
test(network): avoid residual branch in session.status serialization
seonghobae Aug 30, 2026
4e0d5e8
test(network): name session.status handshake fixture type
seonghobae Aug 30, 2026
d0876f1
test(network): cover both session.status id-range paths
seonghobae Aug 30, 2026
0230ae5
test(network): exercise rejected session.status ids externally
seonghobae Aug 30, 2026
f6ad019
style(network): apply canonical rustfmt
seonghobae Aug 30, 2026
04d6c12
test(network): bind session.status response kind
seonghobae Sep 1, 2026
8b5b454
test(network): bind duplicate status command kind
seonghobae Sep 1, 2026
9e4a64f
fix(network): bind session.status correlation kind
seonghobae Sep 1, 2026
f9ec8f4
fix(network): expose command kind to status slice
seonghobae Sep 1, 2026
f347273
merge(network): converge session.status on command-kind correlation
seonghobae Sep 1, 2026
9863d4f
restack(network): adopt repaired command-correlation parent
seonghobae Sep 3, 2026
9ab8181
chore(network): adopt verified BiDi correlation root
seonghobae Sep 4, 2026
eca63e0
style(network): preserve canonical trailing newline after restack
seonghobae Sep 4, 2026
06cf25f
test(network): reject invalid BiDi frame deadline before correlation
seonghobae Sep 4, 2026
0f71dac
fix(network): reject invalid BiDi frame deadline before correlation
seonghobae Sep 4, 2026
2b73482
test(network): cover both invalid BiDi frame deadline bounds
seonghobae Sep 4, 2026
3c9124b
docs(network): distinguish BiDi frame preflight from write ambiguity
seonghobae Sep 4, 2026
b293b35
test(network): expose pre-I/O mask reuse correlation leak
seonghobae Sep 4, 2026
8c7b975
fix(network): retire correlation on pre-I/O mask rejection
seonghobae Sep 4, 2026
b9e38b4
test(network): name mask-reuse outcome precisely
seonghobae Sep 4, 2026
0c6a6da
docs(network): doctor session.status transport contract
seonghobae Sep 4, 2026
117d759
docs(network): record bounded session.status command
seonghobae Sep 4, 2026
588514b
docs(network): correct current BiDi editor draft date
seonghobae Sep 4, 2026
422ac8c
docs: preserve RFC 5280 reference author
seonghobae Sep 4, 2026
6860fc5
merge(parent): refresh session status command base
seonghobae Sep 4, 2026
2636564
test(network): close session status coverage gap
seonghobae Sep 5, 2026
4438718
merge(parent): refresh session status base
seonghobae Sep 5, 2026
2279d18
merge(parent): refresh session status base
seonghobae Sep 5, 2026
017d6e8
merge: restack session status on current correlation
seonghobae Sep 5, 2026
84b9407
fix(network): adopt verified command correlation parent
seonghobae Sep 5, 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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ All notable changes to OriginWeave are documented in this file. The format follo
### Added

- 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.
- Exact WebDriver BiDi socket-peer verification that consumes an approved no-DNS connect target, requires the observed IP address and port to match exactly, preserves the TLS requirement and exact correlated session id, and remains inert metadata that does not authenticate an OS process, does not negotiate TLS, perform a WebSocket handshake, or grant Agent authority.
- Explicit no-DNS WebDriver BiDi loopback connection targets that derive exact IPv4/IPv6 loopback `SocketAddr` metadata from a session-correlated endpoint, reject `localhost` as requiring separately trusted name resolution, preserve the TLS requirement and exact session id, perform no socket I/O, and grant no Agent authority.
Expand Down Expand Up @@ -55,7 +56,9 @@ All notable changes to OriginWeave are documented in this file. The format follo

### Changed

- Kept the `session.status` frame-failure coverage contract focused on observable correlation state, avoiding assertion-internal uncovered branches without weakening preflight retirement or ambiguous-write retention checks.
- Made the command-correlation release-record check run in the existing CI test suite, preserving its exact bounds and authority exclusions; carried the verified message-parent fixture repairs into the correlation stack.
- Carried the verified parent fixture and release-check repairs into the session-status sender without changing command or correlation behavior.
- Aligned the bounded WebDriver BiDi error-envelope vocabulary with the current specification by admitting the defined `no such client window` response while retaining fail-closed rejection of unknown error codes.
- Separated logical origin authority from resolved network destination authority; an origin grant no longer implies permission to connect to every resolver result.
- Separated resolved-address authorization from direct transport evidence; an approved IP now becomes a usable stream only after the operating system reports the exact requested IP and port.
Expand Down
11 changes: 8 additions & 3 deletions crates/originweave-network/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@
//! `originweave-core` into one bounded exact TCP connection, binds and validates
//! 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, and tracks bounded command-response
//! correlation without exposing generic JSON bodies or granting browser, TLS,
//! policy, secret, or Agent authority.
//! 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.

#![forbid(unsafe_code)]
#![deny(missing_docs)]
Expand All @@ -18,6 +19,7 @@ mod connection;
mod webdriver_bidi_command_correlation;
mod webdriver_bidi_connection;
mod webdriver_bidi_json_envelope;
mod webdriver_bidi_session_status_command;
mod webdriver_bidi_websocket_frame;
mod webdriver_bidi_websocket_handshake;
mod webdriver_bidi_websocket_message;
Expand All @@ -44,6 +46,9 @@ pub use webdriver_bidi_json_envelope::{
MAX_WEBDRIVER_BIDI_JS_UINT, MAX_WEBDRIVER_BIDI_JSON_DEPTH, WebDriverBiDiJsonEnvelope,
WebDriverBiDiJsonEnvelopeError, WebDriverBiDiJsonEnvelopeKind,
};
pub use webdriver_bidi_session_status_command::{
WebDriverBiDiSessionStatusCommand, WebDriverBiDiSessionStatusCommandError,
};
pub use webdriver_bidi_websocket_frame::{
MAX_WEBSOCKET_FRAME_PAYLOAD_SIZE, MAX_WEBSOCKET_FRAME_TIMEOUT,
WebDriverBiDiWebSocketEstablished, WebDriverBiDiWebSocketFrame,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
use std::{error::Error, fmt, time::Duration};

use crate::{
MAX_WEBDRIVER_BIDI_JS_UINT, MAX_WEBSOCKET_FRAME_TIMEOUT, WebDriverBiDiCommandCorrelation,
WebDriverBiDiCommandCorrelationError, WebDriverBiDiCommandKind,
WebDriverBiDiWebSocketEstablished, WebDriverBiDiWebSocketFrameError,
WebDriverBiDiWebSocketMaskKey,
};

const SESSION_STATUS_METHOD: &str = "session.status";

/// One bounded WebDriver BiDi `session.status` command.
///
/// The command is deliberately concrete rather than a generic JSON or arbitrary-method escape
/// hatch. It carries only a WebDriver BiDi `js-uint` correlation identifier and always serializes
/// the standards-defined empty parameter map.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct WebDriverBiDiSessionStatusCommand {
command_id: u64,
}

impl WebDriverBiDiSessionStatusCommand {
/// Construct one `session.status` command with a JavaScript-safe correlation identifier.
pub fn new(command_id: u64) -> Result<Self, WebDriverBiDiSessionStatusCommandError> {
if command_id > MAX_WEBDRIVER_BIDI_JS_UINT {
return Err(
WebDriverBiDiSessionStatusCommandError::CommandIdOutOfRange {
command_id,
maximum_command_id: MAX_WEBDRIVER_BIDI_JS_UINT,
},
);
}
Ok(Self { command_id })
}

/// Return the exact local correlation identifier serialized for this command.
#[must_use]
pub const fn command_id(&self) -> u64 {
self.command_id
}

/// Register and write this exact command on an already established verified BiDi stream.
///
/// Locally invalid frame deadlines fail before correlation registration and before any remote
/// side effect. Correlation then registers the command before the first possible frame write.
/// A frame-owner preflight rejection that proves no write began retires this exact command
/// again; currently that covers adjacent client masking-key reuse. Once frame emission can have
/// begun, a later failure leaves the identifier outstanding because partial or full emission is
/// ambiguous. Callers must treat that failed stream/correlation pairing as unusable or
/// explicitly tear down its session state.
pub fn send(
self,
established: WebDriverBiDiWebSocketEstablished,
correlation: &mut WebDriverBiDiCommandCorrelation,
masking_key: WebDriverBiDiWebSocketMaskKey,
frame_timeout: Duration,
) -> Result<WebDriverBiDiWebSocketEstablished, WebDriverBiDiSessionStatusCommandError> {
if frame_timeout.is_zero() || frame_timeout > MAX_WEBSOCKET_FRAME_TIMEOUT {
return Err(WebDriverBiDiSessionStatusCommandError::FrameWrite {
source: WebDriverBiDiWebSocketFrameError::InvalidFrameTimeout {
frame_timeout,
maximum_timeout: MAX_WEBSOCKET_FRAME_TIMEOUT,
},
});
}
correlation
.register_command_for(self.command_id, WebDriverBiDiCommandKind::SessionStatus)
.map_err(|source| WebDriverBiDiSessionStatusCommandError::Correlation { source })?;
let message = self.serialized();
match established.write_text_frame(&message, masking_key, frame_timeout) {
Ok(established) => Ok(established),
Err(source) => Err(map_frame_failure(correlation, self.command_id, source)),
}
}

fn serialized(self) -> String {
format!(
"{{\"id\":{},\"method\":\"{SESSION_STATUS_METHOD}\",\"params\":{{}}}}",
self.command_id
)
}
}

fn map_frame_failure(
correlation: &mut WebDriverBiDiCommandCorrelation,
command_id: u64,
source: WebDriverBiDiWebSocketFrameError,
) -> WebDriverBiDiSessionStatusCommandError {
if matches!(
source,
WebDriverBiDiWebSocketFrameError::MalformedFrame { .. }
) {
let _retirement =
correlation.retire_command_for(command_id, WebDriverBiDiCommandKind::SessionStatus);
}
WebDriverBiDiSessionStatusCommandError::FrameWrite { source }
}

/// Fail-closed errors while constructing or sending one typed `session.status` command.
#[derive(Debug)]
pub enum WebDriverBiDiSessionStatusCommandError {
/// The requested command identifier is outside WebDriver BiDi's `js-uint` range.
CommandIdOutOfRange {
/// Rejected command identifier.
command_id: u64,
/// Largest JavaScript-safe identifier admitted by this boundary.
maximum_command_id: u64,
},
/// The bounded local correlation registry rejected the command before network I/O.
Correlation {
/// Exact typed correlation failure.
source: WebDriverBiDiCommandCorrelationError,
},
/// Frame preflight validation or a later write operation failed.
FrameWrite {
/// Exact typed bounded WebSocket frame validation/write failure.
source: WebDriverBiDiWebSocketFrameError,
},
}

impl fmt::Display for WebDriverBiDiSessionStatusCommandError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::CommandIdOutOfRange { .. } => formatter
.write_str("WebDriver BiDi session.status command id is outside the js-uint range"),
Self::Correlation { .. } => formatter
.write_str("WebDriver BiDi session.status command correlation was rejected"),
Self::FrameWrite { .. } => {
formatter.write_str("WebDriver BiDi session.status command frame write failed")
}
}
}
}

impl Error for WebDriverBiDiSessionStatusCommandError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::CommandIdOutOfRange { .. } => None,
Self::Correlation { source } => Some(source),
Self::FrameWrite { source } => Some(source),
}
}
}

#[cfg(test)]
mod tests {
use std::io;

use super::*;

#[test]
fn constructor_enforces_the_webdriver_bidi_js_uint_range() {
let accepted = WebDriverBiDiSessionStatusCommand::new(MAX_WEBDRIVER_BIDI_JS_UINT);
assert_eq!(
accepted.ok().map(|command| command.command_id()),
Some(MAX_WEBDRIVER_BIDI_JS_UINT)
);

let rejected = WebDriverBiDiSessionStatusCommand::new(MAX_WEBDRIVER_BIDI_JS_UINT + 1);
assert_eq!(
rejected.err().map(|error| error.to_string()).as_deref(),
Some("WebDriver BiDi session.status command id is outside the js-uint range")
);
}

#[test]
fn command_serialization_is_static_and_exact() {
let command = WebDriverBiDiSessionStatusCommand { command_id: 42 };
assert_eq!(command.command_id(), 42);
assert_eq!(
command.serialized(),
r#"{"id":42,"method":"session.status","params":{}}"#
);
}

#[test]
fn command_errors_have_stable_messages_and_typed_sources() {
let range = WebDriverBiDiSessionStatusCommandError::CommandIdOutOfRange {
command_id: MAX_WEBDRIVER_BIDI_JS_UINT + 1,
maximum_command_id: MAX_WEBDRIVER_BIDI_JS_UINT,
};
assert_eq!(
range.to_string(),
"WebDriver BiDi session.status command id is outside the js-uint range"
);
assert!(range.source().is_none());

let correlation = WebDriverBiDiSessionStatusCommandError::Correlation {
source: WebDriverBiDiCommandCorrelationError::CommandAlreadyOutstanding,
};
assert_eq!(
correlation.to_string(),
"WebDriver BiDi session.status command correlation was rejected"
);
assert!(correlation.source().is_some());

let frame = WebDriverBiDiSessionStatusCommandError::FrameWrite {
source: WebDriverBiDiWebSocketFrameError::FrameWriteFailed {
bytes_written: 0,
source: io::Error::other("test frame failure"),
},
};
assert_eq!(
frame.to_string(),
"WebDriver BiDi session.status command frame write failed"
);
assert!(frame.source().is_some());
}

#[test]
fn only_frame_preflight_malformed_errors_retire_registered_correlation() {
let mut correlation = WebDriverBiDiCommandCorrelation::new();
assert!(
correlation
.register_command_for(1, WebDriverBiDiCommandKind::SessionStatus)
.is_ok()
);
let preflight = WebDriverBiDiWebSocketFrameError::MalformedFrame {
reason: "test preflight rejection",
};
map_frame_failure(&mut correlation, 1, preflight);
assert_eq!(correlation.outstanding_count(), 0);

assert!(
correlation
.register_command_for(2, WebDriverBiDiCommandKind::SessionStatus)
.is_ok()
);
let ambiguous = WebDriverBiDiWebSocketFrameError::FrameWriteFailed {
bytes_written: 1,
source: io::Error::other("test ambiguous write failure"),
};
map_frame_failure(&mut correlation, 2, ambiguous);
assert_eq!(correlation.outstanding_count(), 1);
}
}
Loading
Loading