tunnelkit/TunnelKit/Sources/AppExtension/Transport/NEUDPInterface.swift

205 lines
6.3 KiB
Swift

//
// NEUDPInterface.swift
// TunnelKit
//
// Created by Davide De Rosa on 8/27/17.
// Copyright © 2018 London Trust Media. All rights reserved.
//
import Foundation
import NetworkExtension
import SwiftyBeaver
private let log = SwiftyBeaver.self
class NEUDPInterface: NSObject, GenericSocket, LinkInterface {
private static var linkContext = 0
private let impl: NWUDPSession
private let maxDatagrams: Int
init(impl: NWUDPSession, communicationType: CommunicationType, maxDatagrams: Int? = nil) {
self.impl = impl
self.communicationType = communicationType
self.maxDatagrams = maxDatagrams ?? 200
isActive = false
isShutdown = false
}
// MARK: GenericSocket
private weak var queue: DispatchQueue?
private var isActive: Bool
private(set) var isShutdown: Bool
var remoteAddress: String? {
return (impl.resolvedEndpoint as? NWHostEndpoint)?.hostname
}
var hasBetterPath: Bool {
return impl.hasBetterPath
}
weak var delegate: GenericSocketDelegate?
func observe(queue: DispatchQueue, activeTimeout: Int) {
isActive = false
self.queue = queue
queue.schedule(after: .milliseconds(activeTimeout)) { [weak self] in
guard let _self = self else {
return
}
guard _self.isActive else {
_self.delegate?.socketDidTimeout(_self)
return
}
}
impl.addObserver(self, forKeyPath: #keyPath(NWUDPSession.state), options: [.initial, .new], context: &NEUDPInterface.linkContext)
impl.addObserver(self, forKeyPath: #keyPath(NWUDPSession.hasBetterPath), options: .new, context: &NEUDPInterface.linkContext)
}
func unobserve() {
impl.removeObserver(self, forKeyPath: #keyPath(NWUDPSession.state), context: &NEUDPInterface.linkContext)
impl.removeObserver(self, forKeyPath: #keyPath(NWUDPSession.hasBetterPath), context: &NEUDPInterface.linkContext)
}
func shutdown() {
impl.cancel()
}
func upgraded() -> GenericSocket? {
guard impl.hasBetterPath else {
return nil
}
return NEUDPInterface(impl: NWUDPSession(upgradeFor: impl), communicationType: communicationType)
}
func link() -> LinkInterface {
return self
}
// MARK: Connection KVO (any queue)
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard (context == &NEUDPInterface.linkContext) else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
return
}
// if let keyPath = keyPath {
// log.debug("KVO change reported (\(anyPointer(object)).\(keyPath))")
// }
queue?.async {
self.observeValueInTunnelQueue(forKeyPath: keyPath, of: object, change: change, context: context)
}
}
private func observeValueInTunnelQueue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
// if let keyPath = keyPath {
// log.debug("KVO change reported (\(anyPointer(object)).\(keyPath))")
// }
guard let impl = object as? NWUDPSession, (impl == self.impl) else {
log.warning("Discard KVO change from old socket")
return
}
guard let keyPath = keyPath else {
return
}
switch keyPath {
case #keyPath(NWUDPSession.state):
if let resolvedEndpoint = impl.resolvedEndpoint {
log.debug("Socket state is \(impl.state) (endpoint: \(impl.endpoint) -> \(resolvedEndpoint))")
} else {
log.debug("Socket state is \(impl.state) (endpoint: \(impl.endpoint) -> in progress)")
}
switch impl.state {
case .ready:
guard !isActive else {
return
}
isActive = true
delegate?.socketDidBecomeActive(self)
case .cancelled:
isShutdown = true
delegate?.socket(self, didShutdownWithFailure: false)
case .failed:
isShutdown = true
// if timedOut {
// delegate?.socketShouldChangeProtocol(self)
// }
delegate?.socket(self, didShutdownWithFailure: true)
default:
break
}
case #keyPath(NWUDPSession.hasBetterPath):
guard impl.hasBetterPath else {
break
}
log.debug("Socket has a better path")
delegate?.socketHasBetterPath(self)
default:
break
}
}
// MARK: LinkInterface
let isReliable: Bool = false
let mtu: Int = 1000
var packetBufferSize: Int {
return maxDatagrams
}
let communicationType: CommunicationType
let negotiationTimeout: TimeInterval = 10.0
let hardResetTimeout: TimeInterval = 5.0
func setReadHandler(queue: DispatchQueue, _ handler: @escaping ([Data]?, Error?) -> Void) {
// WARNING: runs in Network.framework queue
impl.setReadHandler({ [weak self] (packets, error) in
guard let _ = self else {
return
}
queue.sync {
handler(packets, error)
}
}, maxDatagrams: maxDatagrams)
}
func writePacket(_ packet: Data, completionHandler: ((Error?) -> Void)?) {
impl.writeDatagram(packet) { (error) in
completionHandler?(error)
}
}
func writePackets(_ packets: [Data], completionHandler: ((Error?) -> Void)?) {
impl.writeMultipleDatagrams(packets) { (error) in
completionHandler?(error)
}
}
}
extension NEUDPInterface {
override var description: String {
guard let hostEndpoint = impl.endpoint as? NWHostEndpoint else {
return impl.endpoint.description
}
return "\(hostEndpoint.hostname):\(hostEndpoint.port)"
}
}