use crate::error;
use crate::gai;
use crate::ingress::SyncProtocolHandler;
use crate::ingress::buffer::{QwpBuffer, QwpSendScratch};
use crate::ingress::conf::QwpUdpConfig;
use std::net::{Ipv4Addr, SocketAddrV4, UdpSocket};
pub(crate) struct SyncQwpUdpHandlerState {
pub(crate) socket: UdpSocket,
pub(crate) target_addr: SocketAddrV4,
pub(crate) max_datagram_size: usize,
pub(crate) scratch: QwpSendScratch,
}
fn resolve_udp_target(host: &str, port: &str) -> crate::Result<SocketAddrV4> {
let sock_addr = gai::resolve_host_port_udp(host, port)?;
let addr = sock_addr.as_socket_ipv4().ok_or_else(|| {
error::fmt!(
CouldNotResolveAddr,
"Could not resolve {:?}:{:?}: no IPv4 address found",
host,
port
)
})?;
Ok(addr)
}
fn resolve_bind_addr(net_interface: &str) -> crate::Result<SocketAddrV4> {
let sock_addr = gai::resolve_host_udp(net_interface)?;
let addr = sock_addr.as_socket_ipv4().ok_or_else(|| {
error::fmt!(
CouldNotResolveAddr,
"Could not resolve interface address {:?}: no IPv4 address found",
net_interface
)
})?;
Ok(SocketAddrV4::new(*addr.ip(), 0))
}
pub(crate) fn connect_qwp_udp(
host: &str,
port: &str,
net_interface: Option<&str>,
qwp_udp: &QwpUdpConfig,
) -> crate::Result<SyncProtocolHandler> {
let target_addr = resolve_udp_target(host, port)?;
let bind_addr = match net_interface {
Some(net_interface) => resolve_bind_addr(net_interface)?,
None => SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0),
};
let socket = UdpSocket::bind(bind_addr).map_err(|io_err| {
error::fmt!(
SocketError,
"Could not open UDP socket bound to {:?}: {}",
bind_addr,
io_err
)
})?;
socket.connect(target_addr).map_err(|io_err| {
error::fmt!(
SocketError,
"Could not connect UDP socket to {:?}: {}",
target_addr,
io_err
)
})?;
socket
.set_multicast_ttl_v4(*qwp_udp.multicast_ttl)
.map_err(|io_err| {
error::fmt!(
SocketError,
"Could not set UDP multicast TTL to {}: {}",
*qwp_udp.multicast_ttl,
io_err
)
})?;
let max_datagram_size = *qwp_udp.max_datagram_size;
Ok(SyncProtocolHandler::SyncQwpUdp(SyncQwpUdpHandlerState {
socket,
target_addr,
max_datagram_size,
scratch: QwpSendScratch::new(max_datagram_size),
}))
}
pub(crate) fn flush_qwp_udp(
state: &mut SyncQwpUdpHandlerState,
buffer: &QwpBuffer,
) -> crate::Result<()> {
let target_addr = state.target_addr;
let max_datagram_size = state.max_datagram_size;
buffer.flush_to_socket(
&mut state.scratch,
max_datagram_size,
&mut |datagram: &[u8]| {
state.socket.send(datagram).map_err(|io_err| {
error::fmt!(
SocketError,
"Could not send UDP datagram to {:?}: {}",
target_addr,
io_err
)
})?;
Ok(())
},
)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ErrorCode;
#[test]
fn qwp_udp_rejects_ipv6_only_targets() {
let err = match connect_qwp_udp("::1", "9007", None, &QwpUdpConfig::default()) {
Ok(_) => panic!("expected IPv6-only target resolution to fail"),
Err(err) => err,
};
assert_eq!(err.code(), ErrorCode::CouldNotResolveAddr);
assert!(err.msg().contains("Could not resolve"));
assert!(err.msg().contains("::1"));
}
#[test]
fn qwp_udp_rejects_ipv6_only_bind_interface() {
let err = match connect_qwp_udp("127.0.0.1", "9007", Some("::1"), &QwpUdpConfig::default())
{
Ok(_) => panic!("expected IPv6-only bind interface resolution to fail"),
Err(err) => err,
};
assert_eq!(err.code(), ErrorCode::CouldNotResolveAddr);
assert!(err.msg().contains("Could not resolve"));
assert!(err.msg().contains("::1"));
}
#[test]
fn qwp_udp_binds_requested_interface() {
let handler = connect_qwp_udp(
"127.0.0.1",
"9007",
Some("127.0.0.1"),
&QwpUdpConfig::default(),
)
.unwrap();
#[allow(irrefutable_let_patterns)]
let SyncProtocolHandler::SyncQwpUdp(ref state) = handler else {
panic!("Expected SyncQwpUdp handler");
};
assert_eq!(state.socket.local_addr().unwrap().ip(), Ipv4Addr::LOCALHOST);
}
#[test]
fn qwp_udp_sets_default_multicast_ttl_on_socket() {
let handler = connect_qwp_udp("127.0.0.1", "9007", None, &QwpUdpConfig::default()).unwrap();
#[allow(irrefutable_let_patterns)]
let SyncProtocolHandler::SyncQwpUdp(ref state) = handler else {
panic!("Expected SyncQwpUdp handler");
};
assert_eq!(state.socket.multicast_ttl_v4().unwrap(), 1);
}
#[test]
fn qwp_udp_sets_multicast_ttl_on_socket() {
let mut qwp_udp = QwpUdpConfig::default();
qwp_udp
.multicast_ttl
.set_specified("multicast_ttl", 7)
.unwrap();
let handler = connect_qwp_udp("127.0.0.1", "9007", None, &qwp_udp).unwrap();
#[allow(irrefutable_let_patterns)]
let SyncProtocolHandler::SyncQwpUdp(ref state) = handler else {
panic!("Expected SyncQwpUdp handler");
};
assert_eq!(state.socket.multicast_ttl_v4().unwrap(), 7);
}
#[test]
fn qwp_udp_flush_surfaces_socket_send_failure() {
let qwp_udp = QwpUdpConfig::default();
let mut handler = connect_qwp_udp("127.0.0.1", "9007", None, &qwp_udp).unwrap();
#[allow(irrefutable_let_patterns)]
let SyncProtocolHandler::SyncQwpUdp(state) = &mut handler else {
panic!("Expected SyncQwpUdp handler");
};
state.socket = UdpSocket::bind(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0)).unwrap();
let mut buffer = QwpBuffer::new(127);
buffer
.table("trades")
.unwrap()
.symbol("sym", "ETH-USD")
.unwrap()
.column_i64("qty", 1)
.unwrap()
.at_now()
.unwrap();
let err = flush_qwp_udp(state, &buffer).unwrap_err();
assert_eq!(err.code(), ErrorCode::SocketError);
assert!(err.msg().contains("Could not send UDP datagram to"));
}
}