205 lines
6.3 KiB
Swift
205 lines
6.3 KiB
Swift
|
//
|
||
|
// NEUDPInterface.swift
|
||
|
// PIATunnel
|
||
|
//
|
||
|
// 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)"
|
||
|
}
|
||
|
}
|