Snippets

Mikael Eiman Receiving UDP in Swift

Created by Mikael Eiman
//
//  main.swift
//  SyncThing-Protocol
//
//  Created by Mikael Eiman on 2023-07-10.
//

import Foundation
import Network

print("Hello, World!")


enum SyncThingError : Error {
    case badMagic
}

var listener: NWListener?
var connections: [NWConnection] = []

do {
    // 21027
    let udp = NWParameters.udp
    udp.allowLocalEndpointReuse = true
    udp.allowFastOpen = true
    listener = try NWListener(using: udp, on: 21027)
} catch {
    print("exception upon creating listener")
    exit(1)
}

listener?.stateUpdateHandler = {(newState) in
    switch newState {
    case .ready:
        print("Listener ready")
    default:
        print("Something else")
        break
    }
}


func receive(on connection: NWConnection) {
    print("receive(): preparing callback")
    
    connection.receiveMessage { (data, context, isComplete, error) in
        print("receive callback")
        
        if !isComplete {
            print("Not Complete")
        }
        if let error = error {
            print("Error in receive: - ",error)
            return
        }
        if let data = data, !data.isEmpty {
            print("!!! Data received: \(data.count) bytes")
            
            do {
                // verify magic bytes
                let rawInt = data[0..<4]
                let magic = rawInt.reduce(0) { Int($0) << 8 + Int($1) }
                print("Magic is \(magic)")
                
                if magic != 0x2EA7D90B {
                    throw SyncThingError.badMagic
                }
                
                // parse protobuf
                let decoded = try Announce(serializedData: data.dropFirst(4))
                
                print("Announce:")
                print("Instance ID:  \(decoded.instanceID)")
                print("ID as base64: \(decoded.id.base64EncodedString())")
                print("Addresses:    \(decoded.addresses)")
            }
            catch let err {
                print("An error: \(err)")
            }
                
        }
        
        // prepare another receive
        receive(on: connection)
    }
}


listener?.newConnectionHandler = {(newConnection) in
    print("Got a new connection, preparing...")
    
    connections.append(newConnection)
    
    newConnection.stateUpdateHandler = {newState in
        print("Connection has new state: \(newState)")
        switch newState {
        case .ready:
            print("Connection ready")
            receive(on: newConnection)
            return
        default:
            break
        }
    }
    
    //newConnection.start(queue: DispatchQueue(label: "newconn"))
    newConnection.start(queue: .main)
    print("Started new connection")
    
    listener?.cancel()
    print("Stopped listening for further connections")
}


listener?.start(queue: .main)


//
/*
 let connection = NWConnection(host: "255.255.255.255", port: 21027, using: .udp)
connection.stateUpdateHandler = { (newState) in
        switch (newState) {
        case .ready:
            print("ready")
            //self.send()
            //self.receive()
            connection.receiveMessage { (data, context, isComplete, error) in
                print("Got it")
            }
        case .setup:
            print("setup")
        case .cancelled:
            print("cancelled")
        case .preparing:
            print("Preparing")
        default:
            print("waiting or failed")
        }
    }

connection.start(queue: .global())
*/

/*
import Foundation
import Network
import Combine

class UDPListener: ObservableObject {
    
    var listener: NWListener?
    var connection: NWConnection?
    var queue = DispatchQueue.global(qos: .userInitiated)
    /// New data will be place in this variable to be received by observers
    @Published private(set) public var messageReceived: Data?
    /// When there is an active listening NWConnection this will be `true`
    @Published private(set) public var isReady: Bool = false
    /// Default value `true`, this will become false if the UDPListener ceases listening for any reason
    @Published public var listening: Bool = true
    
    /// A convenience init using Int instead of NWEndpoint.Port
    convenience init(on port: Int) {
        self.init(on: NWEndpoint.Port(integerLiteral: NWEndpoint.Port.IntegerLiteralType(port)))
    }
    /// Use this init or the one that takes an Int to start the listener
    init(on port: NWEndpoint.Port) {
        let params = NWParameters.udp
        params.allowFastOpen = true
        self.listener = try? NWListener(using: params, on: port)
        self.listener?.stateUpdateHandler = { update in
            switch update {
            case .ready:
                self.isReady = true
            case .failed, .cancelled:
                // Announce we are no longer able to listen
                self.listening = false
                self.isReady = false
            default:
                ()
            }
        }
        self.listener?.newConnectionHandler = { connection in
            self.createConnection(connection: connection)
        }
        self.listener?.start(queue: self.queue)
    }
    
    private func createConnection(connection: NWConnection) {
        self.connection = connection
        self.connection?.stateUpdateHandler = { (newState) in
            switch (newState) {
            case .ready:
                self.receive()
            case .cancelled, .failed:
                // Cancel the listener, something went wrong
                self.listener?.cancel()
                // Announce we are no longer able to listen
                self.listening = false
            default:
                ()
            }
        }
        self.connection?.start(queue: .global())
    }
    
    func receive() {
        self.connection?.receiveMessage { data, context, isComplete, error in
            if error != nil {
                return
            }
            guard isComplete, let data = data else {
                return
            }
            
            self.messageReceived = data
            print("Rx data size = \(data.count)")
            if self.listening {
                self.receive()
            }
        }
    }
    
    func cancel() {
        self.listening = false
        self.connection?.cancel()
    }
}


let listener = UDPListener(on: 21027)
*/

// Do nothing for a while to see if we get any data
try await Task.sleep(nanoseconds: 63_000_000_000)

print("End of line")

Comments (0)

HTTPS SSH

You can clone a snippet to your computer for local editing. Learn more.