Usage of QUIC APIs inside HTTP/3 implementation (URLSession/Request)

Hello,

I have a very basic quic client implementation. When you run this code with some basic quic server, you will see that we can't get a handle to stream identifier 0, but behavior is actually different when we use URLSession/URLRequest, and I can see that some information can be sent over the wire for stream identifier 0 with that implementation.

You can find both code below I'm using to test this.

I'd like to get more info about how I can use stream identifier 0 with NWMultiplexGroup, if I can't use it with NWMultiplexGroup, I need a workaround to use stream with id 0 and use multiple streams over the same connection.

import Foundation
import Network


let dispatchQueue = DispatchQueue(label: "quicConnectionQueue")
let incomingStreamQueue = DispatchQueue(label: "quicIncStreamsQueue")
let outgoingStreamQueue = DispatchQueue(label: "quicOutStreamsQueue")


let quicOptions = NWProtocolQUIC.Options()
quicOptions.alpn = ["test"]
sec_protocol_options_set_verify_block(quicOptions.securityProtocolOptions, { (sec_prot_metadata, sec_trust, complete_callback) in
    complete_callback(true)
}, dispatchQueue)
let parameters = NWParameters(quic: quicOptions);
let multiplexGroup = NWMultiplexGroup(to: NWEndpoint.hostPort(host: "127.0.0.1", port: 5000))

let connectionGroup = NWConnectionGroup(with: multiplexGroup, using: parameters)


connectionGroup.stateUpdateHandler = { newState in
    switch newState {
    case .ready:
        print("Connected using QUIC!")
        
        let _ = createNewStream(connGroup: connectionGroup, content: "First Stream")
        let _ = createNewStream(connGroup: connectionGroup, content: "Second Stream")
        
        break
    default:
        print("Default hit: newState: \(newState)")
    }
}

connectionGroup.newConnectionHandler = { newConnection in
    
    // Set state update handler on incoming stream
    newConnection.stateUpdateHandler = { newState in
        // Handle stream states
    }
    
    // Start the incoming stream
    newConnection.start(queue: incomingStreamQueue)

}

connectionGroup.start(queue: dispatchQueue)
sleep(50)

func createNewStream(connGroup: NWConnectionGroup, content: String) -> NWConnection? {
    let stream = NWConnection(from: connectionGroup)
    stream?.stateUpdateHandler = { streamState in
        switch streamState {
        case .ready:
            stream?.send(content: content.data(using: .ascii), completion: .contentProcessed({ error in
                print("Send completed! Error: \(String(describing: error))")
            }))
            print("Sent data!")
            printStreamId(stream: stream)
            break
        default:
            print("Default  hit: streamState: \(streamState)")
        }
    }
    stream?.start(queue: outgoingStreamQueue)
    return stream
}

func printStreamId(stream: NWConnection?)
{
    let streamMetadata = stream?.metadata(definition: NWProtocolQUIC.definition) as? NWProtocolQUIC.Metadata
    print("stream Identifier: \(String(describing: streamMetadata?.streamIdentifier))")
}

URLSession/URLRequest code:

import Foundation

var networkManager = NetworkManager()
networkManager.testHTTP3Request()
sleep(5)


class NetworkManager: NSObject, URLSessionDataDelegate {
    
    private var session: URLSession!
    private var operationQueue = OperationQueue()
    
    func testHTTP3Request() {
        
        if self.session == nil {
            let config = URLSessionConfiguration.default
            config.requestCachePolicy = .reloadIgnoringLocalCacheData
            self.session = URLSession(configuration: config, delegate: self, delegateQueue: operationQueue)
        }
        
        let urlStr = "https://localhost:5000"
        let url = URL(string: urlStr)!
        var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: 60.0)
        request.assumesHTTP3Capable = true
        self.session.dataTask(with: request) { (data, response, error) in
            if let error = error as NSError? {
                print("task transport error \(error.domain) / \(error.code)")
                return
            }
            guard let data = data, let response = response as? HTTPURLResponse else {
                print("task response is invalid")
                return
            }


            guard 200 ..< 300 ~= response.statusCode else {
                print("task response status code is invalid; received \(response.statusCode), but expected 2xx")
                return
            }
            print("task finished with status \(response.statusCode), bytes \(data.count)")
        }.resume()
        
    }
}


extension NetworkManager {
    
    func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) {
        let protocols = metrics.transactionMetrics.map { $0.networkProtocolName ?? "-" }
        print("protocols: \(protocols)")
    }
    func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
        if challenge.protectionSpace.serverTrust == nil {
            completionHandler(.useCredential, nil)
        } else {
            let trust: SecTrust = challenge.protectionSpace.serverTrust!
            let credential = URLCredential(trust: trust)
            completionHandler(.useCredential, credential)
        }
    }
}
Usage of QUIC APIs inside HTTP/3 implementation (URLSession/Request)
 
 
Q