//! Test harness — the producer↔consumer wiring over a //! `tokio::io::duplex` carrying the channels 8-byte chunk format. //! //! Topology (the reverse POC's shape, mirrored for both directions): //! - **producer** (connect side of the channels connection — it runs //! `ChannelClient::from_connection_with_serving`): dials the //! duplex, serves the tunnel open op + generic channel ops on its //! serving registry. Its manager allocates the tunnel channel IDs //! (`ChannelSide::Connect` — ADR-047 §5: the serving side //! allocates; the initiator adopts). //! - **consumer** (accept side): the `ChannelsAdapter` + capturing //! install hook. The hook captures the consumer's channel-0 //! `CallConnection` (for calling the producer's open op) and the //! consumer's `ChannelManager` (for adopting the producer- //! allocated IDs and tearing channels down). The consumer serves //! no ops. //! //! Identity on the serving path follows alkcall 0.7.0's CF-005 //! precedence: payload `auth_token` → `ServingConfig.identity` → the //! transport connection's identity (`Connection::set_identity` before //! dialing). CF-006 (the per-call opener identity on the establisher) //! is probed via the `identity_witness` the establisher records. use std::sync::Arc; use alkcall::channels::adapter::{ChannelsAdapter, InstallChannelZero}; use alkcall::channels::client::{ChannelClient, ServingConfig}; use alkcall::channels::manager::ChannelManager; use alkcall::channels::operations::ChannelOperations; use alkcall::core::auth::{AuthContext, AuthToken, Identity, IdentityProvider}; use alkcall::core::types::Connection as CoreConnection; use alkcall::protocol::connection::{split_single_stream, CallConnection}; use alkcall::protocol::dispatch::Dispatcher; use alkcall::registry::registration::OperationRegistry; use alktunnels::params::{Substrate, TUNNEL_OPEN_SCOPE}; use alktunnels::producer::{register_tunnel_openable, ResourceRegistry}; /// The consumer identity the transport carries (the mTLS/QUIC /// analogue — key-based identity resolved out-of-band, attached to /// the dialing connection before `from_connection_with_serving`). pub fn consumer_identity() -> Identity { Identity { id: "consumer".to_string(), scopes: vec![TUNNEL_OPEN_SCOPE.to_string()], resources: std::collections::HashMap::new(), } } /// The scoped token (the hub-forwarding path) — resolves to /// [`consumer_identity`] via [`TestIdProvider`]. pub const TEST_AUTH_TOKEN: &str = "test-token-with-tunnel-open-scope"; /// Resolve the scoped token to the consumer identity; anything else /// resolves to none (falling through to the transport identity per /// the 0.7.0 precedence order). pub struct TestIdProvider; impl IdentityProvider for TestIdProvider { fn resolve_from_fingerprint(&self, _: &str) -> Option { None } fn resolve_from_token(&self, token: &AuthToken) -> Option { if token.raw == TEST_AUTH_TOKEN.as_bytes() { Some(consumer_identity()) } else { None } } } /// The full producer↔consumer topology over one duplex transport. pub struct Topology { /// The producer's client (connect side, serving). Its manager /// allocated the tunnel channels; its pumps are wrapper-managed. pub producer: Arc, /// The consumer's manager (accept side). `adopt` installs the /// producer-allocated IDs here; reaping calls `teardown_channel`. pub consumer_manager: ChannelManager, /// The consumer's channel-0 call connection — its surface for /// calling the producer's open op (and channel/close). pub consumer_call: Arc, /// The producer's serving registry (probes). pub producer_registry: Arc, /// What the establisher's per-call auth carried as the opener /// identity id (the CF-006 probe — `None` until an open runs). pub identity_witness: Arc>>, } /// Wire the topology. `transport_identity` is the key-based identity /// attached to the dialing connection (CF-005 remediation (b)); /// `serving_identity` is the explicit `ServingConfig.identity` /// override (CF-005 remediation (a)) — `None` in the primary path. /// `provider` resolves payload tokens (the hub-forwarding path). pub async fn wire_with( registry: ResourceRegistry, dial: alktunnels::producer::DialFn, transport_identity: Option, serving_identity: Option, provider: Arc, ) -> Topology { let producer_op_registry = Arc::new(OperationRegistry::new()); let (consumer_call_tx, mut consumer_call_rx) = tokio::sync::mpsc::channel::>(1); let (consumer_manager_tx, mut consumer_manager_rx) = tokio::sync::mpsc::channel::(1); // --- consumer (accept side): ChannelsAdapter + capturing hook ------ let install_hook: InstallChannelZero = Arc::new(move |manager, channel0_conn, _auth| { let call_tx = consumer_call_tx.clone(); let manager_tx = consumer_manager_tx.clone(); tokio::spawn(async move { let channel0_bidi = match channel0_conn.accept_bi().await { Ok(s) => s, Err(_) => return, }; let (writer, reader) = split_single_stream(channel0_bidi); let call_connection = Arc::new(CallConnection::new_single_stream( channel0_conn, Arc::clone(&writer), )); let _ = call_tx.send(Arc::clone(&call_connection)).await; let _ = manager_tx.send(manager).await; // The consumer serves no ops — an empty registry. The // dispatcher keeps the call loop alive (its read loop // resolves the producer's responses to the consumer's // pending entries). let dp = Dispatcher::new( Arc::new(OperationRegistry::new()), Arc::new(alkcall::core::auth::NoopIdentityProvider), ); dp.serve_single_stream(call_connection, reader, writer) .await; }) }); // --- transport ------------------------------------------------------ let (producer_end, consumer_end) = tokio::io::duplex(64 * 1024); let producer_conn = CoreConnection::from_bidi(producer_end, b"alk/channels".to_vec(), None); let consumer_conn = CoreConnection::from_bidi(consumer_end, b"alk/channels".to_vec(), None); let adapter = ChannelsAdapter::new(install_hook, Arc::new(alkcall::channels::policy::NoCap)); let consumer_transport_auth = AuthContext::anonymous(b"alk/channels"); let _adapter_task = tokio::spawn(async move { let _ = alkcall::core::types::ProtocolHandler::handle( &adapter, consumer_conn, &consumer_transport_auth, ) .await; }); // The transport identity attaches to the dialing connection // BEFORE from_connection_with_serving (the mTLS/QUIC posture). if let Some(id) = transport_identity { producer_conn .set_identity(id) .expect("transport identity set once"); } // --- producer (connect side, serving) -------------------------------- let producer_client = ChannelClient::from_connection_with_serving( producer_conn, Some(ServingConfig { registry: Arc::clone(&producer_op_registry), identity_provider: provider, identity: serving_identity, }), ) .await .expect("producer channel client init"); // The generic channel ops ride the producer's serving registry // (channel/close tears a channel down out-of-band — the review // 007 recipe). let ops = ChannelOperations::with_default_policy(producer_client.manager().clone()); ops.register_on(&producer_op_registry) .expect("register channel ops on producer"); // Post-hoc openable registration on the shared registry (W2). The // identity witness captures what the establisher's per-call auth // carried (the CF-006 probe). let identity_witness = Arc::new(tokio::sync::Mutex::new(None::)); let core = alkcall::channels::operations::ChannelCore::new( producer_client.manager().clone(), alkcall::channels::policy::default_policy(), ); register_tunnel_openable( &core, ®istry, &producer_op_registry, AuthContext::anonymous(b"alk/tunnel"), dial, Some(Arc::clone(&identity_witness)), ) .expect("register tunnel openable"); let producer_client = Arc::new(producer_client); // --- consumer pieces (captured from the hook) ------------------------- let consumer_call = consumer_call_rx.recv().await.expect("consumer call conn"); let consumer_manager = consumer_manager_rx.recv().await.expect("consumer manager"); Topology { producer: producer_client, consumer_manager, consumer_call, producer_registry: producer_op_registry, identity_witness, } } /// The default topology: the consumer's transport identity is the /// primary caller-identity path (no token). The provider is /// noop-shaped — only the identity chain authorizes. pub async fn wire(registry: ResourceRegistry, dial: alktunnels::producer::DialFn) -> Topology { wire_with( registry, dial, Some(consumer_identity()), None, Arc::new(alkcall::core::auth::NoopIdentityProvider), ) .await } /// An echo dial closure (the ADR-004 injection point under test): an /// in-process pipe per dial — the "target" echoes bytes until EOF. /// The consumer side gets split `DuplexStream` halves; a spawned task /// echoes the other end. No `local` feature, no sockets. pub fn echo_dial() -> alktunnels::producer::DialFn { Arc::new(move |substrate: Substrate, backing: &str| { let backing = backing.to_string(); Box::pin(async move { match substrate { Substrate::Tcp | Substrate::Unix => { let (consumer_side, target_side) = tokio::io::duplex(64 * 1024); let (mut t_read, mut t_write) = tokio::io::split(target_side); tokio::spawn(async move { use tokio::io::{AsyncReadExt, AsyncWriteExt}; let mut buf = vec![0u8; 8192]; loop { match t_read.read(&mut buf).await { Ok(0) | Err(_) => return, Ok(n) => { if t_write.write_all(&buf[..n]).await.is_err() { return; } } } } }); let _ = &backing; let (c_read, c_write) = tokio::io::split(consumer_side); Ok(alktunnels::producer::TargetHandle { read: Box::new(c_read), write: Box::new(c_write), }) } Substrate::Udp => Err(alktunnels::producer::TunnelEstablishError::DialFailed( format!("udp dial not wired in this harness: {backing}"), )), } }) }) } /// A failing dial closure (the `dial_failed` stand-in). pub fn failing_dial(message: &'static str) -> alktunnels::producer::DialFn { Arc::new(move |_substrate: Substrate, _backing: &str| { Box::pin(async move { Err(alktunnels::producer::TunnelEstablishError::DialFailed( message.to_string(), )) }) }) }