Reading empty UDP datagrams

Hi Cory and everyone,

I'm trying to implement a UDP based protocol and everything has been working fine except when I get a datagram with no data. When that happens, my server crashes with an assertion failure.

For the protocol I'm implementing, an empty datagram has a specific meaning. Is there a way to make it work?

This is the error I get:

NIOPosix/SocketChannel.swift:628: Assertion failed

This is what tcpdump shows immediate before the error occurs:

14:13:00.931507 IP > UDP, length 0

Here's the failing assertion in swift-nio:

Here's my application code:

import NIOCore
import NIOPosix

public struct SwiftProtohackers {
    public static func main() throws {
        let unusualDatabaseMessageHandler = UnusualDatabaseMessageHandler()

        let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
        let bootstrap = DatagramBootstrap(group: group)
            .channelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
            .channelInitializer { channel in
                channel.pipeline.addHandler(UnusualDatabaseMessageDecoder()).flatMap { v in

        defer {
            try! group.syncShutdownGracefully()
        let channel = try bootstrap.bind(host: "", port: 9999).wait()
        print("Server started and listening on \(channel.localAddress!)")
        try channel.closeFuture.wait()
        print("Server closed")

actor UnusualDatabase {
    enum Operation {
        case insert(key: String, value: String)
        case retrieve(key: String)
        init(rawValue s: String) {
            if let eqIdx = s.firstIndex(of: "=") {
                self = .insert(key: String(s[..<eqIdx]), value: String(s[eqIdx...].dropFirst()))
            } else {
                self = .retrieve(key: s)

    private var data: [String: String] = ["version": "SwiftProtohackers 1.0"]
    func perform(_ op: Operation) -> String? {
        switch op {
        case let .insert(key, value):
            if key != "version" {
                data[key] = value
            return nil
        case let .retrieve(key):
            return "\(key)=\(data[key] ?? "")"

final class UnusualDatabaseMessageDecoder: ChannelInboundHandler {
    typealias InboundIn = AddressedEnvelope<ByteBuffer>
    typealias InboundOut = AddressedEnvelope<UnusualDatabase.Operation>
    func channelRead(context: ChannelHandlerContext, data: NIOAny) {
        let envelope = unwrapInboundIn(data)
        var buffer =
        guard let message = buffer.readString(length: buffer.readableBytes)?.trimmingCharacters(in: .whitespacesAndNewlines) else {
            print("Error: invalid string received")
        let operation = UnusualDatabase.Operation(rawValue: message)
            wrapInboundOut(AddressedEnvelope(remoteAddress: envelope.remoteAddress, data: operation))
    func errorCaught(context: ChannelHandlerContext, error: Error) {
        print("error:", error)
        context.close(promise: nil)

final class UnusualDatabaseMessageHandler: ChannelInboundHandler {
    typealias InboundIn = AddressedEnvelope<UnusualDatabase.Operation>
    typealias OutboundOut = AddressedEnvelope<ByteBuffer>
    let database = UnusualDatabase()
    func channelRead(context: ChannelHandlerContext, data: NIOAny) {
        let request = unwrapInboundIn(data)
        let channel =

        Task {
            if let response = await database.perform(, channel.isWritable {
                let buffer = channel.allocator.buffer(string: response)
                let envelope = AddressedEnvelope(
                    remoteAddress: request.remoteAddress,
                    data: buffer
                channel.writeAndFlush(envelope, promise: nil)

    func errorCaught(context: ChannelHandlerContext, error: Error) {
        print("error:", error)
        context.close(promise: nil)

As an aside for the curious, I'm using Protohackers exercises to learn Swift NIO. This one is an implementation of problem 4. My solutions are on GitHub. I'm doing one commit per problem and force pushing when I change things, so the code might look different on the repo.

I think that assertion is simply incorrect! If you're up for it, I'd recommend adding a quick unit test to SwiftNIO that sends a zero-length datagram, and then remove the assertion, and upload that as a PR. We'd happily merge it.

1 Like

Awesome! On it.

Done Allow writing and reading empty datagrams by hashemi · Pull Request #2341 · apple/swift-nio · GitHub