///|
pub(all) struct DiscoveredDevice {
peer : String
announcement : IAm
first_seen : UInt64
last_seen : UInt64
}
///|
pub struct DiscoveryDirectory {
priv entries : Array[DiscoveredDevice]
priv maximum : Int32
priv mut clock : UInt64
}
///|
pub fn DiscoveryDirectory::new(
maximum~ : Int32 = 256,
) -> DiscoveryDirectory raise BacnetError {
if maximum <= 0 || maximum > 65536 {
raise Limit("discovery capacity")
}
{ entries: [], maximum, clock: 0UL, }
}
///|
/// Observe an already-decoded I-Am. Duplicate device IDs at different peers are
/// conflicts, never silently reassigned. Caller chooses peer/address identity.
pub fn DiscoveryDirectory::observe(
self : DiscoveryDirectory,
peer : String,
announcement : IAm,
now : UInt64,
) -> Bool raise BacnetError {
if now < self.clock {
raise State("discovery clock reversed")
}
if peer.is_empty() || @utf8.encode(peer).length() > 256 {
raise Limit("discovery peer length")
}
if announcement.device.kind != 8 ||
announcement.device.instance == 4194303 ||
announcement.vendor > 65535U ||
announcement.max_apdu < 50U ||
announcement.max_apdu > 1476U ||
announcement.segmentation > 3U {
raise Invalid("I-Am capability fields", 0)
}
for i, entry in self.entries {
if entry.announcement.device == announcement.device {
if entry.peer != peer ||
entry.announcement.vendor != announcement.vendor ||
entry.announcement.max_apdu != announcement.max_apdu ||
entry.announcement.segmentation != announcement.segmentation {
raise State(
"conflicting device identity or capabilities; expire or forget explicitly",
)
}
self.entries[i] = { ..entry, last_seen: now, }
self.clock = now
return false
}
}
if self.entries.length() >= self.maximum {
raise Limit("discovery capacity")
}
self.entries.push({ peer, announcement, first_seen: now, last_seen: now, })
self.clock = now
true
}
///|
pub fn DiscoveryDirectory::devices(
self : DiscoveryDirectory,
) -> Array[DiscoveredDevice] {
self.entries.copy()
}
///|
pub fn DiscoveryDirectory::expire(
self : DiscoveryDirectory,
now : UInt64,
max_age : UInt64,
) -> Array[DiscoveredDevice] raise BacnetError {
if now < self.clock {
raise State("discovery clock reversed")
}
let expired = self.entries.filter(e => now - e.last_seen >= max_age)
let retained = self.entries.filter(e => now - e.last_seen < max_age)
self.entries.clear()
for entry in retained {
self.entries.push(entry)
}
self.clock = now
expired
}
///|
pub fn DiscoveryDirectory::forget(
self : DiscoveryDirectory,
device : ObjectId,
) -> Bool {
for i, entry in self.entries {
if entry.announcement.device == device {
self.entries.remove(i) |> ignore
return true
}
}
false
}