Writing an `NWProtocolFramerImplementation` to run on top of `NWProtocolWebSocket`

Hi All, I am trying to write an NWProtocolFramerImplementation that will run after Websockets. I would like to achieve two goals with this

  • Handle the application-layer authentication handshake in-protocol so my external application code can ignore it
  • Automatically send pings periodically so my application can ignore keepalive

I am running into trouble because the NWProtocolWebsocket protocol parses websocket metadata into NWMessage's and I don't see how to handle this at the NWProtocolFramerImplementation level

Here's what I have (see comments for questions)

class CoolProtocol: NWProtocolFramerImplementation {
    static let label = "Cool"
    private var tempStatusCode: Int?

    required init(framer: NWProtocolFramer.Instance) {}
    
    static let definition = NWProtocolFramer.Definition(implementation: CoolProtocol.self)
    
    func start(framer: NWProtocolFramer.Instance) -> NWProtocolFramer.StartResult { return .willMarkReady }

    func wakeup(framer: NWProtocolFramer.Instance) { }

    func stop(framer: NWProtocolFramer.Instance) -> Bool { return true }

    func cleanup(framer: NWProtocolFramer.Instance) { }

    func handleOutput(framer: NWProtocolFramer.Instance, message: NWProtocolFramer.Message, messageLength: Int, isComplete: Bool) {
        // How to write a "Message" onto the next protocol handler. I don't want to just write plain data.
        // How to tell the websocket protocol framer that it's a ping/pong/text/binary...
    }

    func handleInput(framer: NWProtocolFramer.Instance) -> Int {
        // How to handle getting the input from websockets in a message format? I don't want to just get "Data" I would like to know if that data is
        // a ping, pong, text, binary, ...
    }
}

If I implementing this protocol at the application layer, here's how I would send websocket messages

class Client {
    ...
    func send(string: String) async throws {
        guard let data = string.data(using: .utf8) else {
            return
        }
        let metadata = NWProtocolWebSocket.Metadata(opcode: .text)
        let context = NWConnection.ContentContext(
            identifier: "textContext",
            metadata: [metadata]
        )

        self.connection.send(
            content: data,
            contentContext: context,
            isComplete: true,
            completion: .contentProcessed({ [weak self] error in
                ...
            })
        )
    }
}

You see at the application layer I have access to this context object and can access NWProtocolMetadata on the input and output side, but in NWProtocolFramer.Instance I only see

final func writeOutput(data: Data)

which doesn't seem to include context anywhere.

Is this possible? If not how would you recommend I handle this? I know I could re-write the entire Websocket protocol framer, but it feels like I shouldn't have to if framers are supposed to be able to stack.

Writing an `NWProtocolFramerImplementation` to run on top of `NWProtocolWebSocket`
 
 
Q