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.
Musubi/Packages/MusubiKit/Sources/Remote/Link.swift

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
}
}

Powered by TurnKey Linux.