Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
3211b01
test(network): require bounded BiDi command correlation
seonghobae Aug 30, 2026
ceacd54
test(network): canonicalize BiDi correlation regression
seonghobae Aug 30, 2026
cc48d0a
feat(network): correlate bounded BiDi responses
seonghobae Aug 30, 2026
a2ee2b4
style(network): apply canonical BiDi correlation format
seonghobae Aug 30, 2026
adc54f2
test(network): require redacted BiDi correlation diagnostics
seonghobae Aug 30, 2026
53b944b
fix(network): redact BiDi correlation debug state
seonghobae Aug 30, 2026
afef787
test(network): reject cross-command BiDi response correlation
seonghobae Aug 31, 2026
3170911
style(network): format command-kind correlation regression
seonghobae Aug 31, 2026
974b68b
fix(network): bind BiDi responses to command kind
seonghobae Aug 31, 2026
ac3dfa8
style(network): apply command-kind rustfmt output
seonghobae Aug 31, 2026
a5bd8ac
style(network): finish command-kind rustfmt output
seonghobae Aug 31, 2026
4b9a04e
fix(network): remove unreachable success correlation branch
seonghobae Sep 2, 2026
d835c35
fix(network): encode validated BiDi routing at correlation root
seonghobae Sep 3, 2026
43e428a
merge(parent): refresh command correlation base
seonghobae Sep 4, 2026
7349dc4
Merge remote-tracking branch 'origin/feat/webdriver-bidi-json-envelop…
seonghobae Sep 4, 2026
de7754a
merge(parent): refresh command correlation base
seonghobae Sep 5, 2026
e522826
test(docs): require BiDi correlation release record
seonghobae Sep 5, 2026
c518758
docs(changelog): record BiDi command correlation boundary
seonghobae Sep 5, 2026
77cf917
style(changelog): preserve trailing newline
seonghobae Sep 5, 2026
7d6db16
test(docs): scope BiDi release contract to owned record
seonghobae Sep 5, 2026
b386f17
fix(network): adopt message parent and enforce release test
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ All notable changes to OriginWeave are documented in this file. The format follo
- Deterministic WebDriver BiDi `browsingContext.locateNodes` command serialization that accepts only protocol-range `js-uint` identifiers and bounded injection-safe browsing-context identifiers, JSON-escapes reviewed query text, and emits the exact accessibility locator, finite node budget, and minimal serialization options without performing transport I/O or granting browser or Agent authority.
- Fail-closed WebDriver BiDi `locateNodes` response correlation that consumes the exact serialized command, rejects out-of-range or mismatched response `id` values, and returns non-cloneable correlation evidence carrying only the matched command identifier and browsing context without parsing the response or granting browser/Agent authority.
- Typed WebDriver BiDi response-envelope correlation that preserves success/error classification, rejects success responses with absent ids, treats nullable error ids as explicitly uncorrelatable, and prevents correlated error envelopes from becoming success correlation evidence without raw JSON parsing or browser/Agent authority grants.
- Bounded WebDriver BiDi outstanding-command correlation that retains at most 256 local ids with exact typed command-family provenance, consumes only matching success or correlatable error responses exactly once, leaves events, null-id errors, and kind mismatches unable to retire unrelated state, and performs no transport I/O or browser, policy, secret, or Agent authority grant.
- Bounded raw WebDriver BiDi response-document admission before JSON parsing, with a 65,536-byte product safety budget, exact wire-text retention, JSON-whitespace-aware top-level object-boundary checks, and typed fail-closed errors; this coarse boundary deliberately does not claim JSON validity, response correlation, browser authenticity, or Agent authority.
- One consuming bounded WebDriver BiDi response-document correlation boundary that parses the exact admitted JSON document and feeds only its typed response kind and protocol-range id into the existing exact command-correlation gate, preserving nested parser/correlation error sources without authenticating transport or granting browser/Agent authority.
- Consuming WebDriver BiDi WebSocket endpoint/session correlation that validates one caller-supplied canonical session UUID and rejects exact session mismatches before later transport use; the correlated type preserves only bounded endpoint metadata and does not authenticate Chromium, ChromeDriver, the caller, or the socket peer.
Expand Down Expand Up @@ -54,6 +55,7 @@ All notable changes to OriginWeave are documented in this file. The format follo

