use std::collections::HashSet;
use std::sync::{Arc, RwLock};
use crate::error::{Result, TransportError};
use crate::log_transport;
use crate::logging::LogLevel;
use crate::protocol::address::IndividualAddress;
#[derive(Debug, Clone, Default)]
pub struct AddressRegistry {
claimed: Arc<RwLock<HashSet<IndividualAddress>>>,
known_occupied: Arc<RwLock<HashSet<IndividualAddress>>>,
}
impl AddressRegistry {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn claim(&self, addr: IndividualAddress) -> Result<()> {
let mut claimed = self
.claimed
.write()
.expect("address registry claimed lock poisoned");
if claimed.contains(&addr) {
log_transport!(
LogLevel::Warn,
"Address {} already claimed, collision prevented",
addr
);
return Err(TransportError::InvalidConfiguration {
details: format!("Address {addr} already claimed"),
}
.into());
}
claimed.insert(addr);
log_transport!(
LogLevel::Info,
"Address {} claimed for bus connection",
addr
);
Ok(())
}
pub fn release(&self, addr: IndividualAddress) {
let mut claimed = self
.claimed
.write()
.expect("address registry claimed lock poisoned");
if claimed.remove(&addr) {
log_transport!(LogLevel::Info, "Address {} released", addr);
}
}
#[must_use]
pub fn is_claimed(&self, addr: IndividualAddress) -> bool {
self.claimed
.read()
.expect("address registry claimed lock poisoned")
.contains(&addr)
}
pub fn add_known_occupied(&self, addr: IndividualAddress) {
self.known_occupied
.write()
.expect("address registry known_occupied lock poisoned")
.insert(addr);
}
#[must_use]
pub fn is_available(&self, addr: IndividualAddress) -> bool {
let claimed = self
.claimed
.read()
.expect("address registry claimed lock poisoned");
if claimed.contains(&addr) {
return false;
}
let known_occupied = self
.known_occupied
.read()
.expect("address registry known_occupied lock poisoned");
!known_occupied.contains(&addr)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn addr(area: u8, line: u8, device: u8) -> IndividualAddress {
IndividualAddress::new(area, line, device)
}
#[test]
fn address_registry_claim_succeeds_then_collision_fails() {
let registry = AddressRegistry::new();
let a = addr(1, 1, 10);
assert!(registry.claim(a).is_ok());
assert!(registry.claim(a).is_err());
}
#[test]
fn address_registry_release_allows_reclaim() {
let registry = AddressRegistry::new();
let a = addr(1, 1, 11);
assert!(registry.claim(a).is_ok());
assert!(registry.claim(a).is_err());
registry.release(a);
assert!(registry.claim(a).is_ok());
}
#[test]
fn address_registry_is_claimed_reflects_state() {
let registry = AddressRegistry::new();
let a = addr(1, 1, 12);
assert!(!registry.is_claimed(a));
registry.claim(a).unwrap();
assert!(registry.is_claimed(a));
registry.release(a);
assert!(!registry.is_claimed(a));
}
#[test]
fn address_registry_known_occupied_makes_unavailable() {
let registry = AddressRegistry::new();
let a = addr(1, 1, 13);
assert!(registry.is_available(a));
registry.add_known_occupied(a);
assert!(!registry.is_available(a));
assert!(!registry.is_claimed(a));
}
#[test]
fn address_registry_is_available_only_when_free() {
let registry = AddressRegistry::new();
let free = addr(1, 1, 14);
let claimed = addr(1, 1, 15);
let occupied = addr(1, 1, 16);
registry.claim(claimed).unwrap();
registry.add_known_occupied(occupied);
assert!(registry.is_available(free));
assert!(!registry.is_available(claimed));
assert!(!registry.is_available(occupied));
}
#[test]
fn address_registry_clone_shares_state() {
let registry = AddressRegistry::new();
let clone = registry.clone();
let a = addr(1, 1, 17);
registry.claim(a).unwrap();
assert!(clone.is_claimed(a));
assert!(clone.claim(a).is_err());
}
}