Files
umn/Sources/UltraMeshCore/Routing.swift
T
2026-08-31 15:55:09 -05:00

79 lines
3.2 KiB
Swift

import Foundation
public struct Route: Equatable, Sendable {
public let destination: MeshAddress
public let nextHop: MeshAddress
public let hopCount: Int
}
public final class LinkStateRouter: @unchecked Sendable {
private struct Entry { let state: LinkState; var receivedAt: Date }
private var database: [MeshAddress: Entry] = [:]
private let local: MeshAddress
private let lock = NSLock()
public init(local: MeshAddress) { self.local = local }
@discardableResult
public func ingest(_ state: LinkState, now: Date = Date()) -> Bool {
guard state.validate(), state.neighbors.count <= 32 else { return false }
lock.lock(); defer { lock.unlock() }
if let existing = database[state.origin.address], existing.state.sequence >= state.sequence { return false }
if database[state.origin.address] == nil, database.count >= 32 { return false }
database[state.origin.address] = Entry(state: state, receivedAt: now)
return true
}
public func expire(olderThan seconds: TimeInterval = 30, now: Date = Date()) {
lock.lock(); defer { lock.unlock() }
database = database.filter { now.timeIntervalSince($0.value.receivedAt) <= seconds || $0.key == local }
}
public func record(for address: MeshAddress) -> NodeRecord? {
lock.lock(); defer { lock.unlock() }
return database[address]?.state.origin
}
public func allRecords() -> [NodeRecord] {
lock.lock(); defer { lock.unlock() }
return database.values.map(\.state.origin)
}
public func allStates() -> [LinkState] {
lock.lock(); defer { lock.unlock() }
return database.values.map(\.state)
}
public func routes() -> [MeshAddress: Route] {
lock.lock(); let snapshot = database; lock.unlock()
var graph: [MeshAddress: Set<MeshAddress>] = [:]
for entry in snapshot.values {
let origin = entry.state.origin.address
for neighbor in entry.state.neighbors {
graph[origin, default: []].insert(neighbor)
graph[neighbor, default: []].insert(origin)
}
}
var distance: [MeshAddress: Int] = [local: 0]
var firstHop: [MeshAddress: MeshAddress] = [:]
var queue = [local]
while !queue.isEmpty {
let current = queue.removeFirst()
guard let base = distance[current], base < 16 else { continue }
for neighbor in (graph[current] ?? []).sorted() {
let candidateFirst = current == local ? neighbor : firstHop[current]!
let candidateDistance = base + 1
if distance[neighbor] == nil || candidateDistance < distance[neighbor]! ||
(candidateDistance == distance[neighbor]! && candidateFirst < firstHop[neighbor]!) {
distance[neighbor] = candidateDistance; firstHop[neighbor] = candidateFirst
queue.append(neighbor)
}
}
}
return Dictionary(uniqueKeysWithValues: distance.compactMap { address, hops in
guard address != local, let hop = firstHop[address] else { return nil }
return (address, Route(destination: address, nextHop: hop, hopCount: hops))
})
}
}