### Changed

- 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.
- 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
14 changes: 11 additions & 3 deletions crates/originweave-network/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@
//! It also bridges a session-correlated WebDriver BiDi loopback target from
//! `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, and
//! classifies complete local-end JSON envelopes without exposing generic JSON bodies
//! or granting browser, TLS, policy, secret, or Agent authority.
//! 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.

#![forbid(unsafe_code)]
#![deny(missing_docs)]

mod connection;
mod webdriver_bidi_command_correlation;
mod webdriver_bidi_connection;
mod webdriver_bidi_json_envelope;
mod webdriver_bidi_websocket_frame;
Expand All @@ -28,10 +30,16 @@ pub use connection::{
ConnectionPlan, DirectTcpConnection, MAX_CONNECT_TIMEOUT, MAX_CONNECTION_ATTEMPTS,
NetworkError, SocketConnectionEvidence,
};
pub use webdriver_bidi_command_correlation::{
MAX_WEBDRIVER_BIDI_OUTSTANDING_COMMANDS, WebDriverBiDiCommandCorrelation,
WebDriverBiDiCommandCorrelationError, WebDriverBiDiCommandKind,
WebDriverBiDiCorrelatedResponse, WebDriverBiDiCorrelatedResponseOutcome,
};
pub use webdriver_bidi_connection::{
WebDriverBiDiTcpConnection, WebDriverBiDiTcpConnectionError,
WebDriverBiDiTcpConnectionEvidence, WebDriverBiDiTcpConnectionPlan,
};
pub(crate) use webdriver_bidi_json_envelope::WebDriverBiDiJsonEnvelopeRouting;
pub use webdriver_bidi_json_envelope::{
MAX_WEBDRIVER_BIDI_JS_UINT, MAX_WEBDRIVER_BIDI_JSON_DEPTH, WebDriverBiDiJsonEnvelope,
WebDriverBiDiJsonEnvelopeError, WebDriverBiDiJsonEnvelopeKind,
Expand Down
287 changes: 287 additions & 0 deletions crates/originweave-network/src/webdriver_bidi_command_correlation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,287 @@
use std::{collections::BTreeMap, error::Error, fmt};

use crate::{
MAX_WEBDRIVER_BIDI_JS_UINT, WebDriverBiDiJsonEnvelope, WebDriverBiDiJsonEnvelopeRouting,
};

/// Maximum number of local WebDriver BiDi commands retained as outstanding at once.
///
/// WebDriver BiDi permits commands to complete out of order. OriginWeave therefore keeps a
/// bounded local correlation map instead of assuming response order, while this resource ceiling
/// prevents an unbounded remote-control session from growing local correlation state indefinitely.
pub const MAX_WEBDRIVER_BIDI_OUTSTANDING_COMMANDS: usize = 256;

/// Exact WebDriver BiDi command family bound to one outstanding local correlation identifier.
///
/// Command identifiers are local-end routing values rather than command-type provenance. Keeping
/// the reviewed command family beside each outstanding id prevents a success or protocol error for
/// one command from being consumed by a different typed response boundary that happens to receive
/// the same id. Additional command families are introduced by their owning typed command slices.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum WebDriverBiDiCommandKind {
/// WebDriver BiDi `session.status`.
SessionStatus,
/// WebDriver BiDi `session.end`.
SessionEnd,
}

/// Outcome of a response after it has consumed the matching outstanding command identifier.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum WebDriverBiDiCorrelatedResponseOutcome {
/// The remote end returned a successful command response.
Success,
/// The remote end returned a protocol error for the command.
Error,
}

