use std::collections::HashMap;
use std::sync::Mutex;
use tokio::sync::oneshot;
use crate::protocol::knxip::{KnxIpFrame, ServiceType};
#[derive(Default)]
pub struct FrameRouter {
waiters: Mutex<HashMap<u16, Vec<oneshot::Sender<KnxIpFrame>>>>,
}
impl FrameRouter {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn register(&self, service_type: ServiceType) -> oneshot::Receiver<KnxIpFrame> {
let (tx, rx) = oneshot::channel();
self.waiters
.lock()
.unwrap()
.entry(service_type as u16)
.or_default()
.push(tx);
rx
}
pub fn dispatch(&self, mut frame: KnxIpFrame) -> Option<KnxIpFrame> {
let key = frame.header.service_type as u16;
let mut waiters = self.waiters.lock().unwrap();
if let Some(queue) = waiters.get_mut(&key) {
while !queue.is_empty() {
let tx = queue.remove(0);
match tx.send(frame) {
Ok(()) => {
if queue.is_empty() {
waiters.remove(&key);
}
return None;
}
Err(returned) => {
frame = returned;
}
}
}
waiters.remove(&key);
}
Some(frame)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::protocol::knxip::{KnxIpFrame, ServiceType};
fn frame(service_type: ServiceType) -> KnxIpFrame {
KnxIpFrame::new(service_type, vec![0x00, 0x00])
}
#[tokio::test]
async fn dispatch_delivers_to_registered_waiter() {
let router = FrameRouter::new();
let rx = router.register(ServiceType::ConnectionstateResponse);
let consumed = router.dispatch(frame(ServiceType::ConnectionstateResponse));
assert!(consumed.is_none(), "frame should be consumed by the waiter");
let received = rx.await.expect("waiter should receive the frame");
assert_eq!(
received.header.service_type,
ServiceType::ConnectionstateResponse
);
}
#[test]
fn dispatch_returns_frame_when_no_waiter() {
let router = FrameRouter::new();
let out = router.dispatch(frame(ServiceType::TunnellingRequest));
assert!(
out.is_some(),
"unmatched frame should be returned to caller"
);
}
#[test]
fn dispatch_skips_dropped_waiter() {
let router = FrameRouter::new();
let rx = router.register(ServiceType::ConnectionstateResponse);
drop(rx);
let out = router.dispatch(frame(ServiceType::ConnectionstateResponse));
assert!(
out.is_some(),
"frame should be returned when the only waiter was dropped"
);
}
}