Reputation: 3562
I'm trying to use the new Network.framework to connect to WebSocket but facing nil handshake response from server.
(Yes, I know Starscream exist but it didnt support Proxy / Mobility of user switching between network interface)
My test code:
func beginTest() {
let connection = NWConnection(host: "echo.websocket.org", port: 443, using: .tls)
connection.stateUpdateHandler = { state in
print("State:", state)
switch state {
case .ready:
self.connectionReady(connection)
default:
break
}
}
connection.start(queue: .main)
}
func connectionReady(_ connection: NWConnection) {
let raw = """
GET / HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: echo.websocket.org
Origin: https://echo.websocket.org
Sec-WebSocket-Key: s04nPqA7M6pQ3Lu2jRJLSQ==
Sec-WebSocket-Version: 13
"""
let rawData = raw.appending("\n\n\n").replacingOccurrences(of: "\n", with: "\r\n").data(using: .utf8)
connection.send(content: rawData!, completion: .idempotent)
connection.receiveMessage(completion: {data, context, bool, error in
if let data = data {
print("Received:", String(data: data, encoding: .utf8))
}
print("Error:", error)
let hello = "Hello".data(using: .utf8)
connection.send(content: hello, completion: .idempotent)
})
}
It's nil response and connection dropped instead of getting Upgrade handshake response from server, below with console logs:
State: preparing
State: ready
Received: nil
Error: nil
2018-10-08 11:38:57.314885+0800 SwiftNetworkTest[86448:3026660] [] nw_socket_handle_socket_event [C1.1:2] Socket SO_ERROR [54: Connection reset by peer]
Can anyone guide me how to utilize Apple new Network.framework? It will be much appreciated!
My bad, I'm now able to see the handshake response with using .ascii
encoding instead of .utf8
.
But I'm still having connection dropped Connection reset by peer
. How do I retain the connection after upgrade to WebSocket?
Upvotes: 2
Views: 1883
Reputation: 1
After reading through the documentation: https://developer.apple.com/documentation/security/certificate_key_and_trust_services/trust/evaluating_a_trust_and_parsing_the_result
I setup my authenticated TLS connection like this:
init(endpoint: NWEndpoint, interface: NWInterface?, passcode: String, delegate: BitfinexConnectionDelegate)
{
self.delegate = delegate
self.initiatedConnection = false
let host = "api.bitfinex.com"
let port = 443
let options = NWProtocolTCP.Options()
options.connectionTimeout = 15
let tlsOptions = NWProtocolTLS.Options()
sec_protocol_options_set_verify_block(
tlsOptions.securityProtocolOptions,
{
(sec_protocol_metadata, sec_trust, sec_protocol_verify_complete) in
let trust = sec_trust_copy_ref(sec_trust).takeRetainedValue()
let pinner = FoundationSecurity()
pinner.evaluateTrust(trust: trust, domain: host, completion:
{
(state) in
switch state
{
case .success:
sec_protocol_verify_complete(true)
case .failed(_):
sec_protocol_verify_complete(false)
}
})
}, queue
)
let parameters = NWParameters(tls: tlsOptions, tcp: options)
let conn = NWConnection(host: NWEndpoint.Host.name(host, nil),
port: NWEndpoint.Port(rawValue: UInt16(port))!,
using: parameters
)
self.connection = conn
startConnection()
}
The FoundationSecurity() block is a simple TLS evaluation
if SecTrustEvaluateWithError(trust, &error)
{
completion(.success)
}
else
{
completion(.failed(error))
}
Once my connection was ready. I sent through a Data object which I created like this. This depends on the API which you are interfacing with.
private func prepareWebSocket() throws -> Data
{
let apiKey = "API_KEY"
let apiSecret = "API_SECRET"
let authNonce = NonceProvider.sharedInstanse.nonce
let authPayload = "AUTH\(authNonce)"
let authenticationKey = SymmetricKey(data: apiSecret.data(using: .ascii)!)
let authenticationCode = HMAC<SHA384>.authenticationCode(for: authPayload.data(using: .ascii)!,
using: authenticationKey
)
let authSig = authenticationCode.compactMap { String(format: "%02hhx", $0) }.joined()
let payload: [String : Any] =
[
"event": "auth",
"apiKey" : apiKey,
"authSig": authSig,
"authPayload": authPayload,
"authNonce": authNonce
]
return try JSONSerialization.data(withJSONObject: payload, options: .fragmentsAllowed)
}
Upvotes: 0
Reputation: 352
You should follow the Websocket guidelines how to format Websocket sent message.
I think this is a good resource. I used it myself.
https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers
Upvotes: 1