Finally, TCPConnectionManager
class TCPConnectionManager {
public let flow: NEAppProxyTCPFlow
private let connection: NWConnection
private let tcpConnection = NWTCPConnection()
private let log = OSLog(subsystem: "com.ybelikov.transparent.proxy.network.extension", category: "provider")
private let connectionQueue = DispatchQueue(label: "com.ybelikov.transparent.proxy.network.extension.TCPQueue")
init(flow: NEAppProxyTCPFlow, endpoint: NWHostEndpoint) {
self.flow = flow
let host = Network.NWEndpoint.Host(endpoint.hostname)
let port = Network.NWEndpoint.Port(endpoint.port) ?? Network.NWEndpoint.Port.any
os_log(.debug, log: self.log, "On TCP init. host: %@, port: %@", String(describing: host), String(describing: port))
self.connection = NWConnection(host: host, port: port, using: .tcp)
}
public func startExchangingData() {
connection.stateUpdateHandler = self.stateChangedCallback(to:)
connection.start(queue: connectionQueue)
}
private func stateChangedCallback(to state: NWConnection.State) {
switch state {
case .ready:
os_log(.debug, log: self.log, "TCPConnectionManager::stateChangedCallback. TCP connection is ready")
flow.open(withLocalEndpoint: tcpConnection.localAddress as? NWHostEndpoint) { error in
if let error = error {
os_log(.debug, log: self.log, "TCP flow opening failed %@", error.localizedDescription)
return
}
self.handleOutgoingTCPData()
}
break
case .failed(let error):
os_log(.debug, log: self.log, "TCPConnectionManager::stateChangedCallback. TCP connection is failed %@", error.localizedDescription)
self.connection.cancel()
break
case .cancelled:
os_log(.debug, log: self.log, "TCPConnectionManager::stateChangedCallback. TCP connection is cancelled")
break
case .preparing:
os_log(.debug, log: self.log, "TCPConnectionManager::stateChangedCallback. TCP connection is preparing")
break
case .setup:
os_log(.debug, log: self.log, "TCPConnectionManager::stateChangedCallback. TCP connection is in setup state")
break
case .waiting(let error):
os_log(.debug, log: self.log, "TCPConnectionManager::stateChangedCallback. TCP connection is in waiting state, %@", error.localizedDescription)
self.connection.cancel()
break
default:
os_log(.debug, log: self.log, "TCPConnectionManager::stateChangedCallback. State is unknown")
break
}
}
private func handleOutgoingTCPData() {
os_log(.debug, log: self.log, "handleOutgoingTCPData. handling TCP flow data")
flow.readData { data, error in
if let error = error {
os_log(.debug, log: self.log, "handleOutgoingTCPData. Error on handling TCP flow data: %@", error.localizedDescription)
return
}
if let data = data, !data.isEmpty {
self.connection.send(content: data, completion: .contentProcessed({ error in
if let error = error {
os_log(.debug, log: self.log, "TCPConnectionManager::sendDataToEndpoint. TCP error: %@", error.localizedDescription)
return
}
os_log(.debug, log: self.log, "TCP data sent successfully")
self.handleOutgoingTCPData()
}))
} else {
self.handleIncomingTCPData()
}
}
}
private func handleIncomingTCPData() {
os_log(.debug, log: self.log, "On handleIncomingTCPData")
connection.receive(minimumIncompleteLength: 0,
maximumLength: 2048) { (data, _, isComplete, error) in
switch (data, isComplete, error) {
case (let data?, _, _):
os_log(.debug, log: self.log, "On handleIncomingTCPData. Received data, writing it to the flow")
self.flow.write(data) { writeError in
if writeError == nil {
os_log(.debug, log: self.log, "On handleIncomingTCPData. Write error is nil")
self.handleIncomingTCPData()
}
}
case (_, true, _):
os_log(.debug, log: self.log, "On handleIncomingTCPData. Connection is completed")
self.connection.stateUpdateHandler = nil
self.connection.cancel()
self.flow.closeReadWithError(error)
self.flow.closeWriteWithError(error)
case (_, _, let error?):
os_log(.debug, log: self.log, "On handleIncomingTCPData. Error: %@", error.localizedDescription)
self.connection.cancel()
self.flow.closeReadWithError(error)
self.flow.closeWriteWithError(error)
default:
os_log(.debug, log: self.log, "On handleIncomingTCPData. Unknown case")
}
}
}
}