another.im-ios/AnotherIM/xmpp/modules/connection/DirectTLSSocket.swift

103 lines
3.8 KiB
Swift
Raw Normal View History

2024-06-19 15:06:39 +00:00
import Foundation
import Network
private let doPrint = false
final class DirectTLSSocket: Socket {
let (events, eventsContinuation) = AsyncStream.makeStream(of: SocketEvent.self, bufferingPolicy: .unbounded)
private let queue: DispatchQueue
private var nwConnection: NWConnection?
init(id: String, host: String, port: Int, allowInsecure: Bool) {
queue = DispatchQueue(label: "another.xmpp.network.queue_\(id)")
// tcp options
let tcpOptions = NWProtocolTCP.Options()
tcpOptions.noDelay = true
tcpOptions.connectionTimeout = 5
tcpOptions.enableFastOpen = true
tcpOptions.disableAckStretching = true
let params = NWParameters(tls: nil, tcp: tcpOptions)
params.serviceClass = .responsiveData
// tls options
let tlsOptions = NWProtocolTLS.Options()
sec_protocol_options_set_min_tls_protocol_version(tlsOptions.securityProtocolOptions, .TLSv12)
sec_protocol_options_set_max_tls_protocol_version(tlsOptions.securityProtocolOptions, .TLSv13)
// sec_protocol_options_set_peer_authentication_required(tlsOptions.securityProtocolOptions, false)
if let domain = host.cString(using: .utf8) {
sec_protocol_options_set_tls_server_name(tlsOptions.securityProtocolOptions, domain)
}
sec_protocol_options_set_verify_block(tlsOptions.securityProtocolOptions, { _, sec_trust, sec_protocol_verify_complete in
if allowInsecure {
sec_protocol_verify_complete(true)
} else {
let trust = sec_trust_copy_ref(sec_trust).takeRetainedValue()
var error: CFError?
if SecTrustEvaluateWithError(trust, &error) {
sec_protocol_verify_complete(true)
} else {
sec_protocol_verify_complete(false)
}
}
}, queue)
// nw connection
nwConnection = NWConnection(host: .name(host, nil), port: .init(integerLiteral: UInt16(port)), using: .init(tls: tlsOptions, tcp: tcpOptions))
// swiftlint:disable:next force_unwrapping
nwConnection!.stateUpdateHandler = { [weak self] state in
switch state {
case .ready:
self?.eventsContinuation.yield(.state(.connected))
self?.read()
case .waiting(let error), .failed(let error):
print(error.localizedDescription)
self?.eventsContinuation.yield(.state(.disconnected(error)))
default:
break
}
}
}
deinit {
eventsContinuation.finish()
nwConnection?.cancel()
}
func connect() async {
nwConnection?.start(queue: queue)
}
func send(_ data: Data) async {
log(data: data, read: false)
nwConnection?.send(content: data, completion: .contentProcessed { [weak self] error in
if let err = error {
self?.eventsContinuation.yield(.state(.disconnected(err)))
}
})
}
private func read() {
nwConnection?.receive(minimumIncompleteLength: 1, maximumLength: 4096 * 2, completion: { [weak self] data, _, _, error in
if let err = error {
self?.eventsContinuation.yield(.state(.disconnected(err)))
return
}
guard let data else { return }
self?.log(data: data, read: true)
self?.eventsContinuation.yield(.dataReceived(data))
self?.read()
})
}
private func log(data: Data, read: Bool) {
if doPrint {
let direction = read ? "read-in" : "write-out"
let str = String(bytes: data, encoding: .ascii) ?? "?"
print("\nDirectTls \(direction): \(str)\n")
}
}
}