79 lines
3.2 KiB
Swift
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))
|
|
})
|
|
}
|
|
}
|