use std::sync::{Arc, LazyLock, Mutex, Weak};
use std::collections::HashMap;
use std::net::IpAddr;
use crate::name::{ArcName, Name};
use crate::backend::BackendDev;
static REGISTRY: LazyLock<Mutex<Registry>> = LazyLock::new(Default::default);
#[derive(Debug, Default)]
struct Registry {
itr_mapping: HashMap<Arc<str>, Entry>,
port_mapping: HashMap<u16, HashMap<ArcName, Entry>>,
}
#[derive(Debug)]
struct Entry {
dev: Weak<dyn BackendDev>,
host: Arc<str>,
}
impl Entry {
fn is(&self, dev: &Weak<dyn BackendDev>) -> bool {
Weak::ptr_eq(&self.dev, dev)
}
}
pub fn register(dev: &Arc<dyn BackendDev>, itr: Arc<str>, host: Arc<str>, port: u16, name: Arc<str>) -> LocalDevHandle {
let dev = Arc::downgrade(dev);
let mut registry = REGISTRY.lock().unwrap();
registry.itr_mapping.insert(Arc::clone(&itr), Entry {
dev: Weak::clone(&dev),
host: Arc::clone(&host),
});
let port_name = (port != 0).then(|| {
registry.port_mapping.entry(port).or_default().insert(ArcName::from(Arc::clone(&name)), Entry {
dev: Weak::clone(&dev),
host,
});
(port, name)
});
LocalDevHandle { dev, itr, port_name }
}
pub fn find(itr: &str) -> Option<Arc<dyn BackendDev>> {
REGISTRY.lock().unwrap().itr_mapping.get(itr)?.dev.upgrade()
}
pub fn find_at(host: &str, port: u16, name: &str) -> Option<Arc<dyn BackendDev>> {
let registry = REGISTRY.lock().unwrap();
let entry = registry.port_mapping.get(&port)?.get(Name::new(name))?;
if !host.eq_ignore_ascii_case(&entry.host) && !is_loopback(host) {
return None;
}
entry.dev.upgrade()
}
fn is_loopback(host: &str) -> bool {
host.eq_ignore_ascii_case("localhost") || host.parse::<IpAddr>().is_ok_and(|addr| addr.is_loopback())
}
#[derive(Debug)]
pub struct LocalDevHandle {
dev: Weak<dyn BackendDev>,
itr: Arc<str>,
port_name: Option<(u16, Arc<str>)>,
}
impl Drop for LocalDevHandle {
fn drop(&mut self) {
let mut registry = REGISTRY.lock().unwrap();
if registry.itr_mapping.get(&self.itr).is_some_and(|entry| entry.is(&self.dev)) {
registry.itr_mapping.remove(&self.itr);
}
if let Some((port, name)) = &self.port_name
&& let Some(names) = registry.port_mapping.get_mut(port) {
if names.get(Name::new(name)).is_some_and(|entry| entry.is(&self.dev)) {
names.remove(Name::new(name));
}
if names.is_empty() {
registry.port_mapping.remove(port);
}
}
}
}
#[cfg(test)]
mod tests {
use futures::future::{ready, BoxFuture};
use futures::executor::block_on;
use futures::stream::BoxStream;
use crate::dev::{AttrConfig, AttrInfo, AttrRead, AttrReady, AttrValueEvent, CommandInfo, DevInfo, DevSource, DevState, Named, NamedResult, RecordedResult};
use crate::value::{Scalar, Spectrum, Value};
use crate::error::{Error, Result};
use super::*;
#[derive(Debug)]
struct TestDev {
name: &'static str,
}
fn dev(name: &'static str) -> Arc<dyn BackendDev> {
Arc::new(TestDev { name })
}
fn not_implemented<'a, T: Send + 'a>() -> BoxFuture<'a, Result<T>> {
Box::pin(ready(Err(Error::not_implemented())))
}
impl BackendDev for TestDev {
fn name(&self) -> BoxFuture<'_, Result<Arc<str>>> {
Box::pin(ready(Ok(Arc::from(self.name))))
}
fn admin_dev_name(&self) -> BoxFuture<'_, Result<Arc<str>>> { not_implemented() }
fn ping(&self) -> BoxFuture<'_, Result<()>> { not_implemented() }
fn description(&self) -> BoxFuture<'_, Result<Arc<str>>> { not_implemented() }
fn info(&self) -> BoxFuture<'_, Result<DevInfo>> { not_implemented() }
fn state(&self) -> BoxFuture<'_, Result<DevState>> { not_implemented() }
fn status(&self) -> BoxFuture<'_, Result<Scalar<str>>> { not_implemented() }
fn black_box(&self, _max: Option<usize>) -> BoxFuture<'_, Result<Spectrum<str>>> { not_implemented() }
fn command(&self, _name: Scalar<str>, _input: Value, _source: DevSource) -> BoxFuture<'_, Result<Value>> { not_implemented() }
fn read_attr(&self, _names: Option<Spectrum<str>>, _source: DevSource) -> BoxFuture<'_, Result<Vec<NamedResult<AttrRead<Value>>>>> { not_implemented() }
fn write_attr(&self, _values: Vec<Named<Value>>) -> BoxFuture<'_, Result<Vec<Result<()>>>> { not_implemented() }
fn write_read_attr(&self, _values: Vec<Named<Value>>) -> BoxFuture<'_, Result<Vec<NamedResult<AttrRead<Value>>>>> { not_implemented() }
fn command_info(&self, _names: Option<Spectrum<str>>) -> BoxFuture<'_, Result<Vec<NamedResult<CommandInfo>>>> { not_implemented() }
fn attr_info(&self, _names: Option<Spectrum<str>>) -> BoxFuture<'_, Result<Vec<NamedResult<AttrInfo>>>> { not_implemented() }
fn set_attr_config(&self, _configs: Vec<Named<AttrConfig>>) -> BoxFuture<'_, Result<Vec<Result<()>>>> { not_implemented() }
fn command_history(&self, _name: Scalar<str>, _max: usize) -> BoxFuture<'_, Result<Vec<RecordedResult<Value>>>> { not_implemented() }
fn attr_history(&self, _name: Scalar<str>, _max: usize) -> BoxFuture<'_, Result<Vec<RecordedResult<AttrRead<Value>>>>> { not_implemented() }
fn subscribe_attr_value(&self, _admin: Arc<dyn BackendDev>, _name: Scalar<str>, _event: AttrValueEvent) -> BoxFuture<'_, Result<BoxStream<'static, Result<AttrRead<Value>>>>> { not_implemented() }
fn subscribe_attr_ready(&self, _admin: Arc<dyn BackendDev>, _name: Scalar<str>) -> BoxFuture<'_, Result<BoxStream<'static, Result<AttrReady>>>> { not_implemented() }
fn subscribe_attr_config(&self, _admin: Arc<dyn BackendDev>, _name: Scalar<str>) -> BoxFuture<'_, Result<BoxStream<'static, Result<AttrInfo>>>> { not_implemented() }
}
fn name(dev: &Arc<dyn BackendDev>) -> Arc<str> {
block_on(dev.name()).unwrap()
}
#[test]
fn by_itr() {
let served = dev("test/local/1");
let _registration = register(&served, Arc::from("test:by_itr"), Arc::from("myhost"), 0, Arc::from("test/local/1"));
let found = find("test:by_itr").unwrap();
assert_eq!(&*name(&found), "test/local/1");
assert!(find("test:by_itr/other").is_none());
assert!(find_at("localhost", 0, "test/local/1").is_none());
}
#[test]
fn same_name() {
let first = dev("first");
let second = dev("second");
let _first = register(&first, Arc::from("test:same_name/1"), Arc::from("myhost"), 61001, Arc::from("test/same/1"));
let _second = register(&second, Arc::from("test:same_name/2"), Arc::from("myhost"), 61002, Arc::from("TEST/same/1"));
assert_eq!(&*name(&find("test:same_name/1").unwrap()), "first");
assert_eq!(&*name(&find("test:same_name/2").unwrap()), "second");
assert_eq!(&*name(&find_at("myhost", 61001, "test/same/1").unwrap()), "first");
assert_eq!(&*name(&find_at("localhost", 61002, "test/SAME/1").unwrap()), "second");
}
#[test]
fn at_host() {
let served = dev("test/at/1");
let _registration = register(&served, Arc::from("test:at_host"), Arc::from("MyHost"), 61003, Arc::from("test/at/1"));
for host in ["myhost", "localhost", "127.0.0.1", "::1"] {
assert!(find_at(host, 61003, "test/at/1").is_some(), "{host}");
}
assert!(find_at("otherhost", 61003, "test/at/1").is_none());
assert!(find_at("myhost", 61004, "test/at/1").is_none());
}
#[test]
fn lifetime() {
let served = dev("old");
let old = register(&served, Arc::from("test:lifetime"), Arc::from("myhost"), 61005, Arc::from("test/life/1"));
let client = find("test:lifetime").unwrap();
let replacing = dev("new");
let new = register(&replacing, Arc::from("test:lifetime"), Arc::from("myhost"), 61005, Arc::from("test/life/1"));
drop(old);
assert_eq!(&*name(&find("test:lifetime").unwrap()), "new");
assert_eq!(&*name(&find_at("localhost", 61005, "test/life/1").unwrap()), "new");
drop(new);
assert!(find("test:lifetime").is_none());
assert!(find_at("localhost", 61005, "test/life/1").is_none());
let weak = Arc::downgrade(&served);
drop(served);
assert_eq!(&*name(&client), "old");
drop(client);
assert!(weak.upgrade().is_none());
}
}