Files
alksocks/src/params.rs
T
glm-5.3-flash ff3295422c params: the error-schema declaration + the wire hygiene residue (review-002 D-3, N-3, N-9)
- D-3: socks5_open_spec declares the channel:open_failed
  ErrorDefinition (alktty-ported schema, adapted) — reason enum is
  the full ADR-049 §3 five-code vocabulary matching
  SocksEstablishmentReason's parse table; description carries this
  crate's gate classes (FORBIDDEN / INVALID_INPUT /
  channel:too_many_channels) + the no-phantom-channel property.
  Pinned by open_spec_declares_the_open_failed_error_definition,
  which round-trips every enum value through the typed vocabulary —
  schema and typed view cannot drift. Module doc updated from claim
  to fact.
- N-3: DatagramReader::feed's structurally-unreachable let-else
  restructured — the header read folds into the let-else's else arm
  (live-else shape); zero behavior change, wire suite green
  unchanged.
- N-9: is_mid_frame kept; doc names the anticipated batch-2 consumer
  (the front-door bridges) so dead-surface sweeps don't flag it.

Verification:
- cargo test: 357 passed, 0 failed (all suites)
- cargo clippy --all-targets -- -D warnings: clean
- cargo fmt --check: clean
- cargo doc --no-deps: clean
- cargo check --target wasm32-unknown-unknown: clean
- cargo clippy --target wasm32-unknown-unknown -- -D warnings: clean

Task: tasks/implementation/fix-hygiene-residue.md (completed)
2026-09-24 18:41:42 +00:00

294 lines
12 KiB
Rust

