From d2d09091ddd0a109b1798bd7058ac378bce79586 Mon Sep 17 00:00:00 2001 From: David Lichteblau Date: Fri, 17 Mar 2017 14:37:50 +0200 Subject: [PATCH 01/16] Draft UDP socket support, inspired by Node's dgram module --- Package.swift | 4 + Samples/udpd/.gitignore | 16 ++ Samples/udpd/GNUmakefile | 7 + Samples/udpd/Package.swift | 9 + Samples/udpd/main.swift | 23 ++ Sources/dgram/Internals.swift | 58 +++++ Sources/dgram/Module.swift | 57 +++++ Sources/dgram/Socket.swift | 333 ++++++++++++++++++++++++++ Sources/fs/FileDescriptor.swift | 2 +- Sources/net/Util.swift | 2 +- Sources/xsys/POSIXError.swift | 1 + Sources/xsys/fd.swift | 4 + Sources/xsys/socket.swift | 2 + Tests/GNUmakefile | 2 +- Tests/LinuxMain.swift | 6 + Tests/dgramTests/DgramTests.swift | 127 ++++++++++ Tests/dgramTests/GNUmakefile | 10 + Tests/dgramTests/NozeIOTestCase.swift | 102 ++++++++ 18 files changed, 762 insertions(+), 3 deletions(-) create mode 100644 Samples/udpd/.gitignore create mode 100644 Samples/udpd/GNUmakefile create mode 100644 Samples/udpd/Package.swift create mode 100644 Samples/udpd/main.swift create mode 100644 Sources/dgram/Internals.swift create mode 100644 Sources/dgram/Module.swift create mode 100644 Sources/dgram/Socket.swift create mode 100644 Tests/dgramTests/DgramTests.swift create mode 100644 Tests/dgramTests/GNUmakefile create mode 100644 Tests/dgramTests/NozeIOTestCase.swift diff --git a/Package.swift b/Package.swift index da935f4..6d278c5 100644 --- a/Package.swift +++ b/Package.swift @@ -63,6 +63,10 @@ let package = Package( .Target(name: "fs"), .Target(name: "dns") ]), + Target(name: "dgram", + dependencies: [ + .Target(name: "net"), + ]), Target(name: "process", dependencies: [ .Target(name: "core"), diff --git a/Samples/udpd/.gitignore b/Samples/udpd/.gitignore new file mode 100644 index 0000000..83eac8f --- /dev/null +++ b/Samples/udpd/.gitignore @@ -0,0 +1,16 @@ +# CocoaPods +# +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control? +# +# Pods/ + +.DS_Store +xcuserdata +.build +Packages +*~ +build +apidox + diff --git a/Samples/udpd/GNUmakefile b/Samples/udpd/GNUmakefile new file mode 100644 index 0000000..9298197 --- /dev/null +++ b/Samples/udpd/GNUmakefile @@ -0,0 +1,7 @@ +# GNUmakefile + +NOZE_DIR=../.. +PACKAGE=$(notdir $(shell pwd)) +$(PACKAGE)_SWIFT_MODULES = xsys core events streams net fs dns + +include $(NOZE_DIR)/xcconfig/rules.make diff --git a/Samples/udpd/Package.swift b/Samples/udpd/Package.swift new file mode 100644 index 0000000..0556f22 --- /dev/null +++ b/Samples/udpd/Package.swift @@ -0,0 +1,9 @@ +import PackageDescription + +let package = Package( + name: "udpd", + dependencies: [ + .Package(url: "../..", + majorVersion: 0, minor: 5) + ] +) diff --git a/Samples/udpd/main.swift b/Samples/udpd/main.swift new file mode 100644 index 0000000..2a30021 --- /dev/null +++ b/Samples/udpd/main.swift @@ -0,0 +1,23 @@ +// Noze.io UDP server +// - to compile in Swift 3 invoke: swift build +// - to run result: .build/debug/udpd +// - to try it: nc.openbsd -u 10000 + +import Foundation +import dgram +import net +import xsys + +let sock = dgram.createSocket() +sock + .onListening { address in print("dgram: bound to \(address)") } + .onError { err in print("error: \(err)") } + .onMessage { (msg, from) in + print("received: \(msg) from \(from)") + if let decoded = String(data: Data(msg), encoding: .utf8) { + print("decoded: \(decoded)") + print("calling send on \(sock) with \([UInt8](decoded.uppercased().utf8))") + sock.send([UInt8](decoded.uppercased().utf8), to: from) + } + } + .bind(10000) diff --git a/Sources/dgram/Internals.swift b/Sources/dgram/Internals.swift new file mode 100644 index 0000000..3216e7d --- /dev/null +++ b/Sources/dgram/Internals.swift @@ -0,0 +1,58 @@ +import fs +import net +import xsys + +public func recvfrom( + _ fd: FileDescriptor, + likeAddress ignored: AT?, + count: Int = 65535) + -> ( Error?, [ UInt8 ]?, AT?) +{ + // TODO: inefficient init. Also: reuse buffers. + var buf = [ UInt8 ](repeating: 0, count: count) + + // synchronous + + var address = AT() + var addrlen = socklen_t(address.len) + let readCount = withUnsafeMutablePointer(to: &address) { ptr in + ptr.withMemoryRebound(to: xsys_sockaddr.self, capacity: 1) { + bptr in + return xsys.recvfrom(fd.fd, &buf, count, 0, + bptr, &addrlen) + } + } + + guard readCount >= 0 else { + return ( POSIXErrorCode(rawValue: xsys.errno)!, nil, nil ) + } + + // TODO: super inefficient. how to declare sth which works with either? + buf = Array(buf[0.. Error? +{ + // synchronous + + var data = data + var toAddress = toAddress + let addrlen = socklen_t(toAddress.len) + let writtenCount = withUnsafePointer(to: &toAddress) { ptr in + ptr.withMemoryRebound(to: xsys_sockaddr.self, capacity: 1) { + bptr in + return xsys.sendto(fd.fd, &data, data.count, 0, bptr, addrlen) + } + } + + guard writtenCount >= 0 else { + return POSIXErrorCode(rawValue: xsys.errno)! + } + + return nil +} diff --git a/Sources/dgram/Module.swift b/Sources/dgram/Module.swift new file mode 100644 index 0000000..945d814 --- /dev/null +++ b/Sources/dgram/Module.swift @@ -0,0 +1,57 @@ +// +// Module.swift +// Noze.io +// +// Created by Helge Heß on 4/10/16. +// Copyright © 2016 ZeeZide GmbH. All rights reserved. +// + +@_exported import core +@_exported import streams +import xsys + +public class NozeDgram : NozeModule { +} +public let module = NozeDgram() + + +open class CreateOptions { + /// Version of IP stack (IPv4) + public var family : sa_family_t = sa_family_t(xsys.AF_INET) + + public init() {} +} + +/// Creates a new `dgram.Socket` object. +/// +/// Optional onMessage block. +/// +/// Sample: +/// +/// let server = dgram.createSocket { sock in +/// print("connected") +/// } +/// .onError { error in +/// print("error: \(error)") +/// } +/// .bind(...) { +/// print("Server is listening on \($0.address)") +/// } +/// +@discardableResult +public func createSocket( + // TODO + options o: CreateOptions = CreateOptions(), + onMessage : MessageCB? = nil) -> Socket +{ + let sock = Socket() + if let cb = onMessage { _ = sock.onMessage(handler: cb) } + return sock +} + + +#if os(Linux) +#else + // importing this from xsys doesn't seem to work + import Foundation // this is for POSIXError : Error +#endif diff --git a/Sources/dgram/Socket.swift b/Sources/dgram/Socket.swift new file mode 100644 index 0000000..8e7c7b1 --- /dev/null +++ b/Sources/dgram/Socket.swift @@ -0,0 +1,333 @@ +// +// Socket.swift +// Noze.io +// +// Created by Helge Heß on 4/17/16. +// Copyright © 2016 ZeeZide GmbH. All rights reserved. +// + +import Dispatch +import xsys +import core +import events +import fs +import net + +public typealias Datagram = ( data: [UInt8], peer: SocketAddress ) + +public typealias MessageCB = ( Datagram ) -> Void +public typealias SocketEventCB = ( Socket ) -> Void + +/// TODO: doc +open class Socket : ErrorEmitter, LameLogObjectType { + public let log : Logger + public var fd : FileDescriptor? = nil // fd can be invalid too + public var address : sockaddr_any? = nil + public var receiveSource: DispatchSourceProtocol? = nil + public let Q : DispatchQueue + public var didRetainQ : Bool = false // #linux-public + + public init(queue : DispatchQueue = core.Q, + enableLogger : Bool = false) + { + self.Q = queue + self.log = Logger(enabled: enableLogger) + + super.init() + + log.onAfterEnter = { [unowned self] log in self.logState() } + log.onBeforeLeave = { [unowned self] log in self.logState() } + } + deinit { + self._primaryClose() + + if self.didRetainQ { + core.module.release() + self.didRetainQ = false + } + } + + + // MARK: - Binding + + @discardableResult + open func bind(_ port : Int? = nil, + exclusive : Bool = false, + onListening : SocketEventCB? = nil) -> Self + { + if let cb = onListening { _ = self.onListening(handler: cb) } + + // TODO: How to decide between IPv4 and IPv6? Node says it's using v6 when + // available. + let address = xsys_sockaddr_in(port: port) + + return self.bind(address, exclusive: exclusive) + } + + @discardableResult + public func bind(_ address: sockaddr_any, + exclusive: Bool = false) -> Self { + switch address { + case .AF_INET (let addr): return bind(addr, exclusive: exclusive) + case .AF_INET6(let addr): return bind(addr, exclusive: exclusive) + case .AF_LOCAL(let addr): return bind(addr, exclusive: exclusive) + } + } + + @discardableResult + public func bind(_ address: AT, + exclusive: Bool = false) -> Self + { + // Note: Everything here runs synchronously, which I guess is fine in this + // specific case? + // TBD: We could dispatch it, but is it worth? Maybe. More stuff could be + // going on (connections to watchdogs etc). + let log = self.log + log.enter(); defer { log.leave() } + log.debug(" address: \(address)") + + + // setup socket if necessary + + if fd == nil { + let rc = _setupSocket(domain: AT.domain) + guard rc == 0 else { return caught(error: xsys.errno) } // TODO: better err + } + assert(fd?.isValid ?? false) + + + // set SO_REUSEADDR + if !exclusive { + let rc = _makeNonExclusive(fd: fd!) + guard rc == 0 else { return caught(error: xsys.errno) } // TODO: better err + } + + // bind socket + + let brc = _bind(address: address) + guard brc == 0 else { return caught(error: xsys.errno) } // TODO: better err + + + // determine the address we bound to + + let boundAddress : AT? = getasockname(fd: fd!.fd, xsys.getsockname) + self.address = sockaddr_any(boundAddress) + assert(self.address != nil) + + + // setup GCD source + + let receiveSource = DispatchSource.makeReadSource(fileDescriptor: fd!.fd, + queue: self.Q) + self.receiveSource = receiveSource + if !self.didRetainQ { core.module.retain() } + + + // setup GCD callback + // Note: This creates a retain-cycle, which is kinda the right thing to do + // (Socket object should not go away until the dispatch source is + // active? Or should it go away an drop the source properly) + // In other words: the server only goes away if it is closed. + receiveSource.setEventHandler { + self._onMessage(address: boundAddress) + } + receiveSource.resume() + + // make non-blocking + fd!.isNonBlocking = true + + + // finish up + + nextTick { + self.listeningListeners.emit(self) + } + return self + } + + // MARK: - Accepting + + public func _onMessage(address localAddress: AT?) { + // #linux-public + // This is cheating a little, we pass in the localAddress to capture the + // generic socket type (which matches the one returned by accept(). + log.enter(); defer { log.leave() } + + let (err, buf, peer) = recvfrom(self.fd!, likeAddress: localAddress) + if let err = err { + self.caught(error: err) + } else { + self.messageListeners.emit((buf!, peer!)) + } + } + + // MARK: - Binding + + public func _bind(address a: AT) -> Int32 { // #linux-public + var address = a + return withUnsafePointer(to: &address) { ptr -> Int32 in + return ptr.withMemoryRebound(to: xsys_sockaddr.self, capacity: 1) { + bptr in + return xsys.bind(fd!.fd, bptr, socklen_t(address.len)) + } + } + } + + + // MARK: - sending + + // TODO: node's completion callback -- not sure how important it is for + // a transport without delivery guarantee, but would be nice to have + public func send(_ data: [UInt8], to peer: SocketAddress) { + if let err = sendto(self.fd!, data: data, to: peer) { + // TODO: I couldn't get write sources to work properly yet... + // So if the socket send buffer is full, we just discard UDP + // packets. + // + // But suppose the write source issue was solvable -- would we + // really want to buffer outgoing UDP packets? + if err.isWouldBlockError { + log.enter(); defer { log.leave() } + log.debug("sendto(2): \(err)") + } else { + self.caught(error: err) + } + } + } + + // MARK: - Reuse server socket + + public func _makeNonExclusive(fd lfd: FileDescriptor) -> Int32 { // #linux-public + var buf = Int32(1) + let buflen = socklen_t(MemoryLayout.stride) + var rc = xsys.setsockopt(lfd.fd, xsys.SOL_SOCKET, xsys.SO_REUSEADDR, + &buf, buflen) + if rc == 0 { + // Needed on Darwin + rc = xsys.setsockopt(lfd.fd, xsys.SOL_SOCKET, xsys.SO_REUSEPORT, + &buf, buflen) + } + return rc + } + + // MARK: - Closing the socket + + open func close() { + _close() + } + + public func _primaryClose() { // #linux-public + if receiveSource != nil { + receiveSource!.cancel() + receiveSource = nil + } + + if let fd = self.fd { + fd.close() + self.fd = nil + } + self.address = nil + } + + public func _close() { // #linux-public + log.enter(); defer { log.leave() } + + _primaryClose() + + // notify close listeners + nextTick { + self.closeListeners.emit(self) + self.listeningListeners.removeAllListeners() + + if self.didRetainQ { + core.module.release() + self.didRetainQ = false + } + } + } + + + // MARK: - Create socket + + public func _setupSocket(domain d: Int32, type: Int32 = xsys.SOCK_DGRAM) + -> Int32 // #linux-public + { + assert(fd == nil) + + let sockfd = xsys.socket(d, type, 0) + log.debug("setup socket: \(sockfd)") + guard sockfd != -1 else { + log.debug(" failed: \(xsys.errno)") + return xsys.errno + } + + fd = FileDescriptor(sockfd) + log.debug(" FD: \(fd)") + return 0 + } + + + // MARK: - Events + + public var closeListeners = EventOnceListenerSet() + public var listeningListeners = EventListenerSet() + public var messageListeners = EventListenerSet() + + @discardableResult + public func onClose (handler cb: @escaping SocketEventCB) -> Self { + closeListeners.add(handler: cb); return self + } + @discardableResult + public func onceClose(handler cb: @escaping SocketEventCB) -> Self { + closeListeners.add(handler: cb, once: true); return self + } + + @discardableResult + public func onListening(handler cb: @escaping SocketEventCB) -> Self + { + listeningListeners.add(handler: cb); return self + } + @discardableResult + public func onceListening(handler cb: @escaping SocketEventCB) -> Self + { + listeningListeners.add(handler: cb, once: true); return self + } + + @discardableResult + public func onMessage(handler cb: @escaping MessageCB) -> Self { + messageListeners.add(handler: cb); return self + } + @discardableResult + public func onceMessage(handler cb: @escaping MessageCB) -> Self { + messageListeners.add(handler: cb, once: true); return self + } + + // MARK: - ErrorEmitter + + public func caught(error e: Error) { // #linux-public + log.enter(); defer { log.leave() } + self.errorListeners.emit(e) + } + + public func caught(error e: Int32, close: Bool = true) -> Self { + // #linux-public + caught(error: POSIXErrorCode(rawValue: e)!) + if close { _close() } + return self + } + + + // MARK: - Logging + + open var logStateInfo : String { + var s = "" + if let address = address { s += " \(address)" } + if let fd = fd { s += " fd=\(fd.fd)" } + return s + } + + open func logState() { + guard log.enabled else { return } + log.debug("[\(logStateInfo)]") + } +} diff --git a/Sources/fs/FileDescriptor.swift b/Sources/fs/FileDescriptor.swift index cb45bd4..2298990 100644 --- a/Sources/fs/FileDescriptor.swift +++ b/Sources/fs/FileDescriptor.swift @@ -74,7 +74,7 @@ public struct FileDescriptor: buf = Array(buf[0..(buffer: [ T ], count: Int = -1) -> ( Error?, Int ) { diff --git a/Sources/net/Util.swift b/Sources/net/Util.swift index 12447dd..b9c8301 100644 --- a/Sources/net/Util.swift +++ b/Sources/net/Util.swift @@ -15,7 +15,7 @@ typealias GetNameFN = ( Int32, UnsafeMutablePointer, UnsafeMutablePointer) -> Int32 // TBD: -func getasockname(fd: Int32, _ nfn: GetNameFN) -> T? { +public func getasockname(fd: Int32, _ nfn: GetNameFN) -> T? { // FIXME: tried to encapsulate this in a sockaddrbuf which does all the // ptr handling, but it ain't work (autoreleasepool issue?) var baddr = T() diff --git a/Sources/xsys/POSIXError.swift b/Sources/xsys/POSIXError.swift index 3607c2c..6692f1f 100644 --- a/Sources/xsys/POSIXError.swift +++ b/Sources/xsys/POSIXError.swift @@ -55,6 +55,7 @@ case EPIPE = 32 case EDOM = 33 case ERANGE = 34 + case EADDRINUSE = 98 // extra case ECANCELED = 125 diff --git a/Sources/xsys/fd.swift b/Sources/xsys/fd.swift index 558f345..e4f9186 100644 --- a/Sources/xsys/fd.swift +++ b/Sources/xsys/fd.swift @@ -15,6 +15,8 @@ public typealias xsysOpenType = (UnsafePointer, CInt) -> CInt public let close = Glibc.close public let read = Glibc.read public let write = Glibc.write + public let recvfrom = Glibc.recvfrom + public let sendto = Glibc.sendto public let access = Glibc.access public let F_OK = Glibc.F_OK @@ -43,6 +45,8 @@ public typealias xsysOpenType = (UnsafePointer, CInt) -> CInt public let close = Darwin.close public let read = Darwin.read public let write = Darwin.write + public let recvfrom = Darwin.recvfrom + public let sendto = Darwin.sendto public let access = Darwin.access public let F_OK = Darwin.F_OK diff --git a/Sources/xsys/socket.swift b/Sources/xsys/socket.swift index 4d76061..9a580ec 100644 --- a/Sources/xsys/socket.swift +++ b/Sources/xsys/socket.swift @@ -38,6 +38,7 @@ public let PF_UNSPEC = Glibc.PF_UNSPEC public let SOL_SOCKET = Glibc.SOL_SOCKET public let SO_REUSEADDR = Glibc.SO_REUSEADDR + public let SO_REUSEPORT = Glibc.SO_REUSEPORT // using an exact alias gives issues with sizeof() public typealias xsys_sockaddr = Glibc.sockaddr @@ -79,6 +80,7 @@ public let PF_UNSPEC = Darwin.PF_UNSPEC public let SOL_SOCKET = Darwin.SOL_SOCKET public let SO_REUSEADDR = Darwin.SO_REUSEADDR + public let SO_REUSEPORT = Darwin.SO_REUSEPORT // using an exact alias gives issues with sizeof() public typealias xsys_sockaddr = Darwin.sockaddr diff --git a/Tests/GNUmakefile b/Tests/GNUmakefile index e3f306b..8824f5b 100644 --- a/Tests/GNUmakefile +++ b/Tests/GNUmakefile @@ -11,7 +11,7 @@ all : copy-nozeio-testcase-master @make -C $(PACKAGE_DIR) $@ copy-nozeio-testcase-master : - for i in child_processTests dnsTests fsTests httpTests jsonTests netTests cryptoTests; do \ + for i in child_processTests dnsTests dgramTests fsTests httpTests jsonTests netTests cryptoTests; do \ cp streamsTests/NozeIOTestCase.swift $$i; \ done diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index 78ab3d9..36b8354 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -4,12 +4,14 @@ let testStreams = true let testFS = true let testDNS = true let testHTTP = true +let testDGRAM = true @testable import streamsTests @testable import leftpadTests @testable import fsTests @testable import dnsTests @testable import httpTests +@testable import dgramTests var tests = [ // leftpad testCase(NozeIOLeftPadTests.allTests), @@ -38,5 +40,9 @@ tests += testHTTP ? [ // http testCase(NozeIOHttpClientTests.allTests), ] : [] +tests += testDGRAM ? [ // dgram + testCase(NozeIODgramTests.allTests), +] : [] + XCTMain(tests) diff --git a/Tests/dgramTests/DgramTests.swift b/Tests/dgramTests/DgramTests.swift new file mode 100644 index 0000000..abd1676 --- /dev/null +++ b/Tests/dgramTests/DgramTests.swift @@ -0,0 +1,127 @@ +// +// DgramTests.swift +// NozeIO +// +// Created by Helge Hess on 19/04/16. +// Copyright © 2016 ZeeZide GmbH. All rights reserved. +// + +import XCTest + +import Foundation +import core +import xsys +@testable import net +@testable import dgram + +class NozeIODgramTests: NozeIOTestCase { + + func testRequestResponse() { + enableRunLoop() + + let server = dgram.createSocket() + let client = dgram.createSocket() + + server + .onListening { sock in + XCTAssertTrue(server === sock) + switch server.address! { + case let .AF_INET(address): + client + .onError { err in XCTAssertNil(err) } + .onListening { sock in + XCTAssertTrue(client === sock) + client.send([UInt8]("hello noze".utf8), to: address) + } + .onMessage { (resp, from) in + if let decoded = String( + data: Data(resp), encoding: .utf8) { + XCTAssertEqual(decoded, "HELLO NOZE") + self.exitIfDone() + } + } + .bind(0) + case _: + XCTAssertTrue(false) + } + } + .onError { err in XCTAssertNil(err) } + .onMessage { (req, from) in + if let decoded = String(data: Data(req), encoding: .utf8) { + server.send([UInt8](decoded.uppercased().utf8), to: from) + } + } + .bind() + + waitForExit() + } + + func testWellKnownPort() { + enableRunLoop() + + dgram.createSocket() + .onListening { server in + switch server.address! { + case let .AF_INET(address): + XCTAssertEqual(address.port, 10000) + self.exitIfDone() + case _: + XCTAssertTrue(false) + } + } + .bind(10000) + + waitForExit() + } + + func testReuse() { + enableRunLoop() + + var counter = 0 + let count = { (server: dgram.Socket) in + counter += 1 + XCTAssertTrue(counter <= 2) + if counter == 2 { + self.exitIfDone() + } + } + + dgram.createSocket().onListening(handler: count).bind(10000) + dgram.createSocket().onListening(handler: count).bind(10000) + + waitForExit() + } + + func testNoReuse() { + enableRunLoop() + + var counter = 0 + let count = { (server: dgram.Socket) in + counter += 1 + XCTAssertTrue(counter <= 1) + } + + dgram.createSocket().onListening(handler: count).bind(10000) + dgram.createSocket() + .onListening(handler: count) + .onError { err in + XCTAssertEqual(err as? POSIXErrorCode, + POSIXErrorCode.EADDRINUSE) + self.exitIfDone() + } + .bind(10000, exclusive: true) + + waitForExit() + } + + #if os(Linux) + static var allTests = { + return [ + ( "testRequestResponse", testRequestResponse ), + ( "testWellKnownPort", testWellKnownPort ), + ( "testReuse", testReuse ), + ( "testNoReuse", testNoReuse ), + ] + }() + #endif +} diff --git a/Tests/dgramTests/GNUmakefile b/Tests/dgramTests/GNUmakefile new file mode 100644 index 0000000..cb0672d --- /dev/null +++ b/Tests/dgramTests/GNUmakefile @@ -0,0 +1,10 @@ +# GNUmakefile + +PACKAGE_DIR=../.. +PACKAGE=$(notdir $(shell pwd)) +$(PACKAGE)_TYPE = testsuite + +$(PACKAGE)_SWIFT_MODULES = net + +include $(PACKAGE_DIR)/xcconfig/config.make +include $(PACKAGE_DIR)/xcconfig/rules.make diff --git a/Tests/dgramTests/NozeIOTestCase.swift b/Tests/dgramTests/NozeIOTestCase.swift new file mode 100644 index 0000000..4812b98 --- /dev/null +++ b/Tests/dgramTests/NozeIOTestCase.swift @@ -0,0 +1,102 @@ +// +// NozeIOTestCase.swift +// NozeIO +// +// Created by Helge Hess on 30/03/16. +// Copyright © 2016 ZeeZide GmbH. All rights reserved. +// + +import XCTest +import core + +struct StdErrStream : TextOutputStream { + mutating func write(_ string: String) { + fputs(string, stderr) + } +} + +var nzStdErr = StdErrStream() + +public class NozeIOTestCase : XCTestCase { + + private var wantsRunloop = 0 + + public override func setUp() { + super.setUp() + + // the test is running on the main-queue, so we need to run Noze on a + // secondary queue. + core.Q = DispatchQueue(label: "de.zeezide.noze.testqueue") + core.disableAtExitHandler() // we do not want atexit here + core.module.exitFunction = { code in + XCTAssert(code == 0) + if self.wantsRunloop > 0 { + print("WARN: test queue is not done yet?: #\(self.wantsRunloop)") + // FIXME + // XCTAssert(self.wantsRunloop == 0) + } + } + } + + public override func tearDown() { + super.tearDown() + } + + + // MARK: - Global Helper Funcs + + var done = DispatchSemaphore(value: 0) + + public func enableRunLoop() { + self.wantsRunloop += 1 + } + + static let defaultWaitTimeoutInSecs = 10 + + public func waitForExit(timeoutInMS to: Int = + defaultWaitTimeoutInSecs * 1000) + { + let timeout = DispatchTime.now() + DispatchTimeInterval.milliseconds(to) + + let rc = done.wait(timeout: timeout) + let didTimeout = rc == .timedOut + if didTimeout { + // not done in time + XCTAssert(false, "hit async queue timeout!") + } + } + + public func exitIfDone(code: Int32 = 42) { + wantsRunloop -= 1 + if wantsRunloop < 1 { + //exit(code) + done.signal() + // this lets the waitForExit finish + } + } + + public func inRunloop(cb: (@escaping () -> Void) -> Void) { + enableRunLoop() + cb( { self.exitIfDone() } ) + waitForExit() + } +} + +// MARK: - Global Helper Funcs +// Note: those are not part of the class to avoid 'self' capture warnings. + +#if os(Linux) || os(Android) || os(FreeBSD) + import Glibc +#endif + +// 'flush' print +public func fprint(_ value: T) { + fflush(stdout) + print(value) + fflush(stdout) +} +public func efprint(_ value: T) { + fflush(stderr) + print(value, to:&nzStdErr) + fflush(stderr) +} From 2219b9d4a676a979d0e1ecf02b6cbd48926845d9 Mon Sep 17 00:00:00 2001 From: Helge Hess Date: Mon, 20 Mar 2017 13:49:54 +0100 Subject: [PATCH 02/16] Setup dgram module in Xcode ... puh, this is quite a bit of work :-) - add target for: - shared library - test - demo tool - drop all settings in those targets (vs xcconfig) - add files, setup dependencies - lib needs core, events, streams, even fs --- Noze.io.xcodeproj/project.pbxproj | 384 +++++++++++++++++++++++++++++- 1 file changed, 383 insertions(+), 1 deletion(-) diff --git a/Noze.io.xcodeproj/project.pbxproj b/Noze.io.xcodeproj/project.pbxproj index e2bdfbc..7395f1c 100644 --- a/Noze.io.xcodeproj/project.pbxproj +++ b/Noze.io.xcodeproj/project.pbxproj @@ -26,6 +26,7 @@ E807E4011CF671C500AE317B /* PBXTargetDependency */, E807E4031CF671C500AE317B /* PBXTargetDependency */, E807E4051CF671C500AE317B /* PBXTargetDependency */, + E8AF01941E8004770074266E /* PBXTargetDependency */, E807E4071CF671C500AE317B /* PBXTargetDependency */, E807E4091CF671C500AE317B /* PBXTargetDependency */, E807E40B1CF671C500AE317B /* PBXTargetDependency */, @@ -52,6 +53,7 @@ E8B8BB221CFBCCBA00BA30D3 /* PBXTargetDependency */, E8B8BB241CFBCCBA00BA30D3 /* PBXTargetDependency */, E8B8BB261CFBCCBA00BA30D3 /* PBXTargetDependency */, + E8AF01A91E8005A90074266E /* PBXTargetDependency */, E8B8BB281CFBCCBA00BA30D3 /* PBXTargetDependency */, E8B8BB2A1CFBCCBA00BA30D3 /* PBXTargetDependency */, E8E8FF161D441059002663CD /* PBXTargetDependency */, @@ -251,6 +253,21 @@ E8A249DF1D20BB1A0060346B /* UniqueRandomArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8A249DE1D20BB1A0060346B /* UniqueRandomArray.swift */; }; E8A249E01D20BD0A0060346B /* libcows.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E8EED0F11D206738007BE73B /* libcows.dylib */; }; E8A936381D40FC0500BB6DE1 /* timeval_any.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8A936371D40FC0500BB6DE1 /* timeval_any.swift */; }; + E8AF01971E8004B40074266E /* DgramTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8AF01961E8004B40074266E /* DgramTests.swift */; }; + E8AF01981E8004BF0074266E /* libdgram.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E8AF018D1E8003B10074266E /* libdgram.dylib */; }; + E8AF01A71E8005130074266E /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8AF01A61E8005130074266E /* main.swift */; }; + E8AF01B21E8005EE0074266E /* libcore.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E8E2756D1CF49E3A004A6A34 /* libcore.dylib */; }; + E8AF01B31E8005EE0074266E /* libdgram.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E8AF018D1E8003B10074266E /* libdgram.dylib */; }; + E8AF01B41E8005EE0074266E /* libnet.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E8E276131CF49F52004A6A34 /* libnet.dylib */; }; + E8AF01B51E8005EE0074266E /* libxsys.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E8E275561CF49E06004A6A34 /* libxsys.dylib */; }; + E8AF01BE1E80064C0074266E /* libnet.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E8E276131CF49F52004A6A34 /* libnet.dylib */; }; + E8AF01BF1E80064C0074266E /* libxsys.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E8E275561CF49E06004A6A34 /* libxsys.dylib */; }; + E8AF01C51E8006850074266E /* Internals.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8AF01C21E8006850074266E /* Internals.swift */; }; + E8AF01C61E8006850074266E /* Module.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8AF01C31E8006850074266E /* Module.swift */; }; + E8AF01C71E8006850074266E /* Socket.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8AF01C41E8006850074266E /* Socket.swift */; }; + E8AF01C81E80070E0074266E /* libcore.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E8E2756D1CF49E3A004A6A34 /* libcore.dylib */; }; + E8AF01C91E8007120074266E /* libevents.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E8E275811CF49E5F004A6A34 /* libevents.dylib */; }; + E8AF01CA1E80071E0074266E /* libfs.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E8E275C11CF49EC3004A6A34 /* libfs.dylib */; }; E8B33EE41CFB487400091806 /* libchild_process.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E8E275F81CF49F29004A6A34 /* libchild_process.dylib */; }; E8B33EF51CFB48A200091806 /* libfs.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E8E275C11CF49EC3004A6A34 /* libfs.dylib */; }; E8B33EFD1CFB490200091806 /* ChildProcessTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8C15F2D1CF3382600CA5517 /* ChildProcessTests.swift */; }; @@ -1041,6 +1058,76 @@ remoteGlobalIDString = E8EED0F01D206738007BE73B; remoteInfo = cows; }; + E8AF01931E8004770074266E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = E835BFA81B4D4CFB00288839 /* Project object */; + proxyType = 1; + remoteGlobalIDString = E8AF018C1E8003B10074266E; + remoteInfo = dgram; + }; + E8AF01A81E8005A90074266E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = E835BFA81B4D4CFB00288839 /* Project object */; + proxyType = 1; + remoteGlobalIDString = E8AF019C1E8004EC0074266E; + remoteInfo = udpd; + }; + E8AF01AA1E8005D70074266E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = E835BFA81B4D4CFB00288839 /* Project object */; + proxyType = 1; + remoteGlobalIDString = E8E275551CF49E06004A6A34; + remoteInfo = xsys; + }; + E8AF01AC1E8005D70074266E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = E835BFA81B4D4CFB00288839 /* Project object */; + proxyType = 1; + remoteGlobalIDString = E8E2756C1CF49E3A004A6A34; + remoteInfo = core; + }; + E8AF01AE1E8005D70074266E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = E835BFA81B4D4CFB00288839 /* Project object */; + proxyType = 1; + remoteGlobalIDString = E8E276121CF49F52004A6A34; + remoteInfo = net; + }; + E8AF01B01E8005D70074266E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = E835BFA81B4D4CFB00288839 /* Project object */; + proxyType = 1; + remoteGlobalIDString = E8AF018C1E8003B10074266E; + remoteInfo = dgram; + }; + E8AF01B61E80063A0074266E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = E835BFA81B4D4CFB00288839 /* Project object */; + proxyType = 1; + remoteGlobalIDString = E8E275551CF49E06004A6A34; + remoteInfo = xsys; + }; + E8AF01B81E80063A0074266E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = E835BFA81B4D4CFB00288839 /* Project object */; + proxyType = 1; + remoteGlobalIDString = E8E2756C1CF49E3A004A6A34; + remoteInfo = core; + }; + E8AF01BA1E80063A0074266E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = E835BFA81B4D4CFB00288839 /* Project object */; + proxyType = 1; + remoteGlobalIDString = E8E2758E1CF49E74004A6A34; + remoteInfo = streams; + }; + E8AF01BC1E80063A0074266E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = E835BFA81B4D4CFB00288839 /* Project object */; + proxyType = 1; + remoteGlobalIDString = E8E276121CF49F52004A6A34; + remoteInfo = net; + }; E8B33EE51CFB487400091806 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = E835BFA81B4D4CFB00288839 /* Project object */; @@ -1645,6 +1732,15 @@ ); runOnlyForDeploymentPostprocessing = 1; }; + E8AF019B1E8004EC0074266E /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; E8B3724D1CFB3ACF005E09DD /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -1859,6 +1955,13 @@ E8A249DE1D20BB1A0060346B /* UniqueRandomArray.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = UniqueRandomArray.swift; path = Sources/cows/UniqueRandomArray.swift; sourceTree = ""; }; E8A936371D40FC0500BB6DE1 /* timeval_any.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = timeval_any.swift; path = Sources/xsys/timeval_any.swift; sourceTree = SOURCE_ROOT; }; E8AD60811CD243AE00B67236 /* Console.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Console.swift; path = Sources/console/Console.swift; sourceTree = SOURCE_ROOT; }; + E8AF018D1E8003B10074266E /* libdgram.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libdgram.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; + E8AF01961E8004B40074266E /* DgramTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DgramTests.swift; path = Tests/dgramTests/DgramTests.swift; sourceTree = SOURCE_ROOT; }; + E8AF019D1E8004EC0074266E /* udpd */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = udpd; sourceTree = BUILT_PRODUCTS_DIR; }; + E8AF01A61E8005130074266E /* main.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = main.swift; path = Samples/udpd/main.swift; sourceTree = SOURCE_ROOT; }; + E8AF01C21E8006850074266E /* Internals.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Internals.swift; sourceTree = ""; }; + E8AF01C31E8006850074266E /* Module.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Module.swift; sourceTree = ""; }; + E8AF01C41E8006850074266E /* Socket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Socket.swift; sourceTree = ""; }; E8B33EDF1CFB487400091806 /* NozeChildProcessTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NozeChildProcessTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; E8B33EF01CFB48A200091806 /* NozeFSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NozeFSTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; E8B3724F1CFB3ACF005E09DD /* httpd-static */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "httpd-static"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -2084,6 +2187,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + E8AF01981E8004BF0074266E /* libdgram.dylib in Frameworks */, E8EA87CE1CF73A8A00406C27 /* libbase64.dylib in Frameworks */, E807E4141CF676C200AE317B /* libFreddy.dylib in Frameworks */, E8E2773B1CF4A6AF004A6A34 /* libchild_process.dylib in Frameworks */, @@ -2291,6 +2395,29 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + E8AF018A1E8003B10074266E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + E8AF01CA1E80071E0074266E /* libfs.dylib in Frameworks */, + E8AF01C91E8007120074266E /* libevents.dylib in Frameworks */, + E8AF01C81E80070E0074266E /* libcore.dylib in Frameworks */, + E8AF01BE1E80064C0074266E /* libnet.dylib in Frameworks */, + E8AF01BF1E80064C0074266E /* libxsys.dylib in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + E8AF019A1E8004EC0074266E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + E8AF01B21E8005EE0074266E /* libcore.dylib in Frameworks */, + E8AF01B31E8005EE0074266E /* libdgram.dylib in Frameworks */, + E8AF01B41E8005EE0074266E /* libnet.dylib in Frameworks */, + E8AF01B51E8005EE0074266E /* libxsys.dylib in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; E8B33EDC1CFB487400091806 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -2685,6 +2812,7 @@ E80448651CD10FFB00447204 /* child_process */, E84F40E41CBBE09900544DE5 /* dns */, E8CA33F31CBA54600019DECC /* net */, + E8AF01C11E8006850074266E /* dgram */, E8CA33D41CB16DB50019DECC /* process */, E8CA33581CB12A960019DECC /* http */, E8DE16DF1CC90DBB001140DD /* leftpad */, @@ -2801,6 +2929,8 @@ E8978ED91D8A18DE00ED4558 /* libCryptoSwift.dylib */, E8978F041D8A1F8700ED4558 /* libcrypto.dylib */, E8978F261D8A260D00ED4558 /* NozeCryptoTests.xctest */, + E8AF018D1E8003B10074266E /* libdgram.dylib */, + E8AF019D1E8004EC0074266E /* udpd */, ); name = Products; sourceTree = ""; @@ -2814,6 +2944,7 @@ E8C15F391CF3382600CA5517 /* leftpad */, E8C90C071CFE71F600F85436 /* mustache */, E8C15F401CF3382600CA5517 /* streams */, + E8AF01951E8004A70074266E /* dgram */, E8C15F2E1CF3382600CA5517 /* fs */, E8C15F2C1CF3382600CA5517 /* child_process */, E86BC6F41CF3AE8B00A397BE /* dns */, @@ -2932,6 +3063,7 @@ E87BE8D21CF50D8D00C20AE7 /* call-git */, E8EED1101D207257007BE73B /* cows2code */, E87BE8D51CF50DAB00C20AE7 /* miniirc */, + E8AF019E1E8004EC0074266E /* udpd */, E87BE8D41CF50DA000C20AE7 /* httpd-helloworld */, E8B372501CFB3ACF005E09DD /* httpd-static */, E874ECA71D119C6000FA0606 /* httpd-cookies */, @@ -3113,6 +3245,33 @@ name = "Callback Based"; sourceTree = ""; }; + E8AF01951E8004A70074266E /* dgram */ = { + isa = PBXGroup; + children = ( + E8AF01961E8004B40074266E /* DgramTests.swift */, + ); + name = dgram; + sourceTree = ""; + }; + E8AF019E1E8004EC0074266E /* udpd */ = { + isa = PBXGroup; + children = ( + E8AF01A61E8005130074266E /* main.swift */, + ); + path = udpd; + sourceTree = ""; + }; + E8AF01C11E8006850074266E /* dgram */ = { + isa = PBXGroup; + children = ( + E8AF01C31E8006850074266E /* Module.swift */, + E8AF01C21E8006850074266E /* Internals.swift */, + E8AF01C41E8006850074266E /* Socket.swift */, + ); + name = dgram; + path = Sources/dgram; + sourceTree = ""; + }; E8B372501CFB3ACF005E09DD /* httpd-static */ = { isa = PBXGroup; children = ( @@ -3658,6 +3817,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + E8AF018B1E8003B10074266E /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; E8C90BE21CFE6FFE00F85436 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; @@ -4133,6 +4299,48 @@ productReference = E89CDF401D005B99008861AF /* express-simple */; productType = "com.apple.product-type.tool"; }; + E8AF018C1E8003B10074266E /* dgram */ = { + isa = PBXNativeTarget; + buildConfigurationList = E8AF01921E8003B10074266E /* Build configuration list for PBXNativeTarget "dgram" */; + buildPhases = ( + E8AF01891E8003B10074266E /* Sources */, + E8AF018A1E8003B10074266E /* Frameworks */, + E8AF018B1E8003B10074266E /* Headers */, + ); + buildRules = ( + ); + dependencies = ( + E8AF01B71E80063A0074266E /* PBXTargetDependency */, + E8AF01B91E80063A0074266E /* PBXTargetDependency */, + E8AF01BB1E80063A0074266E /* PBXTargetDependency */, + E8AF01BD1E80063A0074266E /* PBXTargetDependency */, + ); + name = dgram; + productName = dgram; + productReference = E8AF018D1E8003B10074266E /* libdgram.dylib */; + productType = "com.apple.product-type.library.dynamic"; + }; + E8AF019C1E8004EC0074266E /* udpd */ = { + isa = PBXNativeTarget; + buildConfigurationList = E8AF01A11E8004EC0074266E /* Build configuration list for PBXNativeTarget "udpd" */; + buildPhases = ( + E8AF01991E8004EC0074266E /* Sources */, + E8AF019A1E8004EC0074266E /* Frameworks */, + E8AF019B1E8004EC0074266E /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + E8AF01AB1E8005D70074266E /* PBXTargetDependency */, + E8AF01AD1E8005D70074266E /* PBXTargetDependency */, + E8AF01AF1E8005D70074266E /* PBXTargetDependency */, + E8AF01B11E8005D70074266E /* PBXTargetDependency */, + ); + name = udpd; + productName = udpd; + productReference = E8AF019D1E8004EC0074266E /* udpd */; + productType = "com.apple.product-type.tool"; + }; E8B33EDE1CFB487400091806 /* NozeChildProcessTests */ = { isa = PBXNativeTarget; buildConfigurationList = E8B33EEB1CFB487400091806 /* Build configuration list for PBXNativeTarget "NozeChildProcessTests" */; @@ -4691,7 +4899,7 @@ E835BFA81B4D4CFB00288839 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 0800; + LastSwiftUpdateCheck = 0820; LastUpgradeCheck = 0800; ORGANIZATIONNAME = "ZeeZide GmbH"; TargetAttributes = { @@ -4763,6 +4971,15 @@ CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 0800; }; + E8AF018C1E8003B10074266E = { + CreatedOnToolsVersion = 8.2.1; + ProvisioningStyle = Automatic; + }; + E8AF019C1E8004EC0074266E = { + CreatedOnToolsVersion = 8.2.1; + LastSwiftMigration = 0820; + ProvisioningStyle = Automatic; + }; E8B33EDE1CFB487400091806 = { CreatedOnToolsVersion = 7.3; }; @@ -4918,6 +5135,7 @@ E8E275F71CF49F29004A6A34 /* child_process */, E8E276061CF49F3B004A6A34 /* dns */, E8E276121CF49F52004A6A34 /* net */, + E8AF018C1E8003B10074266E /* dgram */, E8E276231CF49F67004A6A34 /* process */, E8E276331CF49F7D004A6A34 /* http */, E8E2764B1CF49F91004A6A34 /* leftpad */, @@ -4938,6 +5156,7 @@ E87BE8F61CF50F9900C20AE7 /* call-git */, E8EED1061D207246007BE73B /* cows2code */, E87BE9031CF50FA900C20AE7 /* miniirc */, + E8AF019C1E8004EC0074266E /* udpd */, E87BE9101CF50FB700C20AE7 /* httpd-helloworld */, E8B3724E1CFB3ACF005E09DD /* httpd-static */, E874EC9D1D119C2B00FA0606 /* httpd-cookies */, @@ -5048,6 +5267,7 @@ E8C15F5B1CF3382600CA5517 /* StringDecoderTests.swift in Sources */, E8EA87CD1CF73A7E00406C27 /* Base64Tests.swift in Sources */, E86BC6F61CF3AEA200A397BE /* DNSTests.swift in Sources */, + E8AF01971E8004B40074266E /* DgramTests.swift in Sources */, 4915989E1DC10E0600DB1C88 /* EventTests.swift in Sources */, E8C15F531CF3382600CA5517 /* ServerTests.swift in Sources */, E8C15F541CF3382600CA5517 /* SocketTests.swift in Sources */, @@ -5209,6 +5429,24 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + E8AF01891E8003B10074266E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + E8AF01C51E8006850074266E /* Internals.swift in Sources */, + E8AF01C61E8006850074266E /* Module.swift in Sources */, + E8AF01C71E8006850074266E /* Socket.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + E8AF01991E8004EC0074266E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + E8AF01A71E8005130074266E /* main.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; E8B33EDB1CFB487400091806 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -5952,6 +6190,56 @@ target = E8EED0F01D206738007BE73B /* cows */; targetProxy = E8A249E11D20BD120060346B /* PBXContainerItemProxy */; }; + E8AF01941E8004770074266E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = E8AF018C1E8003B10074266E /* dgram */; + targetProxy = E8AF01931E8004770074266E /* PBXContainerItemProxy */; + }; + E8AF01A91E8005A90074266E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = E8AF019C1E8004EC0074266E /* udpd */; + targetProxy = E8AF01A81E8005A90074266E /* PBXContainerItemProxy */; + }; + E8AF01AB1E8005D70074266E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = E8E275551CF49E06004A6A34 /* xsys */; + targetProxy = E8AF01AA1E8005D70074266E /* PBXContainerItemProxy */; + }; + E8AF01AD1E8005D70074266E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = E8E2756C1CF49E3A004A6A34 /* core */; + targetProxy = E8AF01AC1E8005D70074266E /* PBXContainerItemProxy */; + }; + E8AF01AF1E8005D70074266E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = E8E276121CF49F52004A6A34 /* net */; + targetProxy = E8AF01AE1E8005D70074266E /* PBXContainerItemProxy */; + }; + E8AF01B11E8005D70074266E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = E8AF018C1E8003B10074266E /* dgram */; + targetProxy = E8AF01B01E8005D70074266E /* PBXContainerItemProxy */; + }; + E8AF01B71E80063A0074266E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = E8E275551CF49E06004A6A34 /* xsys */; + targetProxy = E8AF01B61E80063A0074266E /* PBXContainerItemProxy */; + }; + E8AF01B91E80063A0074266E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = E8E2756C1CF49E3A004A6A34 /* core */; + targetProxy = E8AF01B81E80063A0074266E /* PBXContainerItemProxy */; + }; + E8AF01BB1E80063A0074266E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = E8E2758E1CF49E74004A6A34 /* streams */; + targetProxy = E8AF01BA1E80063A0074266E /* PBXContainerItemProxy */; + }; + E8AF01BD1E80063A0074266E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = E8E276121CF49F52004A6A34 /* net */; + targetProxy = E8AF01BC1E80063A0074266E /* PBXContainerItemProxy */; + }; E8B33EE61CFB487400091806 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = E8E275F71CF49F29004A6A34 /* child_process */; @@ -7384,6 +7672,78 @@ }; name = UIKitRelease; }; + E8AF018E1E8003B10074266E /* AppKitDebug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E8D38D421CB19DA9009C7B57 /* SwiftModuleFW.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + EXECUTABLE_PREFIX = lib; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = AppKitDebug; + }; + E8AF018F1E8003B10074266E /* UIKitDebug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E8D38D421CB19DA9009C7B57 /* SwiftModuleFW.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + EXECUTABLE_PREFIX = lib; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = UIKitDebug; + }; + E8AF01901E8003B10074266E /* AppKitRelease */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E8D38D421CB19DA9009C7B57 /* SwiftModuleFW.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + EXECUTABLE_PREFIX = lib; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = AppKitRelease; + }; + E8AF01911E8003B10074266E /* UIKitRelease */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E8D38D421CB19DA9009C7B57 /* SwiftModuleFW.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + EXECUTABLE_PREFIX = lib; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = UIKitRelease; + }; + E8AF01A21E8004EC0074266E /* AppKitDebug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E87BE8D81CF50E9D00C20AE7 /* SwiftTool.xcconfig */; + buildSettings = { + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = AppKitDebug; + }; + E8AF01A31E8004EC0074266E /* UIKitDebug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E87BE8D81CF50E9D00C20AE7 /* SwiftTool.xcconfig */; + buildSettings = { + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = UIKitDebug; + }; + E8AF01A41E8004EC0074266E /* AppKitRelease */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E87BE8D81CF50E9D00C20AE7 /* SwiftTool.xcconfig */; + buildSettings = { + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = AppKitRelease; + }; + E8AF01A51E8004EC0074266E /* UIKitRelease */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E87BE8D81CF50E9D00C20AE7 /* SwiftTool.xcconfig */; + buildSettings = { + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = UIKitRelease; + }; E8B33EE71CFB487400091806 /* AppKitDebug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -9680,6 +10040,28 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = AppKitDebug; }; + E8AF01921E8003B10074266E /* Build configuration list for PBXNativeTarget "dgram" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E8AF018E1E8003B10074266E /* AppKitDebug */, + E8AF018F1E8003B10074266E /* UIKitDebug */, + E8AF01901E8003B10074266E /* AppKitRelease */, + E8AF01911E8003B10074266E /* UIKitRelease */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = AppKitDebug; + }; + E8AF01A11E8004EC0074266E /* Build configuration list for PBXNativeTarget "udpd" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E8AF01A21E8004EC0074266E /* AppKitDebug */, + E8AF01A31E8004EC0074266E /* UIKitDebug */, + E8AF01A41E8004EC0074266E /* AppKitRelease */, + E8AF01A51E8004EC0074266E /* UIKitRelease */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = AppKitDebug; + }; E8B33EEB1CFB487400091806 /* Build configuration list for PBXNativeTarget "NozeChildProcessTests" */ = { isa = XCConfigurationList; buildConfigurations = ( From 9cf4c9b21757476bfa9676e59bfbe2549e50c278 Mon Sep 17 00:00:00 2001 From: Helge Hess Date: Mon, 20 Mar 2017 15:05:53 +0100 Subject: [PATCH 03/16] Cleanup test tool - use console - replace newlines in output --- Noze.io.xcodeproj/project.pbxproj | 15 +++++++++++++++ Samples/udpd/main.swift | 32 +++++++++++++++++++------------ 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/Noze.io.xcodeproj/project.pbxproj b/Noze.io.xcodeproj/project.pbxproj index 7395f1c..0f5f3b5 100644 --- a/Noze.io.xcodeproj/project.pbxproj +++ b/Noze.io.xcodeproj/project.pbxproj @@ -268,6 +268,7 @@ E8AF01C81E80070E0074266E /* libcore.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E8E2756D1CF49E3A004A6A34 /* libcore.dylib */; }; E8AF01C91E8007120074266E /* libevents.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E8E275811CF49E5F004A6A34 /* libevents.dylib */; }; E8AF01CA1E80071E0074266E /* libfs.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E8E275C11CF49EC3004A6A34 /* libfs.dylib */; }; + E8AF01CB1E80168F0074266E /* libconsole.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E8E275EC1CF49F0C004A6A34 /* libconsole.dylib */; }; E8B33EE41CFB487400091806 /* libchild_process.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E8E275F81CF49F29004A6A34 /* libchild_process.dylib */; }; E8B33EF51CFB48A200091806 /* libfs.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E8E275C11CF49EC3004A6A34 /* libfs.dylib */; }; E8B33EFD1CFB490200091806 /* ChildProcessTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8C15F2D1CF3382600CA5517 /* ChildProcessTests.swift */; }; @@ -1128,6 +1129,13 @@ remoteGlobalIDString = E8E276121CF49F52004A6A34; remoteInfo = net; }; + E8AF01CC1E8016930074266E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = E835BFA81B4D4CFB00288839 /* Project object */; + proxyType = 1; + remoteGlobalIDString = E8E275EB1CF49F0C004A6A34; + remoteInfo = console; + }; E8B33EE51CFB487400091806 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = E835BFA81B4D4CFB00288839 /* Project object */; @@ -2411,6 +2419,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + E8AF01CB1E80168F0074266E /* libconsole.dylib in Frameworks */, E8AF01B21E8005EE0074266E /* libcore.dylib in Frameworks */, E8AF01B31E8005EE0074266E /* libdgram.dylib in Frameworks */, E8AF01B41E8005EE0074266E /* libnet.dylib in Frameworks */, @@ -4331,6 +4340,7 @@ buildRules = ( ); dependencies = ( + E8AF01CD1E8016930074266E /* PBXTargetDependency */, E8AF01AB1E8005D70074266E /* PBXTargetDependency */, E8AF01AD1E8005D70074266E /* PBXTargetDependency */, E8AF01AF1E8005D70074266E /* PBXTargetDependency */, @@ -6240,6 +6250,11 @@ target = E8E276121CF49F52004A6A34 /* net */; targetProxy = E8AF01BC1E80063A0074266E /* PBXContainerItemProxy */; }; + E8AF01CD1E8016930074266E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = E8E275EB1CF49F0C004A6A34 /* console */; + targetProxy = E8AF01CC1E8016930074266E /* PBXContainerItemProxy */; + }; E8B33EE61CFB487400091806 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = E8E275F71CF49F29004A6A34 /* child_process */; diff --git a/Samples/udpd/main.swift b/Samples/udpd/main.swift index 2a30021..49a801f 100644 --- a/Samples/udpd/main.swift +++ b/Samples/udpd/main.swift @@ -1,23 +1,31 @@ // Noze.io UDP server // - to compile in Swift 3 invoke: swift build // - to run result: .build/debug/udpd -// - to try it: nc.openbsd -u 10000 +// - to try it: +// - Linux: nc.openbsd -u 10000 +// - macOS: nc -vu4 localhost 10000 -import Foundation +import Foundation // for String/Data import dgram -import net -import xsys +import console let sock = dgram.createSocket() sock - .onListening { address in print("dgram: bound to \(address)") } - .onError { err in print("error: \(err)") } - .onMessage { (msg, from) in - print("received: \(msg) from \(from)") - if let decoded = String(data: Data(msg), encoding: .utf8) { - print("decoded: \(decoded)") - print("calling send on \(sock) with \([UInt8](decoded.uppercased().utf8))") - sock.send([UInt8](decoded.uppercased().utf8), to: from) + .onListening { address in console.info ("dgram: bound to:", address) } + .onError { err in console.error("error:", err) } + .onMessage { (msg, from) in + console.log("received: \(msg) from \(from)") + + guard let decoded = String(data: Data(msg), encoding: .utf8) else { + console.info("could not decode packet: \(msg)") + return } + + console.log(" decoded:", + decoded.replacingOccurrences(of: "\n", with: "\\n")) + + let packet = [UInt8](decoded.uppercased().utf8) + console.log(" calling send on \(sock) with:", packet) + sock.send(packet, to: from) } .bind(10000) From 442275106384104184edb5bbf58befe3faf838af Mon Sep 17 00:00:00 2001 From: Helge Hess Date: Mon, 20 Mar 2017 15:06:35 +0100 Subject: [PATCH 04/16] Beautz is in the eye of the beholder --- Sources/dgram/Internals.swift | 39 ++++++++---------- Sources/dgram/Module.swift | 20 +++------ Sources/dgram/Socket.swift | 78 ++++++++++++++++++++++------------- 3 files changed, 74 insertions(+), 63 deletions(-) diff --git a/Sources/dgram/Internals.swift b/Sources/dgram/Internals.swift index 3216e7d..a31f104 100644 --- a/Sources/dgram/Internals.swift +++ b/Sources/dgram/Internals.swift @@ -1,12 +1,16 @@ -import fs -import net +// +// Module.swift +// Noze.io +// +// Created by https://github.com/lichtblau +// + import xsys +import fs -public func recvfrom( - _ fd: FileDescriptor, - likeAddress ignored: AT?, - count: Int = 65535) - -> ( Error?, [ UInt8 ]?, AT?) +func recvfrom(_ fd: FileDescriptor, + count: Int = 65535) + -> ( Error?, [ UInt8 ]?, AT?) { // TODO: inefficient init. Also: reuse buffers. var buf = [ UInt8 ](repeating: 0, count: count) @@ -16,13 +20,11 @@ public func recvfrom( var address = AT() var addrlen = socklen_t(address.len) let readCount = withUnsafeMutablePointer(to: &address) { ptr in - ptr.withMemoryRebound(to: xsys_sockaddr.self, capacity: 1) { - bptr in - return xsys.recvfrom(fd.fd, &buf, count, 0, - bptr, &addrlen) + ptr.withMemoryRebound(to: xsys_sockaddr.self, capacity: 1) { bptr in + return xsys.recvfrom(fd.fd, &buf, count, 0, bptr, &addrlen) } } - + guard readCount >= 0 else { return ( POSIXErrorCode(rawValue: xsys.errno)!, nil, nil ) } @@ -32,17 +34,12 @@ public func recvfrom( return ( nil, buf, address ) } -public func sendto( - _ fd: FileDescriptor, - data: [UInt8], - to toAddress: SocketAddress) - -> Error? -{ +func sendto(_ fd: FileDescriptor, data: [UInt8], to: SocketAddress) -> Error? { // synchronous - var data = data - var toAddress = toAddress - let addrlen = socklen_t(toAddress.len) + var data = data + var toAddress = to + let addrlen = socklen_t(toAddress.len) let writtenCount = withUnsafePointer(to: &toAddress) { ptr in ptr.withMemoryRebound(to: xsys_sockaddr.self, capacity: 1) { bptr in diff --git a/Sources/dgram/Module.swift b/Sources/dgram/Module.swift index 945d814..47b4e0d 100644 --- a/Sources/dgram/Module.swift +++ b/Sources/dgram/Module.swift @@ -3,11 +3,11 @@ // Noze.io // // Created by Helge Heß on 4/10/16. -// Copyright © 2016 ZeeZide GmbH. All rights reserved. +// Changed by https://github.com/lichtblau +// Copyright © 2016 ZeeZide GmbH and Contributors. All rights reserved. // @_exported import core -@_exported import streams import xsys public class NozeDgram : NozeModule { @@ -17,7 +17,7 @@ public let module = NozeDgram() open class CreateOptions { /// Version of IP stack (IPv4) - public var family : sa_family_t = sa_family_t(xsys.AF_INET) + public var family : sa_family_t = sa_family_t(xsys.AF_INET) public init() {} } @@ -39,19 +39,11 @@ open class CreateOptions { /// } /// @discardableResult -public func createSocket( - // TODO - options o: CreateOptions = CreateOptions(), - onMessage : MessageCB? = nil) -> Socket +public func createSocket(options : CreateOptions = CreateOptions(), + onMessage : MessageCB? = nil) -> Socket { + // TODO: support options let sock = Socket() if let cb = onMessage { _ = sock.onMessage(handler: cb) } return sock } - - -#if os(Linux) -#else - // importing this from xsys doesn't seem to work - import Foundation // this is for POSIXError : Error -#endif diff --git a/Sources/dgram/Socket.swift b/Sources/dgram/Socket.swift index 8e7c7b1..c1d03b5 100644 --- a/Sources/dgram/Socket.swift +++ b/Sources/dgram/Socket.swift @@ -3,7 +3,8 @@ // Noze.io // // Created by Helge Heß on 4/17/16. -// Copyright © 2016 ZeeZide GmbH. All rights reserved. +// Changed by https://github.com/lichtblau +// Copyright © 2016 ZeeZide GmbH and Contributors. All rights reserved. // import Dispatch @@ -14,12 +15,20 @@ import fs import net public typealias Datagram = ( data: [UInt8], peer: SocketAddress ) + // TBD(hh): sockaddr_any may be easier to work with? -public typealias MessageCB = ( Datagram ) -> Void -public typealias SocketEventCB = ( Socket ) -> Void +public typealias MessageCB = ( Datagram ) -> Void +public typealias SocketEventCB = ( Socket ) -> Void /// TODO: doc open class Socket : ErrorEmitter, LameLogObjectType { + + // TODO(hh): This should be a: DuplexStream + + enum Error : Swift.Error { + case UnexpectedError + } + public let log : Logger public var fd : FileDescriptor? = nil // fd can be invalid too public var address : sockaddr_any? = nil @@ -27,8 +36,8 @@ open class Socket : ErrorEmitter, LameLogObjectType { public let Q : DispatchQueue public var didRetainQ : Bool = false // #linux-public - public init(queue : DispatchQueue = core.Q, - enableLogger : Bool = false) + public init(queue : DispatchQueue = core.Q, + enableLogger : Bool = false) { self.Q = queue self.log = Logger(enabled: enableLogger) @@ -65,18 +74,19 @@ open class Socket : ErrorEmitter, LameLogObjectType { } @discardableResult - public func bind(_ address: sockaddr_any, - exclusive: Bool = false) -> Self { + public func bind(_ address : sockaddr_any, + exclusive : Bool = false) -> Self + { switch address { - case .AF_INET (let addr): return bind(addr, exclusive: exclusive) - case .AF_INET6(let addr): return bind(addr, exclusive: exclusive) - case .AF_LOCAL(let addr): return bind(addr, exclusive: exclusive) + case .AF_INET (let addr): return bind(addr, exclusive: exclusive) + case .AF_INET6(let addr): return bind(addr, exclusive: exclusive) + case .AF_LOCAL(let addr): return bind(addr, exclusive: exclusive) } } @discardableResult - public func bind(_ address: AT, - exclusive: Bool = false) -> Self + public func bind(_ address : AT, + exclusive : Bool = false) -> Self { // Note: Everything here runs synchronously, which I guess is fine in this // specific case? @@ -145,26 +155,32 @@ open class Socket : ErrorEmitter, LameLogObjectType { return self } - // MARK: - Accepting + + // MARK: - Receiving public func _onMessage(address localAddress: AT?) { // #linux-public // This is cheating a little, we pass in the localAddress to capture the - // generic socket type (which matches the one returned by accept(). + // generic socket type (which matches the one returned by recvfrom()). log.enter(); defer { log.leave() } - let (err, buf, peer) = recvfrom(self.fd!, likeAddress: localAddress) + let ( err, buf, peer ) : ( Swift.Error?, [ UInt8 ]?, AT? ) + = recvfrom(self.fd!) if let err = err { - self.caught(error: err) - } else { - self.messageListeners.emit((buf!, peer!)) + caught(error: err) + } + else if let buf = buf, let peer = peer { + messageListeners.emit( (buf, peer) ) + } + else { + caught(error: Error.UnexpectedError) } } // MARK: - Binding - public func _bind(address a: AT) -> Int32 { // #linux-public - var address = a + public func _bind(address: AT) -> Int32 { // #linux-public + var address = address return withUnsafePointer(to: &address) { ptr -> Int32 in return ptr.withMemoryRebound(to: xsys_sockaddr.self, capacity: 1) { bptr in @@ -176,7 +192,7 @@ open class Socket : ErrorEmitter, LameLogObjectType { // MARK: - sending - // TODO: node's completion callback -- not sure how important it is for + // TODO: Node's completion callback -- not sure how important it is for // a transport without delivery guarantee, but would be nice to have public func send(_ data: [UInt8], to peer: SocketAddress) { if let err = sendto(self.fd!, data: data, to: peer) { @@ -186,10 +202,15 @@ open class Socket : ErrorEmitter, LameLogObjectType { // // But suppose the write source issue was solvable -- would we // really want to buffer outgoing UDP packets? + // hh: Yes, I think we would want that. UDP is often used in contexts + // where it may fail, but usually doesn't (say NFS). + // Queuing a lot of packets can make sense. Maybe make it + // configurable? if err.isWouldBlockError { log.enter(); defer { log.leave() } log.debug("sendto(2): \(err)") - } else { + } + else { self.caught(error: err) } } @@ -197,8 +218,9 @@ open class Socket : ErrorEmitter, LameLogObjectType { // MARK: - Reuse server socket - public func _makeNonExclusive(fd lfd: FileDescriptor) -> Int32 { // #linux-public - var buf = Int32(1) + public func _makeNonExclusive(fd lfd: FileDescriptor) -> Int32 { + // #linux-public + var buf : Int32 = 1 let buflen = socklen_t(MemoryLayout.stride) var rc = xsys.setsockopt(lfd.fd, xsys.SOL_SOCKET, xsys.SO_REUSEADDR, &buf, buflen) @@ -249,12 +271,12 @@ open class Socket : ErrorEmitter, LameLogObjectType { // MARK: - Create socket - public func _setupSocket(domain d: Int32, type: Int32 = xsys.SOCK_DGRAM) - -> Int32 // #linux-public + public func _setupSocket(domain: Int32, type: Int32 = xsys.SOCK_DGRAM) + -> Int32 // #linux-public { assert(fd == nil) - let sockfd = xsys.socket(d, type, 0) + let sockfd = xsys.socket(domain, type, 0) log.debug("setup socket: \(sockfd)") guard sockfd != -1 else { log.debug(" failed: \(xsys.errno)") @@ -304,7 +326,7 @@ open class Socket : ErrorEmitter, LameLogObjectType { // MARK: - ErrorEmitter - public func caught(error e: Error) { // #linux-public + public func caught(error e: Swift.Error) { // #linux-public log.enter(); defer { log.leave() } self.errorListeners.emit(e) } From 7da249ca9fa2743ae223a85e2d804ce8c5ea0b59 Mon Sep 17 00:00:00 2001 From: Bas Broek Date: Mon, 20 Mar 2017 15:18:35 +0100 Subject: [PATCH 05/16] Add syntax highlighting to Readme (#16) * Add syntax highlighting to Readme * Change to Swift --- README.md | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index b91da16..025e7c8 100644 --- a/README.md +++ b/README.md @@ -48,25 +48,25 @@ high performance statically typed and AOT-compiled language, There is a reasonably large collection of simple, focused: [Noze.io examples](https://github.com/NozeIO/Noze.io/tree/master/Samples) But here you go, the "standard" Node example, a HelloWorld httpd: - - import http - - http.createServer { req, res in - res.writeHead(200, [ "Content-Type": "text/html" ]) - res.end("

