Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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 @@ -6,8 +6,10 @@ All notable changes to OriginWeave are documented in this file. The format follo

### Added

- Session-ending replies from a replacement connection can no longer complete the original pending request. The original reply remains usable, and a protocol acknowledgment still does not prove browser shutdown or cleanup.
- The session-ending command stack now retains the status-reply protections from its current parent. A reply from a replacement connection is rejected while the original pending status request remains recoverable; sending the end command still does not prove that the browser session ended.
- Typed outbound WebDriver BiDi `session.end` over the bounded client WebSocket stream: it serializes only the standards-defined method with empty params, rejects invalid frame deadlines before correlation registration, retires only the just-registered id when frame preflight proves no command bytes were emitted, preserves exact command-kind correlation across ambiguous writes, and does not treat frame-write success as proof that the browser session ended.
- Typed `session.end` response admission that consumes only the exact outstanding command-kind correlation after complete envelope validation, preserves remote protocol errors as failures, and does not claim browser-process exit or resource cleanup from a protocol acknowledgment.
- 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.
Expand Down Expand Up @@ -60,6 +62,7 @@ All notable changes to OriginWeave are documented in this file. The format follo

### Changed

- Carried verified command prerequisites and the executable release-record check into session-end response validation without changing response admission or treating an acknowledgment as proof of resource cleanup.
- Carried the verified status-response prerequisites into the session-end sender, preserving its command behavior and making the inherited release-record check execute in the existing test suite.
- 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.
Expand Down
9 changes: 6 additions & 3 deletions crates/originweave-network/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@
//! unmasked server-frame reads, assembles bounded WebDriver BiDi text messages,
//! classifies complete local-end JSON envelopes, tracks bounded command-response
//! correlation, sends narrowly typed `session.status` and `session.end` commands,
//! and admits the required readiness result through one command-specific correlated
//! parser without exposing generic JSON bodies or granting browser, TLS, policy,
//! secret, or Agent authority.
//! and admits typed correlated status and end responses without exposing generic
//! JSON bodies or granting browser, TLS, policy, secret, or Agent authority.

