- params.rs: OP_TUNNEL_FORWARDED / TUNNEL_FORWARDED_SCOPE; TunnelForwardedParams
{listen, substrate, peer} (deny-unknown; listen positive via schema minimum 1 +
a serde pin); tunnel_forwarded_spec() (Sub, External, alk/tunnel ALPN, scope
tunnel:forwarded, per-substrate if/then peer sub-schema, no establisher);
TunnelListenReply + parse_listen_reply (the additive bound/listen reply
projection — unknown reply fields tolerated, malformed ones fail loud)
- error.rs: TunnelError::MalformedReply (the viewer's fail-loud path)
- producer.rs: BindFirstPlan {listener: Arc<dyn Any + Send + Sync>, token: u32}
— the opaque assembly-typed listener + the correlation token; the mint
discipline pinned in docs (non-zero-seeded AtomicU32, per-open fresh,
per-producer unique, wraps acceptable)
- Wire-stable types pin first (the tunnels/params ordering); the establisher +
accept dispatcher + registration land with tunnels/bindfirst-producer
Verified: cargo test (105 default; 125 --all-features), clippy --all-targets
-D warnings (native + wasm32), fmt --check, wasm32-unknown-unknown check,
cargo doc --no-deps — all clean.
99 lines
4.1 KiB
Rust
99 lines
4.1 KiB
Rust
//! Tunnel error surfaces: the crate-level [`TunnelError`], the typed
|
|
//! session-open error ([`TunnelOpenError`], ADR-049 §4 surface), and
|
|
//! the session data-plane error ([`TunnelIoError`]).
|
|
|
|
use alkcall::channels::client::ChannelOpenError;
|
|
use alkcall::protocol::wire::CallError;
|
|
|
|
use crate::wire::DatagramCodecError;
|
|
|
|
/// The crate-level error type (thiserror; AGENTS.md convention 2).
|
|
#[derive(Debug, thiserror::Error)]
|
|
pub enum TunnelError {
|
|
/// The data-plane codec rejected an operation (UDP framing).
|
|
#[error("codec: {0}")]
|
|
Codec(String),
|
|
/// A substrate-shaped operation was attempted on the wrong
|
|
/// data plane (e.g. `send_datagram` on a stream tunnel).
|
|
#[error("wrong substrate for this operation")]
|
|
WrongSubstrate,
|
|
/// An I/O failure on the session's data plane.
|
|
#[error("io: {0}")]
|
|
Io(#[from] std::io::Error),
|
|
/// A reply field failed its typed projection — the wire carried a
|
|
/// malformed shape (e.g. [`crate::params::parse_listen_reply`]
|
|
/// found `bound` not substrate-shaped, a non-positive `listen`,
|
|
/// or a reply with no `channel_id` at all). A misbehaving
|
|
/// producer is never silently coerced.
|
|
#[error("malformed reply: {0}")]
|
|
MalformedReply(String),
|
|
}
|
|
|
|
/// A failed tunnel open or adopt. A failure never yields a session
|
|
/// (no phantom session, mirroring the no-phantom-channel property —
|
|
/// ADR-005). Branch on [`ChannelOpenError::establishment_reason`]
|
|
/// through [`TunnelOpenError::open_ref`] for `channel:open_failed`'s
|
|
/// reason (`dial_failed` / `unknown_resource` / `resource_shortage` /
|
|
/// `handler_error` / `timeout`), or the pre-establishment codes
|
|
/// (`FORBIDDEN`, `channel:too_many_channels`).
|
|
#[derive(Debug, thiserror::Error)]
|
|
pub enum TunnelOpenError {
|
|
/// The open op failed, or the reply/adopt failed — the typed
|
|
/// [`ChannelOpenError`] carries the wire [`CallError`] verbatim
|
|
/// (adopt failures surface as `AdoptFailed`).
|
|
#[error("tunnel open failed: {0}")]
|
|
Open(#[from] ChannelOpenError),
|
|
}
|
|
|
|
impl TunnelOpenError {
|
|
/// The underlying [`ChannelOpenError`] (the ADR-049 typed surface).
|
|
pub fn open_ref(&self) -> &ChannelOpenError {
|
|
match self {
|
|
TunnelOpenError::Open(e) => e,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// An I/O failure on the session's data plane, or a substrate-shaped
|
|
/// operation attempted on the wrong plane (the POC's shape).
|
|
#[derive(Debug, thiserror::Error)]
|
|
pub enum TunnelIoError {
|
|
/// The underlying stream failed.
|
|
#[error("io: {0}")]
|
|
Io(#[from] std::io::Error),
|
|
/// The datagram codec rejected the operation (e.g. `Oversize` —
|
|
/// a >65535-byte send, rejected at frame time per ADR-003).
|
|
#[error("codec: {0}")]
|
|
Codec(#[from] DatagramCodecError),
|
|
/// The stream ended mid-datagram — declared length not satisfied
|
|
/// (a clean peer never does this; the fail-loud posture, OQ-TN-13
|
|
/// / ADR-003: never a silent partial datagram).
|
|
#[error("stream ended mid-datagram (truncated)")]
|
|
TruncatedDatagram,
|
|
/// A substrate-shaped operation was attempted on the wrong data
|
|
/// plane (e.g. `send_datagram` on a stream tunnel).
|
|
#[error("wrong substrate for this operation")]
|
|
WrongSubstrate,
|
|
/// The session's channel halves were already taken — by
|
|
/// `take_halves` (caller-driven) or `pump_against` (pump-owned);
|
|
/// no data plane remains on the session.
|
|
#[error("channel halves already taken from this session")]
|
|
ChannelTaken,
|
|
}
|
|
|
|
/// The reverse-path open call failed (`open_reverse_channel` — the
|
|
/// assembly layer calls it before [`crate::consumer::TunnelSession::
|
|
/// adopt`]; the hub's call surface is a `CallConnection`, so the
|
|
/// two-step is the honest API).
|
|
#[derive(Debug, thiserror::Error)]
|
|
pub enum ReverseOpenError {
|
|
/// The open op failed — the wire [`CallError`]; branch on
|
|
/// `details.reason` of `channel:open_failed` (ADR-049 §4).
|
|
#[error("open call failed: {0:?}")]
|
|
Call(CallError),
|
|
/// The open op succeeded but the reply carried no `channel_id`
|
|
/// (malformed responder).
|
|
#[error("open reply missing channel_id")]
|
|
NoChannelId,
|
|
}
|