Hello World

") - } - .listen(1337) - +```swift +import http + +http.createServer { req, res in + res.writeHead(200, [ "Content-Type": "text/html" ]) + res.end("

Hello World

") +} +.listen(1337) +``` An echo daemon, just piping the in-end of a socket into its own-out end: - - import net - - net.createServer { sock in - sock.write("Welcome to Noze.io!\r\n") - sock | sock - } - .listen(1337) - +```swift +import net + +net.createServer { sock in + sock.write("Welcome to Noze.io!\r\n") + sock | sock +} +.listen(1337) +``` More complex stuff including a [Todo-MVC backend](https://github.com/NozeIO/Noze.io/blob/master/Samples/todo-mvc-redis/main.swift) can be found in the From 2975d7eaea4d4dd1e796e766707605c8ec04c2e8 Mon Sep 17 00:00:00 2001 From: Helge Hess Date: Tue, 21 Mar 2017 21:11:18 +0100 Subject: [PATCH 06/16] Update Travis setup Install clang 3.6, etc. Match mod-swift more closely. --- .travis.d/before-install.sh | 20 ++++++++++++++++++++ {xcconfig => .travis.d}/install-swiftenv.sh | 0 {xcconfig => .travis.d}/install.sh | 0 .travis.yml | 4 ++-- xcconfig/before-install.sh | 13 ------------- 5 files changed, 22 insertions(+), 15 deletions(-) create mode 100755 .travis.d/before-install.sh rename {xcconfig => .travis.d}/install-swiftenv.sh (100%) rename {xcconfig => .travis.d}/install.sh (100%) delete mode 100755 xcconfig/before-install.sh diff --git a/.travis.d/before-install.sh b/.travis.d/before-install.sh new file mode 100755 index 0000000..f46f7f3 --- /dev/null +++ b/.travis.d/before-install.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +if [[ "$TRAVIS_OS_NAME" == "Linux" ]]; then + sudo apt-get install -y wget \ + clang-3.6 libc6-dev make git libicu52 libicu-dev \ + autoconf libtool pkg-config \ + libblocksruntime-dev \ + libkqueue-dev \ + libpthread-workqueue-dev \ + systemtap-sdt-dev \ + libbsd-dev libbsd0 libbsd0-dbg \ + curl libcurl4-openssl-dev \ + libedit-dev \ + python2.7 python2.7-dev + + sudo update-alternatives --quiet \ + --install /usr/bin/clang clang /usr/bin/clang-3.6 100 + sudo update-alternatives --quiet \ + --install /usr/bin/clang++ clang++ /usr/bin/clang++-3.6 100 +fi diff --git a/xcconfig/install-swiftenv.sh b/.travis.d/install-swiftenv.sh similarity index 100% rename from xcconfig/install-swiftenv.sh rename to .travis.d/install-swiftenv.sh diff --git a/xcconfig/install.sh b/.travis.d/install.sh similarity index 100% rename from xcconfig/install.sh rename to .travis.d/install.sh diff --git a/.travis.yml b/.travis.yml index 8add0ea..6164980 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,10 +13,10 @@ matrix: osx_image: xcode8.2 before_install: - - ./xcconfig/before-install.sh + - ./.travis.d/before-install.sh install: - - ./xcconfig/install.sh + - ./.travis.d/install.sh script: - export PATH="$HOME/usr/bin:$PATH" diff --git a/xcconfig/before-install.sh b/xcconfig/before-install.sh deleted file mode 100755 index f744d36..0000000 --- a/xcconfig/before-install.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -if [[ "$TRAVIS_OS_NAME" == "Linux" ]]; then - # GCD prerequisites - sudo apt-get install -y wget \ - clang make git libicu52 \ - autoconf libtool pkg-config \ - libblocksruntime-dev \ - libkqueue-dev \ - libpthread-workqueue-dev \ - systemtap-sdt-dev \ - libbsd-dev libbsd0 libbsd0-dbg -fi From 18548adac6b111f4eabde9337ead4d60e3cfe8d0 Mon Sep 17 00:00:00 2001 From: David Lichteblau Date: Wed, 22 Mar 2017 21:33:08 +0200 Subject: [PATCH 07/16] Swift 3.1 error: Fix build --- Sources/streams/Module.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/streams/Module.swift b/Sources/streams/Module.swift index 0c857fd..c56b793 100644 --- a/Sources/streams/Module.swift +++ b/Sources/streams/Module.swift @@ -13,7 +13,7 @@ public class NozeStreams : NozeModule, EventEmitterType { lazy var newReadableListeners : EventListenerSet = EventListenerSet(queueLength: 0) - lazy var newWritableListeners : EventListenerSet= + lazy var newWritableListeners : EventListenerSet = EventListenerSet(queueLength: 0) public func onNewReadable(cb: @escaping ( ReadableStreamType ) -> Void) From c0283ee7c5358fb482981b58ebdf073b594fad88 Mon Sep 17 00:00:00 2001 From: David Lichteblau Date: Wed, 22 Mar 2017 19:48:51 +0200 Subject: [PATCH 08/16] Swift 3.1 warnings: Suppress optional-as-any warnings Hiding tremendous warnings. Sad. https://github.com/apple/swift/pull/5110 --- Sources/child_process/ChildProcess.swift | 2 +- Sources/console/Console.swift | 4 ++-- Sources/core/NozeCore.swift | 6 +++--- Sources/http/URL.swift | 2 +- Sources/http_parser/http_parser.swift | 2 +- Sources/mustache/MustacheParser.swift | 2 +- Sources/net/Server.swift | 2 +- Sources/redis/RedisParser.swift | 2 +- Sources/redis/RedisPrint.swift | 2 +- Sources/streams/WritableStream.swift | 2 +- Sources/streams/extra/TransformStream.swift | 2 +- Sources/streams/extra/WritableByteStreamWrapper.swift | 2 +- Sources/streams/pipes/Stream2StreamPipe.swift | 4 ++-- 13 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Sources/child_process/ChildProcess.swift b/Sources/child_process/ChildProcess.swift index 0750223..afc9382 100644 --- a/Sources/child_process/ChildProcess.swift +++ b/Sources/child_process/ChildProcess.swift @@ -92,7 +92,7 @@ public class ChildProcess : ErrorEmitter { if rc == -1 { // TODO let error = POSIXErrorCode(rawValue: xsys.errno) - print("ERROR: waitpid error: \(error)") + print("ERROR: waitpid error: \(error as Optional)") if error?.rawValue == ECHILD { print(" child gone already?") diff --git a/Sources/console/Console.swift b/Sources/console/Console.swift index 2467cdc..23140d8 100644 --- a/Sources/console/Console.swift +++ b/Sources/console/Console.swift @@ -55,7 +55,7 @@ public extension ConsoleType { // Actual logging funcs public func dir(_ obj: Any?) { // TODO: implement more - log("\(obj)") + log("\(obj as Optional)") } } @@ -88,7 +88,7 @@ func writeValues(to t: T, _ values : [ Any? ]) _ = t.write(v) } else { - _ = t.write("\(v)") + _ = t.write("\(v as Optional)") } } } diff --git a/Sources/core/NozeCore.swift b/Sources/core/NozeCore.swift index 304894c..605165e 100644 --- a/Sources/core/NozeCore.swift +++ b/Sources/core/NozeCore.swift @@ -46,7 +46,7 @@ public class NozeCore : NozeModule { workCount += 1 if debugRetain { - let hash = "\(filename)" + let hash = "\(filename as Optional)" let old = retainDebugMap[hash] ?? 0 retainDebugMap[hash] = old + 1 @@ -63,7 +63,7 @@ public class NozeCore : NozeModule { function: String? = #function) { if debugRetain { - let hash = "\(filename)" + let hash = "\(filename as Optional)" let old = retainDebugMap[hash] ?? 0 assert(old > 0) if old == 1 { @@ -79,7 +79,7 @@ public class NozeCore : NozeModule { workCount -= 1 if workCount == 0 { if debugRetain { - print("TERMINATE[\(workCount): \(filename):\(line) \(function)") + print("TERMINATE[\(workCount): \(filename as Optional):\(line as Optional) \(function as Optional)") } maybeTerminate() } diff --git a/Sources/http/URL.swift b/Sources/http/URL.swift index d793be0..cc5d99c 100644 --- a/Sources/http/URL.swift +++ b/Sources/http/URL.swift @@ -338,7 +338,7 @@ func parse_url(_ us: String) -> URL { url.host = s[s.startIndex.. String { case LF: p = "NL" case CR: p = "CR" case cTAB: p = "TAB" - default: p = "'\(UnicodeScalar(Int(ch)))'" + default: p = "'\(UnicodeScalar(Int(ch)) as Optional)'" } return "\(ch) \(p)" } diff --git a/Sources/mustache/MustacheParser.swift b/Sources/mustache/MustacheParser.swift index 45f9d79..4946a8a 100644 --- a/Sources/mustache/MustacheParser.swift +++ b/Sources/mustache/MustacheParser.swift @@ -106,7 +106,7 @@ public class MustacheParser { case .SectionEnd(let s): if !s.isEmpty && s != se { - print("section tags not balanced: \(s) expected \(se)") + print("section tags not balanced: \(s) expected \(se as Optional)") } return nil } diff --git a/Sources/net/Server.swift b/Sources/net/Server.swift index 6f76d25..65b4a29 100644 --- a/Sources/net/Server.swift +++ b/Sources/net/Server.swift @@ -325,7 +325,7 @@ open class Server : ErrorEmitter, LameLogObjectType { } fd = FileDescriptor(lfd) - log.debug(" FD: \(fd)") + log.debug(" FD: \(fd as Optional)") return 0 } diff --git a/Sources/redis/RedisParser.swift b/Sources/redis/RedisParser.swift index b67bbce..7182d21 100644 --- a/Sources/redis/RedisParser.swift +++ b/Sources/redis/RedisParser.swift @@ -206,7 +206,7 @@ class RedisParser : TransformStream { else if let ctx = context { if countValue > 0 { context = ParseContext(ctx, countValue) - if heavyDebug { print("! started new context: \(context)") } + if heavyDebug { print("! started new context: \(context as Optional)") } } else { // push an empty array diff --git a/Sources/redis/RedisPrint.swift b/Sources/redis/RedisPrint.swift index 8acd493..9aae73f 100644 --- a/Sources/redis/RedisPrint.swift +++ b/Sources/redis/RedisPrint.swift @@ -66,7 +66,7 @@ public func print(error err: Error?, values: [RedisValue]?) { let prefix = " [\(i)]: " switch values[i] { case .Array(let values): - print("\(prefix)\(values)") + print("\(prefix)\(values as Optional)") case .Error(let error): print("\(prefix)ERROR \(error)") diff --git a/Sources/streams/WritableStream.swift b/Sources/streams/WritableStream.swift index b127f3d..d11d3c8 100644 --- a/Sources/streams/WritableStream.swift +++ b/Sources/streams/WritableStream.swift @@ -200,7 +200,7 @@ open class WritableStream _primaryWriteV(buckets: brigade) { error, writeCount in // Q: main log.enter(function: "writeNextBlock - handler"); defer { log.leave() } - log.debug("wrote #\(writeCount) brigade #\(brigadeCount) \(error)") + log.debug("wrote #\(writeCount) brigade #\(brigadeCount) \(error as Optional)") assert(writeCount <= brigadeCount) assert(error != nil || writeCount > 0) // right? diff --git a/Sources/streams/extra/TransformStream.swift b/Sources/streams/extra/TransformStream.swift index c0ccc71..bf3810d 100644 --- a/Sources/streams/extra/TransformStream.swift +++ b/Sources/streams/extra/TransformStream.swift @@ -105,7 +105,7 @@ open class TransformStream let bigChunk = Array(c.joined()) _transform(bucket: bigChunk) { error, data in - log.debug("done: \(error) \(data)") + log.debug("done: \(error as Optional) \(data as Optional)") // Note: invoking done(nil, nil) doesn't do EOF! It just doesn't push // anything. if let data = data { self.push(data) } diff --git a/Sources/streams/extra/WritableByteStreamWrapper.swift b/Sources/streams/extra/WritableByteStreamWrapper.swift index acf8677..ff3348a 100644 --- a/Sources/streams/extra/WritableByteStreamWrapper.swift +++ b/Sources/streams/extra/WritableByteStreamWrapper.swift @@ -165,5 +165,5 @@ open class WritableByteStreamWrapper // MARK: - Logging public var log : Logger - public var logStateInfo : String { return "s=\(stream)" } + public var logStateInfo : String { return "s=\(stream as Optional)" } } diff --git a/Sources/streams/pipes/Stream2StreamPipe.swift b/Sources/streams/pipes/Stream2StreamPipe.swift index 83a516a..a673cc0 100644 --- a/Sources/streams/pipes/Stream2StreamPipe.swift +++ b/Sources/streams/pipes/Stream2StreamPipe.swift @@ -157,7 +157,7 @@ private class StreamPipeState } } else { - heavyLog("Keeping open \(dest)") + heavyLog("Keeping open \(dest as Optional)") } // Important: this resets all closures in the inStream which may be @@ -177,7 +177,7 @@ private class StreamPipeState final func onPipeEOF() { - heavyPipeLog("\nCCC hit ********** EOF ***********. \(src)") + heavyPipeLog("\nCCC hit ********** EOF ***********. \(src as Optional)") // FIXME: hitEOF apparently not set on sockets? //assert(inStream.hitEOF) From 4e25dcf93e6333dddb815d93a1b3d90d57413c61 Mon Sep 17 00:00:00 2001 From: David Lichteblau Date: Wed, 22 Mar 2017 19:51:18 +0200 Subject: [PATCH 09/16] Swift 3.1 warnings: Avoid as! warning --- Sources/express/Express.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Sources/express/Express.swift b/Sources/express/Express.swift index 4cd9374..8e5afcb 100644 --- a/Sources/express/Express.swift +++ b/Sources/express/Express.swift @@ -128,7 +128,11 @@ public extension Express { public func listen(_ port: Int? = nil, backlog: Int = 512, onListening cb : (( net.Server ) -> Void)? = nil) -> Self { +#if swift(>=3.1) + let mo = self +#else let mo = self as! MiddlewareObject // not sure why this is necessary +#endif let server = http.createServer(onRequest: mo.requestHandler) _ = server.listen(port, backlog: backlog, onListening: cb) return self From 5049ed49eba7b6d2729e3c67de21b2932025812f Mon Sep 17 00:00:00 2001 From: David Lichteblau Date: Wed, 22 Mar 2017 21:29:31 +0200 Subject: [PATCH 10/16] Swift 3.1 warnings: Avoid mixed-type addition in http_parser --- Sources/http_parser/http_parser.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/http_parser/http_parser.swift b/Sources/http_parser/http_parser.swift index eda9c0f..f165421 100644 --- a/Sources/http_parser/http_parser.swift +++ b/Sources/http_parser/http_parser.swift @@ -927,7 +927,7 @@ public extension http_parser { guard IS_NUM(ch) else { return .Error(.INVALID_VERSION) } self.http_minor *= 10 - self.http_minor += ch - c0 + self.http_minor += Int16(ch - c0) guard self.http_minor < 1000 else { return .Error(.INVALID_VERSION) } @@ -1122,7 +1122,7 @@ public extension http_parser { case .h_content_length: guard IS_NUM(ch) else { return .Error(.INVALID_CONTENT_LENGTH) } - self.content_length = ch - c0; + self.content_length = Int(ch - c0) case .h_connection: /* looking for 'Connection: keep-alive' */ From 5737ff2779b508aa7db8c3719825bd3ee84fb8b0 Mon Sep 17 00:00:00 2001 From: David Lichteblau Date: Wed, 22 Mar 2017 21:40:10 +0200 Subject: [PATCH 11/16] Swift 3.1 warnings: method should not be declared public --- Sources/streams/Duplex.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/streams/Duplex.swift b/Sources/streams/Duplex.swift index 3ead1b8..7f25f06 100644 --- a/Sources/streams/Duplex.swift +++ b/Sources/streams/Duplex.swift @@ -16,8 +16,8 @@ import core open class Duplex : DuplexStream { - typealias ReadType = TSource.SourceElement - typealias WriteType = TTarget.TargetElement + public typealias ReadType = TSource.SourceElement + public typealias WriteType = TTarget.TargetElement public var source : TSource public var target : TTarget From b432cd5faa94dda9e747b16dafdda092b784d3a7 Mon Sep 17 00:00:00 2001 From: David Lichteblau Date: Wed, 22 Mar 2017 22:04:19 +0200 Subject: [PATCH 12/16] Swift 3.1 warnings: obscure UnsafeMutablePointer.allocation warning --- Sources/http_parser/ascii.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/http_parser/ascii.swift b/Sources/http_parser/ascii.swift index 5c1faf2..12b30f1 100644 --- a/Sources/http_parser/ascii.swift +++ b/Sources/http_parser/ascii.swift @@ -110,7 +110,8 @@ let c9 : CChar = 57 // 9 // UnsafePointer. Subscripting still works with that. internal func copyArrayToBuffer(array a: [ T ]) -> UnsafePointer { let res = UnsafeMutablePointer.allocate(capacity: a.count) - res.initialize(from: a) + a.withUnsafeBufferPointer { res.initialize( + from: $0.baseAddress!, count: a.count) } return UnsafePointer(res) } From fcfa029652a7233588250181c38bd8c38833974b Mon Sep 17 00:00:00 2001 From: David Lichteblau Date: Wed, 22 Mar 2017 21:37:06 +0200 Subject: [PATCH 13/16] Semicolons in the eye of the beholder Big day for coding style. Working hard! --- Sources/http_parser/http_parser.swift | 192 +++++++++++++------------- 1 file changed, 96 insertions(+), 96 deletions(-) diff --git a/Sources/http_parser/http_parser.swift b/Sources/http_parser/http_parser.swift index f165421..736ad9f 100644 --- a/Sources/http_parser/http_parser.swift +++ b/Sources/http_parser/http_parser.swift @@ -523,14 +523,14 @@ public extension http_parser { self.content_length = Int.max // was: ULLONG_MAX if ch == 72 /* 'H' */ { - UPDATE_STATE(.s_res_or_resp_H); + UPDATE_STATE(.s_res_or_resp_H) let len = CALLBACK_NOTIFY(.MessageBegin, &CURRENT_STATE, settings, p, data) if let len = len { return .CallbackDone(len) } } else { - self.type = .Request; + self.type = .Request UPDATE_STATE(.s_start_req) return .Reexecute } @@ -554,7 +554,7 @@ public extension http_parser { self.content_length = Int.max // was: ULLONG_MAX switch (ch) { - case 72 /* 'H' */: UPDATE_STATE(.s_res_H); + case 72 /* 'H' */: UPDATE_STATE(.s_res_H) case CR: break case LF: break default: return .Error(.INVALID_CONSTANT) @@ -579,7 +579,7 @@ public extension http_parser { case .s_res_HTTP: guard STRICT_CHECK(ch != cSLASH) else { return .Error(.STRICT) } UPDATE_STATE(.s_res_first_http_major) - break; + break case .s_res_first_http_major: guard ch >= c0 && ch <= c9 else { return .Error(.INVALID_VERSION) } @@ -590,7 +590,7 @@ public extension http_parser { /* major HTTP version or dot */ case .s_res_http_major: if ch == cDOT { - UPDATE_STATE(.s_res_first_http_minor); + UPDATE_STATE(.s_res_first_http_minor) break } @@ -606,12 +606,12 @@ public extension http_parser { guard IS_NUM(ch) else { return .Error(.INVALID_VERSION) } self.http_minor = Int16(ch - c0) - UPDATE_STATE(.s_res_http_minor); + UPDATE_STATE(.s_res_http_minor) /* minor HTTP version or end of request line */ case .s_res_http_minor: if ch == cSPACE { - UPDATE_STATE(.s_res_first_status_code); + UPDATE_STATE(.s_res_first_status_code) break } @@ -629,13 +629,13 @@ public extension http_parser { return .Error(.INVALID_STATUS) } self.status_code = Int16(ch - 48 /* '0' */) - UPDATE_STATE(.s_res_status_code); + UPDATE_STATE(.s_res_status_code) case .s_res_status_code: if !IS_NUM(ch) { switch (ch) { - case cSPACE: UPDATE_STATE(.s_res_status_start); - case CR: UPDATE_STATE(.s_res_line_almost_done); + case cSPACE: UPDATE_STATE(.s_res_status_start) + case CR: UPDATE_STATE(.s_res_line_almost_done) case LF: UPDATE_STATE(.s_header_field_start) default: return .Error(.INVALID_STATUS) } @@ -651,8 +651,8 @@ public extension http_parser { if ch == CR { UPDATE_STATE(.s_res_line_almost_done); break } if ch == LF { UPDATE_STATE(.s_header_field_start); break } MARK(.Status) - UPDATE_STATE(.s_res_status); - self.index = 0; + UPDATE_STATE(.s_res_status) + self.index = 0 case .s_res_status: if ch == CR { @@ -685,7 +685,7 @@ public extension http_parser { guard IS_ALPHA(ch) else { return .Error(.INVALID_METHOD) } self.method = .GET - self.index = 1; + self.index = 1 switch ch { case cA: self.method = .ACL case cB: self.method = .BIND @@ -709,7 +709,7 @@ public extension http_parser { default: return .Error(.INVALID_METHOD) } - UPDATE_STATE(.s_req_method); + UPDATE_STATE(.s_req_method) // CALLBACK_NOTIFY(message_begin); let rc = CALLBACK_NOTIFY(.MessageBegin, &CURRENT_STATE, settings, @@ -718,7 +718,7 @@ public extension http_parser { if debugOn { print(" METHOD: \(self.method)") } - break; + break case .s_req_method: guard ch != 0 else { return .Error(.INVALID_METHOD) } @@ -728,40 +728,40 @@ public extension http_parser { if (ch == cSPACE && matcher[self.index] == 0) { - UPDATE_STATE(.s_req_spaces_before_url); + UPDATE_STATE(.s_req_spaces_before_url) } else if (ch == matcher[self.index]) { /* nada */ } else if (self.method == .CONNECT) { if (self.index == 1 && ch == cH) { - self.method = .CHECKOUT; + self.method = .CHECKOUT } else if (self.index == 2 && ch == cP) { - self.method = .COPY; + self.method = .COPY } else { return .Error(.INVALID_METHOD) } } else if (self.method == .MKCOL) { if (self.index == 1 && ch == cO) { - self.method = .MOVE; + self.method = .MOVE } else if (self.index == 1 && ch == cE) { - self.method = .MERGE; + self.method = .MERGE } else if (self.index == 1 && ch == cDASH) { - self.method = .MSEARCH; + self.method = .MSEARCH } else if (self.index == 2 && ch == cA) { - self.method = .MKACTIVITY; + self.method = .MKACTIVITY } else if (self.index == 3 && ch == cA) { - self.method = .MKCALENDAR; + self.method = .MKCALENDAR } else { return .Error(.INVALID_METHOD) } } else if (self.method == .SUBSCRIBE) { if (self.index == 1 && ch == cE) { - self.method = .SEARCH; + self.method = .SEARCH } else { return .Error(.INVALID_METHOD) } } else if (self.method == .REPORT) { if (self.index == 2 && ch == cB) { - self.method = .REBIND; + self.method = .REBIND } else { return .Error(.INVALID_METHOD) } @@ -772,13 +772,13 @@ public extension http_parser { } else if (ch == cU) { self.method = .PUT; /* or HTTP_PURGE */ } else if (ch == cA) { - self.method = .PATCH; + self.method = .PATCH } else { return .Error(.INVALID_METHOD) } } else if (self.method == .LOCK) { if (ch == cI) { - self.method = .LINK; + self.method = .LINK } else { return .Error(.INVALID_METHOD) } @@ -786,15 +786,15 @@ public extension http_parser { } else if (self.index == 2) { if (self.method == .PUT) { if (ch == cR) { - self.method = .PURGE; + self.method = .PURGE } else { return .Error(.INVALID_METHOD) } } else if (self.method == .UNLOCK) { if (ch == cS) { - self.method = .UNSUBSCRIBE; + self.method = .UNSUBSCRIBE } else if(ch == cB) { - self.method = .UNBIND; + self.method = .UNBIND } else { return .Error(.INVALID_METHOD) } @@ -802,9 +802,9 @@ public extension http_parser { return .Error(.INVALID_METHOD) } } else if (self.index == 4 && self.method == .PROPFIND && ch == cP) { - self.method = .PROPPATCH; + self.method = .PROPPATCH } else if (self.index == 3 && self.method == .UNLOCK && ch == cI) { - self.method = .UNLINK; + self.method = .UNLINK } else { return .Error(.INVALID_METHOD) } @@ -817,7 +817,7 @@ public extension http_parser { MARK(.URL) if (self.method == .CONNECT) { - UPDATE_STATE(.s_req_server_start); + UPDATE_STATE(.s_req_server_start) } UPDATE_STATE(parse_url_char(CURRENT_STATE, ch)) @@ -902,12 +902,12 @@ public extension http_parser { case .s_req_http_major: if ch == cDOT { UPDATE_STATE(.s_req_first_http_minor) - break; + break } guard IS_NUM(ch) else { return .Error(.INVALID_VERSION) } - self.http_major *= 10; - self.http_major += Int16(ch - c0); + self.http_major *= 10 + self.http_major += Int16(ch - c0) guard self.http_major < 1000 else { return .Error(.INVALID_VERSION) } @@ -934,7 +934,7 @@ public extension http_parser { /* end of request line */ case .s_req_line_almost_done: guard ch == LF else { return .Error(.LF_EXPECTED) } - UPDATE_STATE(.s_header_field_start); + UPDATE_STATE(.s_header_field_start) case .s_header_field_start: if ch == CR { UPDATE_STATE(.s_headers_almost_done); break } @@ -950,9 +950,9 @@ public extension http_parser { guard c != 0 else { return .Error(.INVALID_HEADER_TOKEN) } - MARK(.HeaderField); + MARK(.HeaderField) - self.index = 0; + self.index = 0 UPDATE_STATE(.s_header_field) switch c { @@ -996,11 +996,11 @@ public extension http_parser { case .h_matching_connection: self.index += 1 if self.index > lCONNECTION || c != CONNECTION[self.index] { - self.header_state = .h_general; + self.header_state = .h_general } else if self.index == lCONNECTION - 1 { - self.header_state = .h_connection; + self.header_state = .h_connection } - break; + break /* proxy-connection */ @@ -1008,9 +1008,9 @@ public extension http_parser { self.index += 1 if (self.index > lPROXY_CONNECTION || c != PROXY_CONNECTION[self.index]) { - self.header_state = .h_general; + self.header_state = .h_general } else if self.index == lPROXY_CONNECTION-1 { - self.header_state = .h_connection; + self.header_state = .h_connection } /* content-length */ @@ -1019,9 +1019,9 @@ public extension http_parser { self.index += 1 if (self.index > lCONTENT_LENGTH || c != CONTENT_LENGTH[self.index]) { - self.header_state = .h_general; + self.header_state = .h_general } else if self.index == lCONTENT_LENGTH-1 { - self.header_state = .h_content_length; + self.header_state = .h_content_length } /* transfer-encoding */ @@ -1030,9 +1030,9 @@ public extension http_parser { self.index += 1 if (self.index > lTRANSFER_ENCODING || c != TRANSFER_ENCODING[self.index]) { - self.header_state = .h_general; + self.header_state = .h_general } else if self.index == lTRANSFER_ENCODING-1 { - self.header_state = .h_transfer_encoding; + self.header_state = .h_transfer_encoding } /* upgrade */ @@ -1041,9 +1041,9 @@ public extension http_parser { self.index += 1 if self.index > lUPGRADE || c != UPGRADE[self.index] { - self.header_state = .h_general; + self.header_state = .h_general } else if self.index == lUPGRADE-1 { - self.header_state = .h_upgrade; + self.header_state = .h_upgrade } case .h_connection, .h_content_length, .h_transfer_encoding, @@ -1073,7 +1073,7 @@ public extension http_parser { } if ch == cCOLON { - UPDATE_STATE(.s_header_value_discard_ws); + UPDATE_STATE(.s_header_value_discard_ws) // CALLBACK_DATA(header_field); let rc = CALLBACK_DATA(.HeaderField, &header_field_mark, @@ -1110,7 +1110,7 @@ public extension http_parser { switch self.header_state { case .h_upgrade: _ = self.flags.insert(.F_UPGRADE) - self.header_state = .h_general; + self.header_state = .h_general case .h_transfer_encoding: /* looking for 'Transfer-Encoding: chunked' */ @@ -1127,14 +1127,14 @@ public extension http_parser { case .h_connection: /* looking for 'Connection: keep-alive' */ if (c == ck) { - self.header_state = .h_matching_connection_keep_alive; + self.header_state = .h_matching_connection_keep_alive /* looking for 'Connection: close' */ } else if (c == cc) { - self.header_state = .h_matching_connection_close; + self.header_state = .h_matching_connection_close } else if (c == cu) { - self.header_state = .h_matching_connection_upgrade; + self.header_state = .h_matching_connection_upgrade } else { - self.header_state = .h_matching_connection_token; + self.header_state = .h_matching_connection_token } /* Multi-value `Connection` header */ @@ -1151,8 +1151,8 @@ public extension http_parser { let ch = p!.pointee if ch == CR { - UPDATE_STATE(.s_header_almost_done); - self.header_state = h_state; + UPDATE_STATE(.s_header_almost_done) + self.header_state = h_state // CALLBACK_DATA(header_value); let rc = CALLBACK_DATA(.HeaderValue, &header_value_mark, &CURRENT_STATE, settings, p, data) @@ -1161,11 +1161,11 @@ public extension http_parser { } if ch == LF { - UPDATE_STATE(.s_header_almost_done); + UPDATE_STATE(.s_header_almost_done) guard COUNT_HEADER_SIZE(p! - start!) else { return .Error(.HEADER_OVERFLOW) } - self.header_state = h_state; + self.header_state = h_state // CALLBACK_DATA_NOADVANCE(header_value); let rc = CALLBACK_DATA_NOADVANCE(.HeaderValue, &header_value_mark, @@ -1180,9 +1180,9 @@ public extension http_parser { switch h_state { case .h_general: - var limit : size_t = data! + len - p!; + var limit : size_t = data! + len - p! - limit = min(limit, HTTP_MAX_HEADER_SIZE); + limit = min(limit, HTTP_MAX_HEADER_SIZE) let rCR = memchr(p!, Int32(CR), limit) let rLF = memchr(p!, Int32(LF), limit) @@ -1202,7 +1202,7 @@ public extension http_parser { } else if p_lf != nil { p = p_lf } else { - p = data! + len; + p = data! + len } p! -= 1 @@ -1214,7 +1214,7 @@ public extension http_parser { if ch == cSPACE { break } guard IS_NUM(ch) else { - self.header_state = h_state; + self.header_state = h_state return .Error(.INVALID_CONTENT_LENGTH) } @@ -1225,7 +1225,7 @@ public extension http_parser { /* Overflow? Test against a conservative limit for simplicity. */ // HH: was ULLONG_MAX if (Int.max - 10) / 10 < self.content_length { - self.header_state = h_state; + self.header_state = h_state return .Error(.INVALID_CONTENT_LENGTH) } @@ -1259,7 +1259,7 @@ public extension http_parser { /* looking for 'Connection: keep-alive' */ case .h_matching_connection_keep_alive: - self.index += 1; + self.index += 1 if self.index > lKEEP_ALIVE || c != KEEP_ALIVE[self.index] { h_state = .h_matching_connection_token } else if self.index == lKEEP_ALIVE-1 { @@ -1305,7 +1305,7 @@ public extension http_parser { _ = self.flags.insert(.F_CONNECTION_UPGRADE) } h_state = .h_matching_connection_token_start - self.index = 0; + self.index = 0 } else if ch != cSPACE { h_state = .h_matching_connection_token } @@ -1317,7 +1317,7 @@ public extension http_parser { p! += 1 } - self.header_state = h_state; + self.header_state = h_state guard COUNT_HEADER_SIZE(p! - start!) else { return .Error(.HEADER_OVERFLOW) @@ -1347,7 +1347,7 @@ public extension http_parser { _ = self.flags.insert(.F_CHUNKED) case .h_connection_upgrade: _ = self.flags.insert(.F_CONNECTION_UPGRADE) - default: break; + default: break } UPDATE_STATE(.s_header_field_start) @@ -1376,7 +1376,7 @@ public extension http_parser { /* header value was empty */ MARK(.HeaderValue) - UPDATE_STATE(.s_header_field_start); + UPDATE_STATE(.s_header_field_start) let rc = CALLBACK_DATA_NOADVANCE(.HeaderValue, &header_value_mark, @@ -1391,7 +1391,7 @@ public extension http_parser { if self.flags.contains(.F_TRAILING) { /* End of a chunked request */ - UPDATE_STATE(.s_message_done); + UPDATE_STATE(.s_message_done) let rc = CALLBACK_NOTIFY_NOADVANCE(.ChunkComplete, &CURRENT_STATE, settings, p, data) if let rc = rc { return .CallbackDone(rc) } @@ -1399,7 +1399,7 @@ public extension http_parser { return .Reexecute } - UPDATE_STATE(.s_headers_done); + UPDATE_STATE(.s_headers_done) /* Set this here so that on_headers_complete() callbacks can see it */ self.upgrade = @@ -1418,11 +1418,11 @@ public extension http_parser { */ switch settings.onHeadersComplete(parser: self) { case 0: - break; + break case 1: _ = self.flags.insert(.F_SKIPBODY) - break; + break default: error = .CB_headers_complete @@ -1446,7 +1446,7 @@ public extension http_parser { || !hasBody)) { /* Exit, the rest of the message is in a different protocol. */ - UPDATE_STATE(NEW_MESSAGE); + UPDATE_STATE(NEW_MESSAGE) // CALLBACK_NOTIFY(message_complete); let rc = CALLBACK_NOTIFY(.MessageComplete, &CURRENT_STATE, settings, p, data) @@ -1455,18 +1455,18 @@ public extension http_parser { } if self.flags.contains(.F_SKIPBODY) { - UPDATE_STATE(NEW_MESSAGE); + UPDATE_STATE(NEW_MESSAGE) // CALLBACK_NOTIFY(message_complete); let rc = CALLBACK_NOTIFY(.MessageComplete, &CURRENT_STATE, settings, p, data) if let rc = rc { return .CallbackDone(rc) } } else if self.flags.contains(.F_CHUNKED) { /* chunked encoding - ignore Content-Length header */ - UPDATE_STATE(.s_chunk_size_start); + UPDATE_STATE(.s_chunk_size_start) } else { if self.content_length == 0 { /* Content-Length header given but zero: Content-Length: 0\r\n */ - UPDATE_STATE(NEW_MESSAGE); + UPDATE_STATE(NEW_MESSAGE) // CALLBACK_NOTIFY(message_complete); let rc = CALLBACK_NOTIFY(.MessageComplete, &CURRENT_STATE, settings, p, data) @@ -1477,7 +1477,7 @@ public extension http_parser { } else { if (!messageNeedsEOF) { /* Assume content-length 0 - read the next */ - UPDATE_STATE(NEW_MESSAGE); + UPDATE_STATE(NEW_MESSAGE) // CALLBACK_NOTIFY(message_complete); let rc = CALLBACK_NOTIFY(.MessageComplete, &CURRENT_STATE, settings, p, data) @@ -1491,10 +1491,10 @@ public extension http_parser { case .s_body_identity: let to_read : Int /* uint64_t */ = min(self.content_length, - ((data! + len) - p!)); + ((data! + len) - p!)) assert(self.content_length != 0 - && self.content_length != Int.max /* ULLONG_MAX */); + && self.content_length != Int.max /* ULLONG_MAX */) /* The difference between advancing content_length and p is because * the latter will automaticaly advance on the next loop iteration. @@ -1503,11 +1503,11 @@ public extension http_parser { */ MARK(.Body) - self.content_length -= to_read; - p! += to_read - 1; + self.content_length -= to_read + p! += to_read - 1 if (self.content_length == 0) { - UPDATE_STATE(.s_message_done); + UPDATE_STATE(.s_message_done) /* Mimic CALLBACK_DATA_NOADVANCE() but with one extra byte. * @@ -1529,7 +1529,7 @@ public extension http_parser { /* read until EOF */ case .s_body_identity_eof: MARK(.Body) - p = data! + len - 1; + p = data! + len - 1 case .s_message_done: UPDATE_STATE(NEW_MESSAGE) @@ -1545,7 +1545,7 @@ public extension http_parser { } case .s_chunk_size_start: - assert(self.nread == 1); + assert(self.nread == 1) assert(self.flags.contains(.F_CHUNKED)) let unhex_val = unhex[Int(ch)]; // (unsigned char) @@ -1554,7 +1554,7 @@ public extension http_parser { } self.content_length = Int(unhex_val) - UPDATE_STATE(.s_chunk_size); + UPDATE_STATE(.s_chunk_size) case .s_chunk_size: assert(self.flags.contains(.F_CHUNKED)) @@ -1565,7 +1565,7 @@ public extension http_parser { if unhex_val == -1 { if ch == cSEMICOLON || ch == cSPACE { - UPDATE_STATE(.s_chunk_parameters); + UPDATE_STATE(.s_chunk_parameters) break } @@ -1581,20 +1581,20 @@ public extension http_parser { return .Error(.INVALID_CONTENT_LENGTH) } - self.content_length = t; + self.content_length = t case .s_chunk_parameters: assert(self.flags.contains(.F_CHUNKED)) /* just ignore this shit. TODO check for overflow */ if ch == CR { - UPDATE_STATE(.s_chunk_size_almost_done); + UPDATE_STATE(.s_chunk_size_almost_done) } case .s_chunk_size_almost_done: assert(self.flags.contains(.F_CHUNKED)) guard STRICT_CHECK(ch != LF) else { return .Error(.STRICT) } - self.nread = 0; + self.nread = 0 if (self.content_length == 0) { _ = self.flags.insert(.F_TRAILING) @@ -1619,18 +1619,18 @@ public extension http_parser { * length and data pointers are managed this way. */ MARK(.Body) - self.content_length -= to_read; - p! += to_read - 1; + self.content_length -= to_read + p! += to_read - 1 if self.content_length == 0 { - UPDATE_STATE(.s_chunk_data_almost_done); + UPDATE_STATE(.s_chunk_data_almost_done) } case .s_chunk_data_almost_done: assert(self.flags.contains(.F_CHUNKED)) assert(self.content_length == 0) guard STRICT_CHECK(ch != CR) else { return .Error(.STRICT) } - UPDATE_STATE(.s_chunk_data_done); + UPDATE_STATE(.s_chunk_data_done) // CALLBACK_DATA(body); let rc = CALLBACK_DATA_(.Body, &body_mark, &CURRENT_STATE, settings, @@ -1641,7 +1641,7 @@ public extension http_parser { assert(self.flags.contains(.F_CHUNKED)) guard STRICT_CHECK(ch != LF) else { return .Error(.STRICT) } self.nread = 0 - UPDATE_STATE(.s_chunk_size_start); + UPDATE_STATE(.s_chunk_size_start) // CALLBACK_NOTIFY(chunk_complete); let rc = CALLBACK_NOTIFY(.ChunkComplete, &CURRENT_STATE, settings, From 96e2ee37a3284a2cc3e8c9a2c66dea05c751f301 Mon Sep 17 00:00:00 2001 From: Helge Hess Date: Fri, 24 Mar 2017 12:16:22 +0100 Subject: [PATCH 14/16] Add a README --- Noze.io.xcodeproj/project.pbxproj | 2 ++ Sources/dgram/README.md | 36 +++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 Sources/dgram/README.md diff --git a/Noze.io.xcodeproj/project.pbxproj b/Noze.io.xcodeproj/project.pbxproj index 0f5f3b5..823aa99 100644 --- a/Noze.io.xcodeproj/project.pbxproj +++ b/Noze.io.xcodeproj/project.pbxproj @@ -2173,6 +2173,7 @@ E8F2ACEE1CF64C19000B8CD1 /* JSONParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = JSONParser.swift; path = Sources/freddy/JSONParser.swift; sourceTree = ""; }; E8F2ACF11CF656F6000B8CD1 /* JSONSubscripting.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = JSONSubscripting.swift; path = Sources/freddy/JSONSubscripting.swift; sourceTree = ""; }; E8F2ACF41CF658A7000B8CD1 /* JSONEncodingDetector.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = JSONEncodingDetector.swift; path = Sources/freddy/JSONEncodingDetector.swift; sourceTree = ""; }; + E8F398F61E85353700204096 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; E8F75D2E1D58EA21001F7559 /* http_parser_settings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = http_parser_settings.swift; path = Sources/http_parser/http_parser_settings.swift; sourceTree = SOURCE_ROOT; }; E8FF78801CDA3657000256E2 /* URL.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = URL.swift; path = Sources/http/URL.swift; sourceTree = SOURCE_ROOT; }; /* End PBXFileReference section */ @@ -3273,6 +3274,7 @@ E8AF01C11E8006850074266E /* dgram */ = { isa = PBXGroup; children = ( + E8F398F61E85353700204096 /* README.md */, E8AF01C31E8006850074266E /* Module.swift */, E8AF01C21E8006850074266E /* Internals.swift */, E8AF01C41E8006850074266E /* Socket.swift */, diff --git a/Sources/dgram/README.md b/Sources/dgram/README.md new file mode 100644 index 0000000..5c101bd --- /dev/null +++ b/Sources/dgram/README.md @@ -0,0 +1,36 @@ +# Noze.io UDP / Datagram module + +An UDP/Datagram Sockets module modelled after the builtin Node +[dgram module](https://nodejs.org/api/dgram.html). + +### Example + +```Swift +import Foundation // for String/Data +import dgram +import console + +let sock = dgram.createSocket() +sock + .onListening { address in console.info ("dgram: bound to:", address) } + .onError { err in console.error("error:", err) } + .onMessage { (msg, from) in + sock.send(msg, to: from) // Echo back + } + .bind(10000) +``` + +### TODO + +- [ ] make the Datagram socket a proper stream (e.g. Duplex) +- [ ] sends are blocking and are not enqueued + +### Who + +Noze.io is brought to you by +[The Always Right Institute](http://www.alwaysrightinstitute.com) +and +[ZeeZide](http://zeezide.de). + +The `dgram` module was contributed by +[David Lichteblau](https://github.com/lichtblau). From 69b813cbdd20b26f5b9e39fd629b9f500e9552e2 Mon Sep 17 00:00:00 2001 From: Helge Hess Date: Fri, 24 Mar 2017 12:21:19 +0100 Subject: [PATCH 15/16] Work on README --- Sources/dgram/README.md | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/Sources/dgram/README.md b/Sources/dgram/README.md index 5c101bd..98679a2 100644 --- a/Sources/dgram/README.md +++ b/Sources/dgram/README.md @@ -1,29 +1,37 @@ # Noze.io UDP / Datagram module -An UDP/Datagram Sockets module modelled after the builtin Node +An UDP/datagram socket module modelled after the builtin Node [dgram module](https://nodejs.org/api/dgram.html). ### Example +Small UDP server which echos back packets it receives: + ```Swift -import Foundation // for String/Data import dgram -import console -let sock = dgram.createSocket() +sock = dgram.createSocket() sock - .onListening { address in console.info ("dgram: bound to:", address) } - .onError { err in console.error("error:", err) } .onMessage { (msg, from) in - sock.send(msg, to: from) // Echo back + sock.send(msg, to: from) // echo back } - .bind(10000) + .bind(1337) ``` +You can test that on Linux using + + nc.openbsd -u localhost 1337 + +and on macOS via + + nc -vu4 localhost 1337 + + ### TODO -- [ ] make the Datagram socket a proper stream (e.g. Duplex) -- [ ] sends are blocking and are not enqueued +- [ ] make the Datagram socket a proper stream (e.g. a + `Duplex`) +- [ ] sends are blocking and not queued ### Who From dbeb2805307f9a0854c67c630d9fa04cf893a8d7 Mon Sep 17 00:00:00 2001 From: Helge Hess Date: Fri, 24 Mar 2017 12:39:04 +0100 Subject: [PATCH 16/16] Travis: Build with swift-3.1-DEVELOPMENT-SNAPSHOT-2017-03-23-a --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index 6164980..ed9ce6b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,10 @@ matrix: dist: trusty env: SWIFT_SNAPSHOT_NAME="https://swift.org/builds/swift-3.0.2-release/ubuntu1404/swift-3.0.2-RELEASE/swift-3.0.2-RELEASE-ubuntu14.04.tar.gz" sudo: required + - os: Linux + dist: trusty + env: SWIFT_SNAPSHOT_NAME="https://swift.org/builds/swift-3.1-branch/ubuntu1404/swift-3.1-DEVELOPMENT-SNAPSHOT-2017-03-23-a/swift-3.1-DEVELOPMENT-SNAPSHOT-2017-03-23-a-ubuntu14.04.tar.gz" + sudo: required - os: osx osx_image: xcode8.2