Files
xah30 6c14c0d103 feat(proto,cli): v3.1 multi-hop scaffold — control kinds + config sections
Foundation for v3.1 onion routing (client → entry-relay → exit-server).
The relay/circuit runtime is implemented in a follow-up commit; this
scaffold lands the wire-level control extensions and the config schema:

- aura-proto: ControlKind gains ExtendBridge (client→relay), CircuitReady
  (relay→client), CircuitFailed (relay→client, with utf-8 reason); helpers
  encode_extend_bridge / decode_extend_bridge (1-byte family + 4/16 addr
  bytes + u16 port). Integration test in tests/control_extend.rs covers
  IPv4/IPv6 roundtrip + full magic-envelope wrap.
- aura-cli config: [server.relay] {enabled, allow_extend_to} +
  [client.circuit] {enabled, hops} sections; relay_whitelist() helper
  parses IP:port literals. All new fields serde-default, back-compat.
- crl_push.rs touched only to leave the new ControlKinds passing through
  the existing magic-envelope dispatcher unchanged.

Workspace: 247 tests passed (+12), clippy/fmt clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 12:54:12 +03:00

71 lines
2.8 KiB
Rust

//! Integration test for v3.1 multi-hop control envelope payloads (`ExtendBridge`).
//!
//! Mirrors `frame.rs`'s in-crate unit coverage but at the integration level so an external
//! consumer of `aura-proto` (the CLI's `circuit` module) sees the same wire layout.
use std::net::SocketAddr;
use aura_proto::{
decode_control_envelope, decode_extend_bridge, encode_control_envelope, encode_extend_bridge,
ControlKind,
};
#[test]
fn extend_bridge_payload_roundtrips_ipv4() {
let addr: SocketAddr = "203.0.113.42:443".parse().unwrap();
let payload = encode_extend_bridge(addr);
assert_eq!(payload.len(), 1 + 4 + 2);
let got = decode_extend_bridge(&payload).expect("decode v4");
assert_eq!(got, addr);
}
#[test]
fn extend_bridge_payload_roundtrips_ipv6() {
let addr: SocketAddr = "[2001:db8::dead:beef]:1234".parse().unwrap();
let payload = encode_extend_bridge(addr);
assert_eq!(payload.len(), 1 + 16 + 2);
let got = decode_extend_bridge(&payload).expect("decode v6");
assert_eq!(got, addr);
}
#[test]
fn extend_bridge_via_full_envelope() {
// Build the bytes the client actually sends over the wire: the envelope wraps the payload.
let addr: SocketAddr = "10.0.0.5:443".parse().unwrap();
let payload = encode_extend_bridge(addr);
let envelope = encode_control_envelope(ControlKind::ExtendBridge, &payload);
let (kind, decoded_payload) = decode_control_envelope(&envelope).unwrap().unwrap();
assert_eq!(kind, ControlKind::ExtendBridge);
let got_addr = decode_extend_bridge(&decoded_payload).expect("decode addr from envelope");
assert_eq!(got_addr, addr);
}
#[test]
fn extend_bridge_rejects_malformed_payload() {
assert!(decode_extend_bridge(&[]).is_err());
assert!(decode_extend_bridge(&[4u8]).is_err()); // family but truncated
assert!(decode_extend_bridge(&[4u8, 1, 2, 3, 4]).is_err()); // missing port bytes
assert!(decode_extend_bridge(&[4u8, 1, 2, 3, 4, 0, 0, 99]).is_err()); // extra byte
assert!(decode_extend_bridge(&[6u8, 0, 0]).is_err()); // v6 truncated
assert!(decode_extend_bridge(&[7u8, 0, 0, 0, 0, 0, 0]).is_err()); // unknown family
}
#[test]
fn circuit_ready_envelope_has_empty_payload() {
let envelope = encode_control_envelope(ControlKind::CircuitReady, &[]);
let (kind, payload) = decode_control_envelope(&envelope).unwrap().unwrap();
assert_eq!(kind, ControlKind::CircuitReady);
assert!(payload.is_empty());
}
#[test]
fn circuit_failed_carries_utf8_reason() {
let envelope = encode_control_envelope(ControlKind::CircuitFailed, b"not in allow_extend_to");
let (kind, payload) = decode_control_envelope(&envelope).unwrap().unwrap();
assert_eq!(kind, ControlKind::CircuitFailed);
assert_eq!(
std::str::from_utf8(&payload).unwrap(),
"not in allow_extend_to"
);
}