How to add a String to NWProtocolTLS.Options()?

I want achieve what @eskimo said in his answer on this page:
  1. Have each peer decide on its own unique ID.

  2. When a client connects to a server, the first message it sends across that connection contains its unique ID.

  3. The server then responds with its own unique ID.

  4. If either end discovers that a connection with that unique ID pair already exists, it drops one of the connections. As long as they both agree to drop the same connection, everything will be fine. A good strategy here is to drop the one with the lesser ID.

I looked at Apple's TicTokToe example app and they set a password string using a custom initializer. I followed some of their code but once the Listener starts and the connection.stateUpdateHandler is called it never goes past .preparing:

Code Block
connection.stateUpdateHandler = { [weak self](nwConnectionState) in
switch nwConnectionState {
case .preparing:
            print("Connection preparing") // it's stuck here
// ...
}
}

Here is how I use the custom param (rest of setup is in this question):

Code Block
var listener: NWListener?
init () {
do {
let uid = UUID().uuidString
let parameters = NWParameters(uid: uid)
listener = try NWListener(using: parameters)
listener?.service = NWListener.Service(name: "MyName", type: "_myApp._tcp")
startListening()
} catch {
}
}

I'm using UUID().uuidString to create the uid just so that I can attempt what @eskimo proposed in step 2 and step 4. Once the user kills the app and opens it again a new uid will be generated every time they try to connect with other devices. Because of this I don't need the uid to be secure. The Apple TikTokToe tls code seemed like overkill for my situation so I removed all of the encryption features that they used.

How do I add the uid string to the NWProtocolTLS.Options()?

Code Block
extension NWParameters {
convenience init(uid: String) {
let tcpOptions = NWProtocolTCP.Options()
      tcpOptions.enableKeepalive = true
      tcpOptions.keepaliveIdle = 2
    self.init(tls: NWParameters.tlsOptions(uid: uid), tcp: tcpOptions)
}
private static func tlsOptions(uid: String) -> NWProtocolTLS.Options {
let tlsOptions = NWProtocolTLS.Options()
return tlsOptions
}
}


It should be noted that outside of the problems inside the link, the problem from this question is avoided and .ready is called when I use the normal initializer:

Code Block
let tcpOptions = NWProtocolTCP.Options()
tcpOptions.enableKeepalive = true
tcpOptions.keepaliveIdle = 2
let parameters = NWParameters(tls: nil, tcp: tcpOptions)
parameters.includePeerToPeer = true



A few things here to double check:

but once the Listener starts and the connection.stateUpdateHandler is called it never goes past .preparing:

I am assuming that you are getting a browsing client reaching your Bonjour service and the listener is accepting the new connection. Check what the new connection's endpoint is and make sure that is set. If there is no know protocol for your endpoint try changing the NWParameters to use TCP so that this is defined. I believe what Quinn meant here, is that once the connection is completely established the fist send would include the unique id.

How do I add the uid string to the NWProtocolTLS.Options()?

I do not believe there is an option to add data to TLS options that does not work inside the context of TLS. How is the extension from NWParameters expected to work in this case for NWListener and NWConnection?

If you want to open a TSI I can take a deeper look at this and the code you have so far for this project.


Matt Eaton
DTS Engineering, CoreOS
meaton3@apple.com
@meaton hi and thanks for the help. I checked the endPoint and it's definitely set (below)


I do not believe there is an option to add data to TLS options that does not work inside the context of TLS. How is the extension from NWParameters expected to work in this case for NWListener and NWConnection? 

Based on what @eskimo said about each peer having its own a unique ID, this seemed like the only way to add it. That is the only reason I took that approach. Since you said that the way I'm doing isn't possible, then I wonder what he meant by that. I'll go back to that thread and ask him there or email him for further explanation.

As per the multiple call problem that I'm having, yes you are correct:

you are getting a browsing client reaching your Bonjour service and the listener is accepting the new connection

Using a device as the browser and simulator as the listener, I just added a print statement to listener.newConnectionHandler and when the connection came in, this is the print out:

Code Block
endpoint: fe80::149d:ff95:503f:9d9%en4.59013
endpoint: fe80::149d:ff95:503f:9d9%en4.59014
endpoint: fe80::149d:ff95:503f:9d9%en4.59015
endpoint: fe80::837:3ac4:fb1f:492e%en1.63936
Connection preparing // stops here


Listener:

Code Block
listener.newConnectionHandler = { [weak self](newConnection) in
self?.delegate?.createNewIncoming(newConnection) // called in the below vc that conforms to the delegate
}


vc:

Code Block
var connectionIncoming: ConnectionIncoming?
func createNewIncoming(_ connection: NWConnection) {
print("endpoint: ", connection.endPoint)
connectionIncoming = ConnectionIncoming(connection:connection, delegate: self)
}


NWConnection class named ConnectionIncoming:

Code Block
class ConnectionIncoming {
    private var connection: NWConnection?
// ...
connection.stateUpdateHandler = { [weak self](nwConnectionState) in
switch nwConnectionState {
case .preparing:           
print("Connection preparing") // still stuck here
// ...
}
}
}


Here is the basic browser code if it makes any difference:

Code Block
protocol PeerBrowserDelegate: class {
    func createConnection(from endPoint: NWEndpoint)
}
class PeerBrowser {
weak var delegate: PeerBrowserDelegate?
fileprivate var browser: NWBrowser?
init() {
        let parameters = NWParameters()
        parameters.includePeerToPeer = true
        browser = NWBrowser(for: .bonjour(type: "_myApp._tcp", domain: "local"), using: parameters)
        startBrowsing()
    }
fileprivate func startBrowsing() {
        guard let browser = browser else { return }
        browser.stateUpdateHandler = { [weak self](newState) in
            switch newState {
            case .setup: print("Browser - setup")
            case .ready: print("Browser - ready")
            case .cancelled: print("Browser - Cancelled")
            case .failed(let error): /* restart code ... */
            default:break
            }
        }
        browser.browseResultsChangedHandler = { [weak self](results, changes) in
            for result in results {
                let endPoint = result.endpoint
                self?.delegate?.createConnection(from: endPoint) // will create a NWConnection from endPoint
                break
            }
        }
        browser.start(queue: .main)
    }
}

@meaton

If you want to open a TSI I can take a deeper look at this and the code you have so far for this project.

hi, I created a very small project for a TSI but I haven't submitted it yet. How do I submit it so that it comes to you directly?

I created a TSI for ARKit in the past, I don't remember there being a way to send it to a specific specialist.

How do I submit it so that it comes to you directly?

You can create it and call out this thread. Either Quinn or myself will see if and pick it up. Make sure to call out this thread as a continuation of your incident.


Matt Eaton
DTS Engineering, CoreOS
meaton3@apple.com
Here is the reason for the the multiple call problem, which isn't a problem, as explained by @eskimo here:


Apple’s connect-by-name API uses a fancy algorithm to run multiple connections in parallel to see which one connects first (see RFC 8305 for some background on this)


How to add a String to NWProtocolTLS.Options()?
 
 
Q