/// Credential-free evidence that one parsed response consumed one outstanding local command.
///
/// This value carries only the matched command identifier and success/error classification. 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,
outcome: WebDriverBiDiCorrelatedResponseOutcome,
}

impl WebDriverBiDiCorrelatedResponse {
/// Return the local command identifier consumed by this response.
#[must_use]
pub const fn command_id(&self) -> u64 {
self.command_id
}

/// Return whether the correlated response was successful or a protocol error.
#[must_use]
pub const fn outcome(&self) -> WebDriverBiDiCorrelatedResponseOutcome {
self.outcome
}
}

/// Fail-closed command-correlation failures at the local WebDriver BiDi response boundary.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum WebDriverBiDiCommandCorrelationError {
/// A caller attempted to register an identifier outside WebDriver BiDi's `js-uint` range.
CommandIdOutOfRange,
/// The identifier is already outstanding and cannot become ambiguous.
CommandAlreadyOutstanding,
/// The reviewed outstanding-command resource budget has been reached.
OutstandingCommandLimit,
/// No currently outstanding command matches the requested or returned identifier.
CommandNotOutstanding,
/// The typed consumer does not match the command family registered for this identifier.
CommandKindMismatch {
/// Command family required by the typed consumer.
expected: WebDriverBiDiCommandKind,
/// Command family actually registered for the outstanding identifier.
actual: WebDriverBiDiCommandKind,
},
/// An event is not a command response and cannot consume correlation state.
EventIsNotResponse,
/// A protocol error with a `null` id cannot be attributed to one outstanding command.
UncorrelatableErrorResponse,
}

impl fmt::Display for WebDriverBiDiCommandCorrelationError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
let message = match self {
Self::CommandIdOutOfRange => "WebDriver BiDi command id is outside the js-uint range",
Self::CommandAlreadyOutstanding => "WebDriver BiDi command id is already outstanding",
Self::OutstandingCommandLimit => "WebDriver BiDi outstanding-command limit reached",
Self::CommandNotOutstanding => "WebDriver BiDi command id is not outstanding",
Self::CommandKindMismatch { .. } => {
"WebDriver BiDi response command kind does not match the outstanding command"
}
Self::EventIsNotResponse => "WebDriver BiDi event cannot be correlated as a response",
Self::UncorrelatableErrorResponse => {
"WebDriver BiDi error response has no correlatable command id"
}
};
formatter.write_str(message)
}
}

impl Error for WebDriverBiDiCommandCorrelationError {}

/// Bounded local WebDriver BiDi command-response correlation state.
///
/// Register an id together with its exact typed command family only after the caller has committed
/// to that outbound command. A success or correlatable error response consumes the id exactly once
/// only through a matching typed consumer. Events, null-id errors, and command-kind mismatches 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 and command families remain private correlation state.
#[derive(Default)]
pub struct WebDriverBiDiCommandCorrelation {
outstanding: BTreeMap<u64, WebDriverBiDiCommandKind>,
}

impl fmt::Debug for WebDriverBiDiCommandCorrelation {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("WebDriverBiDiCommandCorrelation")
.field("outstanding_count", &self.outstanding.len())
.finish()
}
}

