use std::{net::UdpSocket, thread, time::Duration}; use irtt_client::{Authentication, Client, ClientConfig, ClientEvent, HmacKey, SocketConfig}; use irtt_proto::{decode_request, verify_packet_hmac}; use irtt_server::{ServerConfig, ServerCore}; // Exercise the runtime-free API against the production server core, including // the wire HMAC flag on Open, Echo and Close for an explicitly empty key. #[test] fn concrete_authentication_preserves_session_and_empty_key_behavior() { for key in [None, Some(b"session-test-key".as_slice()), Some(&[])] { let socket = UdpSocket::bind("127.0.0.1:0").unwrap(); socket .set_read_timeout(Some(Duration::from_secs(2))) .unwrap(); let addr = socket.local_addr().unwrap(); let server = thread::spawn(move || { let config = key.map_or_else(ServerConfig::default, |key| { ServerConfig::default().with_hmac_key(key) }); let mut core = ServerCore::new(config); let mut buffer = [0; 2048]; for _ in 0..3 { let (len, peer) = socket.recv_from(&mut buffer).unwrap(); let packet = &buffer[..len]; assert_eq!(decode_request(packet).unwrap().hmac_present, key.is_some()); if let Some(key) = key { verify_packet_hmac(key, packet).unwrap(); } if let Some(reply) = core.handle_datagram(peer, packet).unwrap() { socket.send_to(reply.bytes(), peer).unwrap(); } } assert_eq!(core.session_count(), 0, "Close must release the session"); }); let mut client = Client::connect(ClientConfig { server_addr: addr.to_string(), auth: key.map_or(Authentication::Unauthenticated, |key| { Authentication::Hmac(HmacKey::new(key)) }), open_timeouts: vec![Duration::from_millis(200)], socket_config: SocketConfig { recv_timeout: Some(Duration::from_secs(1)), ..SocketConfig::default() }, ..ClientConfig::default() }) .unwrap(); client.open().unwrap(); client.send_probe().unwrap(); assert!(client .recv_once() .unwrap() .iter() .any(|event| { matches!(event, ClientEvent::EchoReply { .. }) })); client.close().unwrap(); server.join().unwrap(); } } #[test] fn authentication_debug_redacts_keys_in_enclosing_configurations() { let sentinel = b"CONSPICUOUS_HMAC_SECRET_DO_NOT_PRINT"; let key = HmacKey::new(sentinel.as_slice()); let auth = Authentication::Hmac(key.clone()); let config = ClientConfig { auth: auth.clone(), ..ClientConfig::default() }; let mut outputs = vec![ format!("{key:?}"), format!("{auth:?}"), format!("{config:?}"), ]; #[cfg(feature = "tokio")] { use irtt_client::managed::{ManagedClientConfig, ManagedTargetConfig, TargetAuth}; let mut target = ManagedTargetConfig::new("one", "127.0.0.1:2112"); target.auth = TargetAuth::Override(auth); outputs.push(format!("{target:?}")); outputs.push(format!( "{:?}", ManagedClientConfig { client: config, ..ManagedClientConfig::default() } )); } // Check both textual and ordinary byte-slice Debug encodings so replacing // the opaque formatter with a derived formatter cannot slip through. for output in outputs.drain(..) { assert!(!output.contains(std::str::from_utf8(sentinel).unwrap())); assert!(!output.contains(&format!("{sentinel:?}"))); assert!(output.contains("[REDACTED]")); } }