//! The trivial open-op params, the wire-stable constants, and the
//! `OperationSpec` builder — `wire.md` §The Open Op, ADR-001, ADR-006.
//!
//! This module is the crate's wire surface (the one-way door): the op
//! id, the ALPN string, the two scope strings, and the trivial params
//! shape are frozen at first publish (ADR-001). The open-op params are
//! **trivially empty** — the SOCKS5 target is unknown at open time (it
//! arrives in-stream post-negotiation); the establisher is a session
//! gate, not a dial. The empty-object schema with
//! `additionalProperties: false` keeps `{}` valid forever: any future
//! per-session option is an *additive* schema extension (ADR-001's
//! extension rule) — consumers that send `{}` stay valid.
//!
//! All three RFC 1928 commands (CONNECT, BIND, UDP ASSOCIATE) ride the
//! one open op, selected in-stream at command dispatch (ADR-001); BIND
//! is scope-gated separately at that dispatch (`SOCKS5_BIND_SCOPE`,
//! ADR-006) — the command is an RFC-1928 wire fact, not a wire-format
//! choice of ours.
//!
//! The typed open-error vocabulary ([`crate::error::SocksOpenError`],
//! the `channel:open_failed` reason mapping) lives in [`crate::error`]
//! (alkcall ADR-049 §4); the establishment-reason extraction helper is
//! re-exported here's-error-shaped via [`establishment_reason`].
use alkcall::channels::client::ChannelOpenError;
use alkcall::registry::spec::{
AccessControl, ChannelOpenSpec, ErrorDefinition, OperationSpec, OperationType, Visibility,
};
use serde::{Deserialize, Serialize};
use serde_json::json;
/// The open-op operation id (the one SOCKS5 session op; ADR-001 —
/// flavor-form per alkcall ADR-047: openable ALPNs are operations).
/// All three RFC 1928 commands ride this one op. Wire-stable.
pub const OP_SOCKS5_OPEN: &str = "channels/socks5/sub";
/// The data-plane ALPN the SOCKS5 channel carries (ADR-001; the
/// `alk/` prefix per the alkcall ADR-004 ALPN convention). One ALPN
/// per protocol. Wire-stable.
pub const SOCKS5_ALPN: &str = "alk/socks5";
/// The scope gating SOCKS5 opens (ADR-006): proxy egress — CONNECT
/// and UDP ASSOCIATE. The scope-string convention
/// `<alpn-family>:<capability>`. Stable once published.
pub const SOCKS5_OPEN_SCOPE: &str = "socks5:open";
/// The scope gating TCP BIND at command dispatch (ADR-006): the
/// *inbound* capability — ingress listeners. A separate grant from
/// [`SOCKS5_OPEN_SCOPE`], **never implied by it**: an identity with
/// proxy egress must not implicitly get ingress listeners. Checked
/// in-stream at command read, from the per-call opener identity
/// (CF-005/CF-006); a scope-less identity gets the RFC-faithful
/// `ConnectionNotAllowed` (0x02) refusal while CONNECT still serves
/// it. Stable once published.
pub const SOCKS5_BIND_SCOPE: &str = "socks5:bind";
/// The open-op input params (ADR-001): the trivial object. Wire-stable
/// — the shape is frozen (empty object, nothing required, nothing
/// else); a future per-session option is an additive schema extension,
/// never a format change.
///
/// `deny_unknown_fields` is the loud-rejection posture: unknown fields
/// fail at parse time, never silently ignored (matching the input
/// schema's `additionalProperties: false` — the registry's gate and
/// this serde pin agree). Empty params stay valid forever.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Socks5Params {}
impl Socks5Params {
/// The canonical wire form: the empty object — the only valid
/// params value (ADR-001).
pub fn as_json() -> serde_json::Value {
json!({})
}
}
/// The open-op spec (`channels/socks5/sub`, ADR-001/ADR-006):
/// `Sub`-typed, external, channel-open marker on the `alk/socks5`
/// ALPN, scope-gated by `socks5:open`, trivial params.
///
/// - Input schema: `{type: object}` + `additionalProperties: false` —
/// empty params stay valid forever (ADR-001's additive-only
/// extension rule); unknown fields are rejected loudly.
/// - Reply schema: `{"channel_id": <u32>, minimum 1}` — the
/// `Sub`-typed operation reply. No extra reply fields in v1 (the
/// establishment phase is a session gate and has nothing to
/// project; alkcall's `with_reply_field` seam remains available
/// additively if a future need appears — ADR-001).
/// - `required_scopes = ["socks5:open"]` — the registry runs the ACL
/// before the open handler (ADR-006; the open gate is the
/// boundary).
///
/// `error_schemas` declare the `channel:open_failed` contract
/// (alkcall ADR-049 §3/§4 + ADR-016): the establisher's refusal
/// reasons (`dial_failed` / `unknown_resource` / `resource_shortage`
/// / `handler_error` / `timeout` — the ADR-049 §3 vocabulary, the
/// same five codes [`crate::error::SocksEstablishmentReason`] parses)
/// plus the registry's own `FORBIDDEN` class (the ACL gate runs
/// before the establisher; ADR-006). The wrapper's wire shape is
/// `details: { reason, message }` (ADR-049 §3).
///
/// The typed open-error vocabulary lives in [`crate::error`]
/// ([`crate::error::SocksOpenError`] + its
/// [`crate::error::SocksOpenError::reason`] typed view).
pub fn socks5_open_spec() -> OperationSpec {
let open_failed_schema = json!({
"type": "object",
"properties": {
"reason": {
"type": "string",
"enum": [
"dial_failed",
"unknown_resource",
"resource_shortage",
"handler_error",
"timeout"
]
},
"message": { "type": "string" }
},
"required": ["reason", "message"]
});
OperationSpec::new(
OP_SOCKS5_OPEN,
OperationType::Sub,
Visibility::External,
json!({
"type": "object",
"additionalProperties": false
}),
json!({
"type": "object",
"properties": {
"channel_id": { "type": "integer", "minimum": 1 }
},
"required": ["channel_id"]
}),
vec![ErrorDefinition {
code: "channel:open_failed".to_string(),
description: "The channel open failed: the registry's ACL gate refused \
(FORBIDDEN — no socks5:open scope), the params payload did \
not match the schema (INVALID_INPUT), the per-connection \
channel cap was hit (channel:too_many_channels), or the \
establishment phase failed (details.reason ∈ the ADR-049 \
§3 vocabulary). No channel_id is returned — no phantom \
channel (ADR-049)."
.to_string(),
schema: open_failed_schema,
http_status: None,
}],
AccessControl {
required_scopes: vec![SOCKS5_OPEN_SCOPE.to_string()],
required_scopes_any: None,
resource_type: None,
resource_action: None,
},
None,
)
.with_channel_open(ChannelOpenSpec::new(SOCKS5_ALPN))
.with_description(
"Open a SOCKS5 proxy channel (RFC 1928 rides the channel BiStream; \
trivial params — the target arrives in-stream post-negotiation)",
)
}
/// The typed open-failure reason of a `channel:open_failed` error
/// (alkcall ADR-049 §4): the consumer-side establishment-failure
/// vocabulary. `None` for any other failure shape (the registry's
/// `FORBIDDEN`, `channel:too_many_channels`, local-only failures).
///
/// Delegates to [`ChannelOpenError::establishment_reason`] — the
/// alktunnels helper posture. The typed vocabulary lives in
/// [`crate::error`] ([`crate::error::SocksEstablishmentReason`], the
/// parsed view a consumer branches on via
/// [`crate::error::SocksOpenError::reason`]); use this directly for
/// the raw reason string.
pub fn establishment_reason(err: &ChannelOpenError) -> Option<&str> {
err.establishment_reason()
}
#[cfg(test)]
mod tests {
use super::*;
use alkcall::protocol::wire::CallError;
#[test]
fn open_spec_matches_adr_001_and_wire_md() {
let spec = socks5_open_spec();
assert_eq!(spec.name, "channels/socks5/sub");
assert_eq!(spec.op_type, OperationType::Sub);
assert_eq!(spec.visibility, Visibility::External);
// Trivial params: empty object, unknown fields rejected loudly
// (ADR-001 — empty params stay valid forever).
assert_eq!(spec.input_schema["type"], "object");
assert_eq!(spec.input_schema["additionalProperties"], json!(false));
assert!(spec.input_schema.get("required").is_none());
assert_eq!(
spec.output_schema["properties"]["channel_id"],
json!({"type": "integer", "minimum": 1})
);
assert_eq!(spec.output_schema["required"], json!(["channel_id"]));
assert_eq!(
spec.access_control.required_scopes,
vec![SOCKS5_OPEN_SCOPE.to_string()]
);
assert!(spec.access_control.required_scopes_any.is_none());
let channel_open = spec.channel_open.as_ref().expect("channel-open marker");
assert_eq!(channel_open.alpn, SOCKS5_ALPN);
assert!(spec.description.is_some());
}
#[test]
fn constants_match_adr_001_and_adr_006() {
assert_eq!(OP_SOCKS5_OPEN, "channels/socks5/sub");
assert_eq!(SOCKS5_ALPN, "alk/socks5");
assert_eq!(SOCKS5_OPEN_SCOPE, "socks5:open");
assert_eq!(SOCKS5_BIND_SCOPE, "socks5:bind");
}
#[test]
fn trivial_params_serde_round_trip() {
let v = serde_json::to_value(Socks5Params::default()).unwrap();
assert_eq!(v, json!({}));
assert_eq!(v, Socks5Params::as_json());
let back: Socks5Params = serde_json::from_value(json!({})).unwrap();
assert_eq!(back, Socks5Params::default());
}
#[test]
fn trivial_params_reject_unknown_fields() {
// The loud-rejection posture — the serde pin agrees with the
// input schema's additionalProperties: false (ADR-001).
let v = json!({"resource": "sneaky"});
assert!(serde_json::from_value::<Socks5Params>(v).is_err());
let v = json!({"substrate": "tcp"});
assert!(serde_json::from_value::<Socks5Params>(v).is_err());
}
#[test]
fn establishment_reason_reads_the_typed_surface() {
let open_failed = ChannelOpenError::CallFailed {
error: CallError::new("channel:open_failed", "establishment failed", false)
.with_details(json!({"reason": "dial_failed"})),
};
assert_eq!(establishment_reason(&open_failed), Some("dial_failed"));
let registry_gate = ChannelOpenError::CallFailed {
error: CallError::internal("forbidden"),
};
assert_eq!(establishment_reason(&registry_gate), None);
let local_only = ChannelOpenError::MissingChannelId;
assert_eq!(establishment_reason(&local_only), None);
}
#[test]
fn open_spec_declares_the_open_failed_error_definition() {
let spec = socks5_open_spec();
let def = spec
.error_schemas
.iter()
.find(|e| e.code == "channel:open_failed")
.expect("channel:open_failed ErrorDefinition declared");
assert!(
!def.description.is_empty(),
"the failure contract is described"
);
let props = &def.schema["properties"];
assert!(props["message"]["type"] == "string");
assert_eq!(def.schema["required"], json!(["reason", "message"]));
let enum_values = props["reason"]["enum"].as_array().expect("reason enum");
let reasons: Vec<&str> = enum_values.iter().filter_map(|v| v.as_str()).collect();
// The declared vocabulary is exactly the typed parse table
// (crate::error::SocksEstablishmentReason) — the schema and
// the typed view cannot drift.
assert_eq!(
reasons,
vec![
"dial_failed",
"unknown_resource",
"resource_shortage",
"handler_error",
"timeout"
]
);
for reason in &reasons {
assert_eq!(
crate::error::SocksEstablishmentReason::parse(reason).map(|r| r.as_str()),
Some(*reason),
"declared reason {reason} is a typed ADR-049 §3 code"
);
}
}
}