impl WebDriverBiDiCommandCorrelation {
/// Create empty correlation state.
#[must_use]
pub fn new() -> Self {
Self::default()
}

/// Return the number of commands currently awaiting a correlatable response.
#[must_use]
pub fn outstanding_count(&self) -> usize {
self.outstanding.len()
}

/// Register one local command id and its exact command family before its response can be accepted.
///
/// Identifiers are unique only while outstanding. A completed or explicitly retired id may be
/// reused later, matching WebDriver BiDi's local-end correlation semantics. Reusing an id while
/// any command family is still outstanding fails before replacing its provenance.
pub fn register_command_for(
&mut self,
command_id: u64,
command_kind: WebDriverBiDiCommandKind,
) -> Result<(), WebDriverBiDiCommandCorrelationError> {
if command_id > MAX_WEBDRIVER_BIDI_JS_UINT {
return Err(WebDriverBiDiCommandCorrelationError::CommandIdOutOfRange);
}
if self.outstanding.contains_key(&command_id) {
return Err(WebDriverBiDiCommandCorrelationError::CommandAlreadyOutstanding);
}
if self.outstanding.len() >= MAX_WEBDRIVER_BIDI_OUTSTANDING_COMMANDS {
return Err(WebDriverBiDiCommandCorrelationError::OutstandingCommandLimit);
}
let _previous = self.outstanding.insert(command_id, command_kind);
Ok(())
}

/// Explicitly retire one exact outstanding command without accepting a response for it.
///
/// The expected command family must match the registered provenance. A mismatched caller cannot
/// retire another typed command merely by knowing or reusing its local correlation identifier.
pub fn retire_command_for(
&mut self,
command_id: u64,
expected_kind: WebDriverBiDiCommandKind,
) -> Result<(), WebDriverBiDiCommandCorrelationError> {
self.require_command_kind(command_id, expected_kind)?;
let _removed = self.outstanding.remove(&command_id);
Ok(())
}

/// Correlate one parsed local-end envelope with an exact outstanding command family.
///
/// 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.
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,
),
}
}

fn require_command_kind(
&self,
command_id: u64,
expected_kind: WebDriverBiDiCommandKind,
) -> Result<(), WebDriverBiDiCommandCorrelationError> {
let actual = self
.outstanding
.get(&command_id)
.copied()
.ok_or(WebDriverBiDiCommandCorrelationError::CommandNotOutstanding)?;
if actual != expected_kind {
return Err(WebDriverBiDiCommandCorrelationError::CommandKindMismatch {
expected: expected_kind,
actual,
});
}
Ok(())
}

fn complete(
&mut self,
command_id: u64,
expected_kind: WebDriverBiDiCommandKind,
outcome: WebDriverBiDiCorrelatedResponseOutcome,
) -> Result<WebDriverBiDiCorrelatedResponse, WebDriverBiDiCommandCorrelationError> {
self.require_command_kind(command_id, expected_kind)?;
let _removed = self.outstanding.remove(&command_id);
Ok(WebDriverBiDiCorrelatedResponse {
command_id,
outcome,
})
}
}

#[cfg(test)]
mod tests {
use super::{WebDriverBiDiCommandCorrelationError, WebDriverBiDiCommandKind};

#[test]
fn correlation_errors_have_stable_nonempty_operator_messages() {
let cases = [
(
WebDriverBiDiCommandCorrelationError::CommandIdOutOfRange,
"WebDriver BiDi command id is outside the js-uint range",
),
(
WebDriverBiDiCommandCorrelationError::CommandAlreadyOutstanding,
"WebDriver BiDi command id is already outstanding",
),
(
WebDriverBiDiCommandCorrelationError::OutstandingCommandLimit,
"WebDriver BiDi outstanding-command limit reached",
),
(
WebDriverBiDiCommandCorrelationError::CommandNotOutstanding,
"WebDriver BiDi command id is not outstanding",
),
(
WebDriverBiDiCommandCorrelationError::CommandKindMismatch {
expected: WebDriverBiDiCommandKind::SessionEnd,
actual: WebDriverBiDiCommandKind::SessionStatus,
},
"WebDriver BiDi response command kind does not match the outstanding command",
),
(
WebDriverBiDiCommandCorrelationError::EventIsNotResponse,
"WebDriver BiDi event cannot be correlated as a response",
),
(
WebDriverBiDiCommandCorrelationError::UncorrelatableErrorResponse,
"WebDriver BiDi error response has no correlatable command id",
),
];
for (error, expected) in cases {
assert_eq!(error.to_string(), expected);
}
}
}
Loading
Loading