punchnet-macos/Tun/Session/SessionTable.swift
2026-06-21 22:38:49 +08:00

113 lines
3.3 KiB
Swift

//
// SessionTable.swift
// Tun
//
// Created by Codex on 2026/5/21.
//
import Foundation
actor SessionManager {
private var sessions: [Data: [Session.AddressType: Session]] = [:]
// session
private let ttl: Int32
nonisolated private let snapshotPublisher: SnapshotPublisher<SessionSnapshot>
init() {
let ttl: Int32 = 10
self.ttl = ttl
self.snapshotPublisher = SnapshotPublisher(initial: SessionSnapshot.empty())
}
func getSession(toAddress: Data) -> Session? {
let timestamp = Int32(Date().timeIntervalSince1970)
guard var peerSessions = self.sessions[toAddress] else {
return nil
}
peerSessions = peerSessions.filter { $0.value.lastTimestamp + ttl >= timestamp }
guard !peerSessions.isEmpty else {
self.sessions.removeValue(forKey: toAddress)
self.publishSnapshot()
return nil
}
guard var session = self.selectSession(in: peerSessions) else {
self.sessions[toAddress] = peerSessions
self.publishSnapshot()
return nil
}
session.updateLastTimestamp(timestamp)
peerSessions[session.addressType] = session
self.sessions[toAddress] = peerSessions
self.publishSnapshot()
return session
}
func addSession(session: Session) {
let timestamp = Int32(Date().timeIntervalSince1970)
var sessions = self.sessions[session.dstMac, default: [:]]
sessions = sessions.filter {
$0.value.lastTimestamp + ttl >= timestamp && $0.key != session.addressType
}
sessions[session.addressType] = session
self.sessions[session.dstMac] = sessions
self.publishSnapshot()
}
func removeSession(dstMac: Data) {
self.sessions.removeValue(forKey: dstMac)
self.publishSnapshot()
}
func clear() {
self.sessions.removeAll()
self.publishSnapshot()
}
@discardableResult
func pruneExpiredSessions() -> Int {
let oldCount = self.sessionCount()
self.sessions = self.validSessions()
let newCount = self.sessionCount()
self.publishSnapshot()
return oldCount - newCount
}
nonisolated func snapshot() -> SessionSnapshot {
return self.snapshotPublisher.current()
}
private func selectSession(in sessions: [Session.AddressType: Session]) -> Session? {
return sessions.values.max(by: { $0.lastTimestamp < $1.lastTimestamp })
}
private func publishSnapshot() {
self.snapshotPublisher.publish(self.compileSnapshot())
}
private func validSessions() -> [Data: [Session.AddressType: Session]] {
let timestamp = Int32(Date().timeIntervalSince1970)
return self.sessions.compactMapValues { peerSessions in
let validSessions = peerSessions.filter { $0.value.lastTimestamp + self.ttl >= timestamp }
return validSessions.isEmpty ? nil : validSessions
}
}
private func sessionCount() -> Int {
return self.sessions.values.reduce(0) { count, peerSessions in
count + peerSessions.count
}
}
private func compileSnapshot() -> SessionSnapshot {
return SessionSnapshot(sessions: self.validSessions())
}
}