#![forbid(unsafe_code)]
#![deny(missing_docs)]
Expand All @@ -22,6 +21,7 @@ mod webdriver_bidi_connection;
mod webdriver_bidi_json_envelope;
mod webdriver_bidi_received_message;
mod webdriver_bidi_session_end_command;
mod webdriver_bidi_session_end_response;
mod webdriver_bidi_session_status_command;
mod webdriver_bidi_session_status_response;
mod webdriver_bidi_websocket_frame;
Expand Down Expand Up @@ -57,6 +57,9 @@ pub use webdriver_bidi_received_message::{
pub use webdriver_bidi_session_end_command::{
WebDriverBiDiSessionEndCommand, WebDriverBiDiSessionEndCommandError,
};
pub use webdriver_bidi_session_end_response::{
WebDriverBiDiSessionEndResponseError, WebDriverBiDiSessionEndResult,
};
pub use webdriver_bidi_session_status_command::{
WebDriverBiDiSessionStatusCommand, WebDriverBiDiSessionStatusCommandError,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ impl WebDriverBiDiSessionEndCommand {
/// 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.
/// side effect. Correlation then binds the command to this connection before the first possible
/// frame write. Only a reply received on this same connection can complete that registration.
/// A frame-owner preflight rejection that proves no write began retires this exact command
/// again. Once frame emission can have begun, a later failure leaves the identifier outstanding
/// because partial or full emission is ambiguous. A successful write also leaves the identifier
Expand All @@ -62,7 +63,11 @@ impl WebDriverBiDiSessionEndCommand {
});
}
correlation
.register_command_for(self.command_id, WebDriverBiDiCommandKind::SessionEnd)
.register_command_for_connection(
self.command_id,
WebDriverBiDiCommandKind::SessionEnd,
established.transport_evidence().connection_generation(),
)
.map_err(|source| WebDriverBiDiSessionEndCommandError::Correlation { source })?;
let message = self.serialized();
match established.write_text_frame(&message, masking_key, frame_timeout) {
Expand Down
142 changes: 142 additions & 0 deletions crates/originweave-network/src/webdriver_bidi_session_end_response.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
use std::{error::Error, fmt};

use crate::{
WebDriverBiDiCommandCorrelation, WebDriverBiDiCommandCorrelationError,
WebDriverBiDiCommandKind, WebDriverBiDiCorrelatedResponseOutcome, WebDriverBiDiJsonEnvelope,
WebDriverBiDiJsonEnvelopeError, WebDriverBiDiReceivedTextMessage,
};

/// Typed protocol acknowledgment for one correlated WebDriver BiDi `session.end` command.
///
/// WebDriver BiDi defines `session.EndResult` as the extensible `EmptyResult` object. The common
/// local-end envelope parser already validates the complete JSON document and requires a success
/// `result` object, so this command-specific boundary intentionally retains no generic result body
/// and accepts extension members. This value proves only that the remote end returned a correlated
/// protocol success; it does not prove Chromium process exit, profile deletion, resource release,
/// or any other OriginWeave operational teardown postcondition.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct WebDriverBiDiSessionEndResult {
command_id: u64,
}

impl WebDriverBiDiSessionEndResult {
/// Parse one bounded local-end message and consume its exact outstanding command on response.
///
/// Complete JSON and common WebDriver BiDi envelope validation occur before correlation state
/// can be consumed. Successful responses retain only the matched command id. A correlatable
/// protocol-error response consumes its matching id and returns a typed remote failure, while
/// events, null-id errors, malformed envelopes, unknown ids, and command-kind mismatches fail
/// closed without consuming unrelated outstanding state. Only a sealed reply from the same
/// connection that registered the command can consume it; a replacement connection cannot
/// complete the request even when its session and command identifiers match.
pub fn parse_and_correlate(
message: &WebDriverBiDiReceivedTextMessage,
correlation: &mut WebDriverBiDiCommandCorrelation,
) -> Result<Self, WebDriverBiDiSessionEndResponseError> {
let envelope = WebDriverBiDiJsonEnvelope::parse(message.message())
.map_err(|source| WebDriverBiDiSessionEndResponseError::Envelope { source })?;
let completed = correlation
.correlate_response_for_connection(
&envelope,
WebDriverBiDiCommandKind::SessionEnd,
message.connection_generation(),
)
.map_err(|source| WebDriverBiDiSessionEndResponseError::Correlation { source })?;

match completed.outcome() {
WebDriverBiDiCorrelatedResponseOutcome::Success => Ok(Self {
command_id: completed.command_id(),
}),
WebDriverBiDiCorrelatedResponseOutcome::Error => {
Err(WebDriverBiDiSessionEndResponseError::RemoteProtocolError {
command_id: completed.command_id(),
})
}
}
}

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

/// Fail-closed failures while admitting one typed WebDriver BiDi `session.end` response.
#[derive(Debug)]
pub enum WebDriverBiDiSessionEndResponseError {
/// Common local-end JSON envelope validation failed before correlation state was touched.
Envelope {
/// Exact common-envelope validation failure.
source: WebDriverBiDiJsonEnvelopeError,
},
/// Exact command-response correlation failed without consuming unrelated state.
Correlation {
/// Exact typed correlation failure.
source: WebDriverBiDiCommandCorrelationError,
},
/// The remote end returned a correlatable WebDriver BiDi protocol error for this command.
RemoteProtocolError {
/// Exact local command identifier consumed by the protocol-error response.
command_id: u64,
},
}

impl fmt::Display for WebDriverBiDiSessionEndResponseError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Envelope { .. } => {
formatter.write_str("WebDriver BiDi session.end envelope is invalid")
}
Self::Correlation { .. } => {
formatter.write_str("WebDriver BiDi session.end response correlation failed")
}
Self::RemoteProtocolError { .. } => {
formatter.write_str("WebDriver BiDi session.end returned a protocol error")
}
}
}
}

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

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn response_errors_have_stable_messages_and_typed_sources() {
let envelope = WebDriverBiDiSessionEndResponseError::Envelope {
source: WebDriverBiDiJsonEnvelopeError::InvalidJson,
};
assert_eq!(
envelope.to_string(),
"WebDriver BiDi session.end envelope is invalid"
);
assert!(envelope.source().is_some());

let correlation = WebDriverBiDiSessionEndResponseError::Correlation {
source: WebDriverBiDiCommandCorrelationError::CommandNotOutstanding,
};
assert_eq!(
correlation.to_string(),
"WebDriver BiDi session.end response correlation failed"
);
assert!(correlation.source().is_some());

let remote = WebDriverBiDiSessionEndResponseError::RemoteProtocolError { command_id: 7 };
assert_eq!(
remote.to_string(),
"WebDriver BiDi session.end returned a protocol error"
);
assert!(remote.source().is_none());
}
}
Loading
Loading