Can't the input loop of a NWProtocolFramerImplementation be like...

Recently, I've been asking here about creating a NWProtocolFramerImplementation that splits across line breaks. Examples and questions usually involve some glob in a "while true" loop. From reading around, couldn't that function be implemented as:

  func handleInput(framer: NWProtocolFramer.Instance) -> Int {
      // Pull out objects until an incomplete object is discovered.
      var domainData: Data?
      while framer.parseInput(
        minimumIncompleteLength: 1,
        maximumLength: .max,
        parse: { buffer, isComplete in
          guard let buffer else { return 0 }  // Why is `buffer` an Optional?

          // Other checks and computations
          // Mutate domainData with the result of this iteration.
          // Return how many bytes from the buffer are an irrevocable part of domainData.
        }
      ) {
        defer { domainData = nil }
        guard let domainData else { break }

        let domainMessage = //...
        let framerMessage = NWProtocolFramer.Message(myMessage: domainMessage)
        // The return value is whether or not the delivery was immediate.
        _ = framer.deliverInputNoCopy(length: domainData.count, message: framerMessage, isComplete: true)
      }
    return 0
  }

A clear separation of concerns, and makes figuring out what's going on easier.

Answered by DTS Engineer in 908552022

I’m not sure what you’re actually asking here, so rather than guess I’m gonna drop in the core code of one of the test frameworks that I created. This parses DNS messages out of a TCP stream, where is DNS message starts with a two-byte length in network byte order.

Sadly, I never created a line-based framer, and I’m unlikely to ever do that because these days I typically do this work outside of the networking stack, relying on Swift async functions for the I/O side of things.

Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

func handleInput(framer instance: NWProtocolFramer.Instance) -> Int {
    while true {
        var payloadLength: Int = 0
        var success = instance.parseInput(minimumIncompleteLength: 2, maximumLength: 2) { buffer, _ in
            guard let buffer = buffer, buffer.count >= 2 else { return 0 }
            let length16 = buffer.reduce(0) { soFar, next in (soFar << 8) | UInt16(next) }
            payloadLength = Int(length16)
            // We return 0 here to specifically /not/ consume the two bytes
            // we’ve parsed. That’s because the second parse might fail due
            // to a lack of input.  We don’t want to save the length in some
            // state (Barbie says “Mutable state is hard!” and she’s not
            // wrong) so, if we hit this situation, we end up paying the
            // (very small) price of parsing the length again.
            return 0
        }
        // If there weren’t enough bytes to parse the header, return 2 to
        // indicate that there needs to be at least 2 bytes available to
        // bother calling us again.
        guard success else { return 2 }
        let frameLength = 2 + payloadLength
        success = instance.parseInput(minimumIncompleteLength: frameLength, maximumLength: frameLength) { buffer, _ in
            guard let buffer = buffer, buffer.count >= frameLength else { return 0 }
            let payload = Data(buffer.dropFirst(2))
            let message = NWProtocolFramer.Message(instance: instance)
            // `deliverInput(data:message:isComplete:)` won’t accept an
            // empty payload so we call `deliverInputNoCopy(length:message:isComplete:)`
            // in that case.
            if payload.isEmpty {
                let didDeliver = instance.deliverInputNoCopy(length: 0, message: message, isComplete: true)
                // This return false if the data “will be delivered once
                // more bytes become available” but we’re not asking it to
                // deliver any data so assert that didn’t happen.  If we hit
                // this trap, we’ve missed something important about how the
                // API works.
                assert(didDeliver)
            } else {
                instance.deliverInput(data: payload, message: message, isComplete: true)
            }
            // Return length + 2 to consume both the 2 byte header and the
            // payload data.
            return frameLength
        }
        // If there weren’t enough bytes to parse the payload data, return
        // the number of bytes that we need, that is, the 2 byte header plus
        // the known payload length.
        guard success else { return frameLength }
    }
}

I’m not sure what you’re actually asking here, so rather than guess I’m gonna drop in the core code of one of the test frameworks that I created. This parses DNS messages out of a TCP stream, where is DNS message starts with a two-byte length in network byte order.

Sadly, I never created a line-based framer, and I’m unlikely to ever do that because these days I typically do this work outside of the networking stack, relying on Swift async functions for the I/O side of things.

Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

func handleInput(framer instance: NWProtocolFramer.Instance) -> Int {
    while true {
        var payloadLength: Int = 0
        var success = instance.parseInput(minimumIncompleteLength: 2, maximumLength: 2) { buffer, _ in
            guard let buffer = buffer, buffer.count >= 2 else { return 0 }
            let length16 = buffer.reduce(0) { soFar, next in (soFar << 8) | UInt16(next) }
            payloadLength = Int(length16)
            // We return 0 here to specifically /not/ consume the two bytes
            // we’ve parsed. That’s because the second parse might fail due
            // to a lack of input.  We don’t want to save the length in some
            // state (Barbie says “Mutable state is hard!” and she’s not
            // wrong) so, if we hit this situation, we end up paying the
            // (very small) price of parsing the length again.
            return 0
        }
        // If there weren’t enough bytes to parse the header, return 2 to
        // indicate that there needs to be at least 2 bytes available to
        // bother calling us again.
        guard success else { return 2 }
        let frameLength = 2 + payloadLength
        success = instance.parseInput(minimumIncompleteLength: frameLength, maximumLength: frameLength) { buffer, _ in
            guard let buffer = buffer, buffer.count >= frameLength else { return 0 }
            let payload = Data(buffer.dropFirst(2))
            let message = NWProtocolFramer.Message(instance: instance)
            // `deliverInput(data:message:isComplete:)` won’t accept an
            // empty payload so we call `deliverInputNoCopy(length:message:isComplete:)`
            // in that case.
            if payload.isEmpty {
                let didDeliver = instance.deliverInputNoCopy(length: 0, message: message, isComplete: true)
                // This return false if the data “will be delivered once
                // more bytes become available” but we’re not asking it to
                // deliver any data so assert that didn’t happen.  If we hit
                // this trap, we’ve missed something important about how the
                // API works.
                assert(didDeliver)
            } else {
                instance.deliverInput(data: payload, message: message, isComplete: true)
            }
            // Return length + 2 to consume both the 2 byte header and the
            // payload data.
            return frameLength
        }
        // If there weren’t enough bytes to parse the payload data, return
        // the number of bytes that we need, that is, the 2 byte header plus
        // the known payload length.
        guard success else { return frameLength }
    }
}
Can't the input loop of a NWProtocolFramerImplementation be like...
 
 
Q