You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
86 lines
2.9 KiB
86 lines
2.9 KiB
import Foundation
|
|
import Network
|
|
import OSLog
|
|
|
|
/// One framed message connection (either side). Callbacks arrive on the main actor.
|
|
final class Link: @unchecked Sendable {
|
|
let connection: NWConnection
|
|
private let queue: DispatchQueue
|
|
private var framer = MessageFramer() // queue only
|
|
private let log = Logger(subsystem: "studio.zielke.musubi", category: "remote")
|
|
|
|
var onMessage: (@MainActor (RemoteMessage) -> Void)?
|
|
var onState: (@MainActor (NWConnection.State) -> Void)?
|
|
|
|
init(connection: NWConnection, queue: DispatchQueue) {
|
|
self.connection = connection
|
|
self.queue = queue
|
|
}
|
|
|
|
func start() {
|
|
connection.stateUpdateHandler = { [weak self] state in
|
|
guard let self else { return }
|
|
let handler = onState
|
|
Task { @MainActor in handler?(state) }
|
|
}
|
|
connection.start(queue: queue)
|
|
receive()
|
|
}
|
|
|
|
func cancel() {
|
|
connection.cancel()
|
|
}
|
|
|
|
func send(_ message: RemoteMessage) {
|
|
do {
|
|
let data = try MessageFramer.encode(message)
|
|
connection.send(content: data, completion: .contentProcessed { [log] error in
|
|
if let error { log.error("Send failed: \(error.localizedDescription, privacy: .public)") }
|
|
})
|
|
} catch {
|
|
log.error("Encode failed: \(error.localizedDescription, privacy: .public)")
|
|
}
|
|
}
|
|
|
|
private func receive() {
|
|
connection.receive(minimumIncompleteLength: 1, maximumLength: 65_536) { [weak self] data, _, isComplete, error in
|
|
guard let self else { return }
|
|
if let data, !data.isEmpty {
|
|
do {
|
|
let messages = try framer.append(data)
|
|
let handler = onMessage
|
|
if !messages.isEmpty {
|
|
Task { @MainActor in messages.forEach { handler?($0) } }
|
|
}
|
|
} catch {
|
|
log.error("Framing error, closing: \(String(describing: error), privacy: .public)")
|
|
connection.cancel()
|
|
return
|
|
}
|
|
}
|
|
if isComplete || error != nil {
|
|
connection.cancel()
|
|
return
|
|
}
|
|
receive()
|
|
}
|
|
}
|
|
}
|
|
|
|
extension NWParameters {
|
|
/// TCP bound to one interface, with Bonjour peer-to-peer off (technical network only).
|
|
static func musubi(interface: NWInterface?) -> NWParameters {
|
|
let parameters = NWParameters.tcp
|
|
parameters.includePeerToPeer = false
|
|
if let interface { parameters.requiredInterface = interface }
|
|
if let tcp = parameters.defaultProtocolStack.transportProtocol as? NWProtocolTCP.Options {
|
|
tcp.enableKeepalive = true
|
|
tcp.keepaliveIdle = 2
|
|
tcp.keepaliveInterval = 1
|
|
tcp.keepaliveCount = 3
|
|
tcp.noDelay = true
|
|
}
|
|
return parameters
|
|
}
|
|
}
|