use std::{collections::HashSet, sync::Arc};
use tokio::sync::RwLock;
use crate::{ComparableListener, UListener, UMessage, UStatus, UTransport, UUri};
#[derive(Eq, PartialEq, Hash)]
struct RegisteredListener {
source_filter: UUri,
sink_filter: Option<UUri>,
listener: ComparableListener,
}
impl RegisteredListener {
fn matches(&self, source: &UUri, sink: Option<&UUri>) -> bool {
if !self.source_filter.matches(source) {
return false;
}
if let Some(pattern) = &self.sink_filter {
sink.is_some_and(|candidate_sink| pattern.matches(candidate_sink))
} else {
sink.is_none()
}
}
fn matches_msg(&self, msg: &UMessage) -> bool {
if let Some(source) = msg
.attributes
.as_ref()
.and_then(|attribs| attribs.source.as_ref())
{
self.matches(
source,
msg.attributes
.as_ref()
.and_then(|attribs| attribs.sink.as_ref()),
)
} else {
false
}
}
async fn on_receive(&self, msg: UMessage) {
self.listener.on_receive(msg).await
}
}
#[derive(Default)]
pub struct LocalTransport {
listeners: RwLock<HashSet<RegisteredListener>>,
}
impl LocalTransport {
async fn dispatch(&self, message: UMessage) {
let listeners = self.listeners.read().await;
for listener in listeners.iter() {
if listener.matches_msg(&message) {
listener.on_receive(message.clone()).await;
}
}
}
}
#[async_trait::async_trait]
impl UTransport for LocalTransport {
async fn send(&self, message: UMessage) -> Result<(), UStatus> {
self.dispatch(message).await;
Ok(())
}
async fn register_listener(
&self,
source_filter: &UUri,
sink_filter: Option<&UUri>,
listener: Arc<dyn UListener>,
) -> Result<(), UStatus> {
let registered_listener = RegisteredListener {
source_filter: source_filter.to_owned(),
sink_filter: sink_filter.map(|u| u.to_owned()),
listener: ComparableListener::new(listener),
};
let mut listeners = self.listeners.write().await;
if listeners.contains(®istered_listener) {
Err(UStatus::fail_with_code(
crate::UCode::ALREADY_EXISTS,
"listener already registered for filters",
))
} else {
listeners.insert(registered_listener);
Ok(())
}
}
async fn unregister_listener(
&self,
source_filter: &UUri,
sink_filter: Option<&UUri>,
listener: Arc<dyn UListener>,
) -> Result<(), UStatus> {
let registered_listener = RegisteredListener {
source_filter: source_filter.to_owned(),
sink_filter: sink_filter.map(|u| u.to_owned()),
listener: ComparableListener::new(listener),
};
let mut listeners = self.listeners.write().await;
if listeners.remove(®istered_listener) {
Ok(())
} else {
Err(UStatus::fail_with_code(
crate::UCode::NOT_FOUND,
"no such listener registered for filters",
))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{utransport::MockUListener, LocalUriProvider, StaticUriProvider, UMessageBuilder};
#[tokio::test]
async fn test_send_dispatches_to_matching_listener() {
const RESOURCE_ID: u16 = 0xa1b3;
let mut listener = MockUListener::new();
listener.expect_on_receive().once().return_const(());
let listener_ref = Arc::new(listener);
let uri_provider = StaticUriProvider::new("my-vehicle", 0x100d, 0x02);
let transport = LocalTransport::default();
transport
.register_listener(
&uri_provider.get_resource_uri(RESOURCE_ID),
None,
listener_ref.clone(),
)
.await
.unwrap();
let _ = transport
.send(
UMessageBuilder::publish(uri_provider.get_resource_uri(RESOURCE_ID))
.build()
.unwrap(),
)
.await;
transport
.unregister_listener(
&uri_provider.get_resource_uri(RESOURCE_ID),
None,
listener_ref,
)
.await
.unwrap();
let _ = transport
.send(
UMessageBuilder::publish(uri_provider.get_resource_uri(RESOURCE_ID))
.build()
.unwrap(),
)
.await;
}
#[tokio::test]
async fn test_send_does_not_dispatch_to_non_matching_listener() {
const RESOURCE_ID: u16 = 0xa1b3;
let mut listener = MockUListener::new();
listener.expect_on_receive().never().return_const(());
let listener_ref = Arc::new(listener);
let uri_provider = StaticUriProvider::new("my-vehicle", 0x100d, 0x02);
let transport = LocalTransport::default();
transport
.register_listener(
&uri_provider.get_resource_uri(RESOURCE_ID + 10),
None,
listener_ref.clone(),
)
.await
.unwrap();
let _ = transport
.send(
UMessageBuilder::publish(uri_provider.get_resource_uri(RESOURCE_ID))
.build()
.unwrap(),
)
.await;
transport
.unregister_listener(
&uri_provider.get_resource_uri(RESOURCE_ID + 10),
None,
listener_ref,
)
.await
.unwrap();
let _ = transport
.send(
UMessageBuilder::publish(uri_provider.get_resource_uri(RESOURCE_ID))
.build()
.unwrap(),
)
.await;
}
}