#![allow(clippy::unwrap_used, clippy::expect_used)]
use std::sync::Arc;
use parking_lot::Mutex;
use rama_core::{
Layer, Service, ServiceInput, bytes::Bytes, error::BoxError, extensions::ExtensionsRef,
service::service_fn,
};
use rama_haproxy::{
client::{HaProxyLayer as ClientLayer, protocol as client_protocol, version as client_version},
protocol::v2,
server::{HaProxyCommand, HaProxyService as ServerService, HaProxyTlvs},
};
use rama_net::{
address::{Domain, SocketAddress},
client::{ConnectionError, ConnectionErrorDomain, ConnectionErrorKind},
forwarded::Forwarded,
stream::SocketInfo,
test_utils::client::MockConnectorService,
};
#[derive(Default)]
struct Captured {
forwarded: Option<Forwarded>,
command: Option<HaProxyCommand>,
tlvs: Option<HaProxyTlvs>,
}
const SRC: SocketAddress = SocketAddress::local_ipv4(54321);
async fn run_e2e<F>(configure_client: F) -> Captured
where
F: FnOnce(
ClientLayer<client_protocol::Tcp, client_version::Two>,
) -> ClientLayer<client_protocol::Tcp, client_version::Two>,
{
let captured: Arc<Mutex<Captured>> = Arc::new(Mutex::new(Captured::default()));
let captured_clone = captured.clone();
let connector = MockConnectorService::new(move || {
let captured = captured_clone.clone();
ServerService::new(service_fn(move |stream| {
let captured = captured.clone();
async move {
let exts = <_ as ExtensionsRef>::extensions(&stream);
let mut guard = captured.lock();
guard.forwarded = exts.get_ref::<Forwarded>().cloned();
guard.command = exts.get_ref::<HaProxyCommand>().copied();
guard.tlvs = exts.get_ref::<HaProxyTlvs>().cloned();
Ok::<_, BoxError>(())
}
}))
});
let client_layer = configure_client(ClientLayer::tcp());
let client_svc = client_layer.layer(connector);
let input: ServiceInput<()> = ServiceInput::new(());
input.extensions().insert(SocketInfo::new(None, SRC));
let established = client_svc.serve(input).await.expect("client serve");
drop(established);
tokio::task::yield_now().await;
let guard = captured.lock();
Captured {
forwarded: guard.forwarded.clone(),
command: guard.command,
tlvs: guard.tlvs.clone(),
}
}
async fn run_e2e_expect_client_error<F>(configure_client: F) -> ConnectionError
where
F: FnOnce(
ClientLayer<client_protocol::Tcp, client_version::Two>,
) -> ClientLayer<client_protocol::Tcp, client_version::Two>,
{
let connector = MockConnectorService::new(|| {
ServerService::new(service_fn(|_stream| async move { Ok::<_, BoxError>(()) }))
});
let client_svc = configure_client(ClientLayer::tcp()).layer(connector);
let input: ServiceInput<()> = ServiceInput::new(());
input.extensions().insert(SocketInfo::new(None, SRC));
client_svc
.serve(input)
.await
.expect_err("expected client error")
}
#[tokio::test]
async fn roundtrip_minimal_v2() {
let out = run_e2e(|l| l).await;
let fwd = out.forwarded.expect("Forwarded must be present");
let socket = fwd.client_socket_addr().expect("client socket addr");
assert_eq!(socket.ip_addr, SRC.ip_addr);
assert_eq!(socket.port, SRC.port);
assert_eq!(out.command, Some(HaProxyCommand::Proxy));
assert!(
out.tlvs.is_none() || out.tlvs.as_ref().unwrap().entries().is_empty(),
"no TLVs configured: extension should be absent or empty",
);
}
#[tokio::test]
async fn roundtrip_with_authority_tlv() {
let out =
run_e2e(|l| l.with_tlv(v2::Type::Authority, Bytes::from_static(b"example.com"))).await;
let tlvs = out.tlvs.expect("TLV extension must be present");
let authority = tlvs.authority().expect("authority TLV parsed as Domain");
assert_eq!(authority, Domain::from_static("example.com"));
let raw = tlvs.get(v2::Type::Authority).expect("authority raw");
assert_eq!(raw.as_ref(), b"example.com");
}
#[tokio::test]
async fn roundtrip_with_unique_id_and_unknown_tlv() {
let unique = b"req-abc-1234567890";
let vendor_kind = v2::Type::Unknown(0xEA); let vendor_value = Bytes::from_static(b"\x01\x02\x03\x04");
let out = run_e2e(|l| {
l.with_tlv(v2::Type::UniqueId, Bytes::copy_from_slice(unique))
.with_tlv(vendor_kind, vendor_value.clone())
})
.await;
let tlvs = out.tlvs.expect("TLV extension must be present");
assert_eq!(
tlvs.unique_id().expect("unique id").as_ref(),
unique.as_slice(),
);
let vendor = tlvs.get(vendor_kind).expect("vendor TLV must round-trip");
assert_eq!(vendor.as_ref(), vendor_value.as_ref());
let kinds: Vec<_> = tlvs.entries().iter().map(|t| t.kind).collect();
assert_eq!(kinds, vec![v2::Type::UniqueId, vendor_kind]);
}
#[tokio::test]
async fn rejects_payload_combined_with_crc32c() {
let err = run_e2e_expect_client_error(|l| {
l.with_payload(Bytes::from_static(b"garbage"))
.with_crc32c(true)
})
.await;
let msg = err.to_string();
assert_eq!(err.domain(), ConnectionErrorDomain::Local);
assert_eq!(err.kind(), ConnectionErrorKind::InvalidInput);
assert!(
msg.contains("payload") && msg.contains("crc32c"),
"unexpected error: {msg}",
);
}
#[tokio::test]
async fn rejects_manual_crc32c_tlv() {
let err = run_e2e_expect_client_error(|l| {
l.with_tlv(v2::Type::CRC32C, Bytes::from_static(&[0u8; 4]))
})
.await;
let msg = err.to_string();
assert_eq!(err.domain(), ConnectionErrorDomain::Local);
assert_eq!(err.kind(), ConnectionErrorKind::InvalidInput);
assert!(
msg.contains("CRC32C") && msg.contains("with_crc32c"),
"unexpected error: {msg}",
);
}
#[tokio::test]
async fn roundtrip_with_crc32c_is_accepted() {
let out = run_e2e(|l| {
l.with_tlv(v2::Type::Authority, Bytes::from_static(b"svc.example"))
.with_crc32c(true)
})
.await;
let tlvs = out.tlvs.expect("TLV extension must be present");
assert_eq!(tlvs.authority(), Some(Domain::from_static("svc.example")));
assert!(
tlvs.get(v2::Type::CRC32C).is_some(),
"CRC32C TLV should be present after roundtrip",
);
}