ref: d2a973b953f6ccc33e493c3aa8eeeb4241f6dcc0
parent: fb6ad05b1adb3b52d0709d3a5218cdaaf4b01b11
author: halfwit <michaelmisch1985@gmail.com>
date: Sat May 18 15:13:05 PDT 2024
Initial commit
--- a/Package.swift
+++ b/Package.swift
@@ -15,7 +15,8 @@
// Targets are the basic building blocks of a package, defining a module or a test suite.
// Targets can depend on other targets in this package and products from dependencies.
.target(
- name: "NineSwift"),
+ name: "NineSwift",
+ path: "Sources"),
.testTarget(
name: "NineSwiftTests",
dependencies: ["NineSwift"]),
--- /dev/null
+++ b/Sources/NineSwift/Connection.swift
@@ -1,0 +1,251 @@
+//
+// Connection.swift
+// Altid - Connect with a service over 9p
+//
+// Created by halfwit on 2024-01-03.
+//
+
+import Foundation
+import Network
+
+@available(macOS 10.15, *)
+func applicationServiceParameters() -> NWParameters {
+ let tcpOptions = NWProtocolTCP.Options()
+ tcpOptions.enableKeepalive = true
+ tcpOptions.keepaliveIdle = 2
+ tcpOptions.keepaliveCount = 2
+ tcpOptions.keepaliveInterval = 2
+ tcpOptions.noDelay = true
+
+ let params: NWParameters = NWParameters(tls: nil, tcp: tcpOptions)
+ params.includePeerToPeer = true
+
+ let nineOptions = NWProtocolFramer.Options(definition: NineProtocol.definition)
+ params.defaultProtocolStack.applicationProtocols.insert(nineOptions, at: 0)
+
+ return params
+}
+
+@available(macOS 10.15, *)
+var sharedConnection: PeerConnection?
+
+@available(macOS 10.15, *)
+let wcb = NWConnection.SendCompletion.contentProcessed { cbe in
+ if cbe != nil {
+ print("Callback error encountered: \(String(describing: cbe))")
+ }
+}
+
+@available(macOS 10.15, *)
+protocol PeerConnectionDelegate: AnyObject {
+ func connectionReady()
+ func connectionFailed()
+ func displayAdvertiseError(_ error: NWError)
+}
+
+@available(macOS 10.15, *)
+class PeerConnection {
+ weak var delegate: PeerConnectionDelegate?
+ var connection: NWConnection?
+ let name: String
+ let initiatedConnection: Bool
+ var sendQueue = Queue<Enqueued>()
+ var handles: [Handle] = [Handle]()
+ var running: Bool = false
+
+ /* Connect to a service */
+ @available(macOS 10.15, *)
+ init(name: String, delegate: PeerConnectionDelegate) {
+ self.delegate = delegate
+ self.name = name
+ self.initiatedConnection = true
+
+ guard let endpointPort = NWEndpoint.Port("12345") else { return }
+ let endpoint = NWEndpoint.hostPort(host: NWEndpoint.Host("localhost"), port: endpointPort)
+ //let endpoint = NWEndpoint.hostPort(host: NWEndpoint.Host("127.0.0.1"), port: endpointPort)
+ //let endpoint = NWEndpoint.service(name: name, type: "_altid._tcp.", domain: "local.", interface: nil)
+ connection = NWConnection(to: endpoint, using: applicationServiceParameters())
+ }
+
+ func addHandle(handle: Handle) {
+ handles.append(handle)
+ }
+
+ func cancel() {
+ if let connection = self.connection {
+ connection.cancel()
+ self.connection = nil
+ }
+ }
+
+ // Handle starting the peer-to-peer connection for both inbound and outbound connections.
+ @available(macOS 10.15, *)
+ func startConnection() {
+ guard let connection = self.connection else {
+ return
+ }
+
+ connection.stateUpdateHandler = { [weak self] newState in
+ switch newState {
+ case .ready:
+
+ if let delegate = self?.delegate {
+ delegate.connectionReady()
+ }
+ case .failed(let error):
+ print("\(connection) failed with \(error)")
+ connection.cancel()
+ if let initiated = self?.initiatedConnection,
+ initiated && error == NWError.posix(.ECONNABORTED) {
+ // Reconnect if the user suspends the app on the nearby device.
+ guard let endpointPort = NWEndpoint.Port("12345") else { return }
+ let endpoint = NWEndpoint.hostPort(host: NWEndpoint.Host("192.168.0.248"), port: endpointPort)
+ //let endpoint = NWEndpoint.service(name: self!.name, type: "_altid._tcp", domain: "local", interface: nil)
+ let connection = NWConnection(to: endpoint, using: applicationServiceParameters())
+ self?.connection = connection
+ self?.startConnection()
+ } else if let delegate = self?.delegate {
+ delegate.connectionFailed()
+ }
+ default:
+ break
+ }
+ }
+
+ connection.start(queue: .main)
+ }
+}
+
+/* Utility functions */
+@available(macOS 10.15, *)
+extension PeerConnection {
+ func connect(uname: String = "") {
+ send(Tversion())
+ send(Tattach(fid: 0, afid: 0, uname: uname, aname: ""))
+ }
+
+ func run() {
+ if sendQueue.size > 0 {
+ _runjob()
+ }
+ }
+
+ func flush(_ handle: Handle) {
+ let tag = UInt16(handles.count > 1 ? handles.count : 1)
+ send(Tflush(tag: tag, oldtag: handle.tag))
+ }
+
+ func stat(_ handle: Handle, callback: @escaping (nineStat) -> Void) {
+ send(Tstat(tag: handle.tag, fid: handle.fid)) { (stat: nineStat) in
+ callback(stat)
+ }
+ }
+
+ func close(_ handle: Handle) {
+ if let index = self.handles.firstIndex(where: { $0.fid == handle.fid }) {
+ self.handles.remove(at: index)
+ }
+ send(Tclunk(tag: handle.tag, fid: handle.fid))
+ }
+
+ func open(_ wname: String, mode: nineMode, callback: @escaping (Handle) -> Void) {
+ var handle = Handle(fid: 1, tag: 0, name: wname)
+ Again:
+ for h in handles {
+ /* Walk it off the end of the chain, or pop it in a hole */
+ if h.fid == handle.fid {
+ handle.fid += 1
+ continue Again
+ }
+ if h.tag == handle.tag {
+ handle.tag += 1
+ continue Again
+ }
+ }
+ self.addHandle(handle: handle)
+ /* Be careful that we do not continue if we error here */
+ self.send(Twalk(fid: 0, newFid: handle.fid, wname: wname)) { (error: NineErrors) in
+ if(error == NineErrors.success) {
+ self.send(Topen(tag: handle.tag, fid: handle.fid, mode: mode)) { (msg: NWProtocolFramer.Message) in
+ /* iounit occassionally is misparsed in 9p */
+ handle.iounit = msg.iounit > 0 ? msg.iounit : 8168
+ callback(handle)
+ }
+ } else {
+ self.close(handle)
+ }
+ }
+ }
+
+ func read(_ handle: Handle, offset: UInt64 = 0, count: UInt32 = 8168, callback: @escaping (String) -> Void) {
+ send(Tread(tag: handle.tag, fid: handle.fid, offset: offset, count: count)) { (data: String) in
+ callback(data)
+ }
+ }
+
+ func write(_ handle: Handle, data: Data, offset: UInt64 = 0, callback: @escaping (NineErrors) -> Void) {
+ send(Twrite(tag: handle.tag, fid: handle.fid, offset: offset, count: UInt32(data.count), bytes: data)) { (error: NineErrors) in
+ callback(error)
+ }
+ }
+
+ private func send(_ message: QueueableMessage) {
+ _enqueue(message) { (msg, content, error) in }
+ }
+
+ private func send(_ message: QueueableMessage, callback: @escaping (NWProtocolFramer.Message) -> Void) {
+ _enqueue(message) { (msg, content, error) in
+ callback(msg)
+ }
+ }
+
+ private func send(_ message: QueueableMessage, callback: @escaping (String) -> Void) {
+ _enqueue(message) { (msg, content, error) in
+ guard let content = content else {
+ return
+ }
+ callback(String(decoding:content, as: UTF8.self))
+ }
+ }
+
+ private func send(_ message: QueueableMessage, callback: @escaping (NineErrors) -> Void) {
+ _enqueue(message) { (msg, content, error ) in
+ switch error {
+ case .none:
+ callback(.success)
+ case .some(_):
+ callback(.decodeError)
+ }
+ }
+ }
+
+ private func send(_ message: QueueableMessage, callback: @escaping (nineStat) -> Void) {
+ _enqueue(message) { (msg, content, error ) in
+ if let stat = msg.stat {
+ callback(stat)
+ }
+ }
+ }
+
+ private func _enqueue( _ message: QueueableMessage, callback: @escaping (NWProtocolFramer.Message, Data?, NWError?) -> Void) {
+ sendQueue.enqueue(Enqueued(message: message, action: callback))
+ }
+
+ func _runjob() {
+ guard let item = sendQueue.dequeue() else { return }
+ guard let connection = self.connection else { return }
+ connection.batch {
+ connection.send(content: item.message.encodedData, contentContext: item.message.context, isComplete: true, completion: .idempotent)
+ connection.receiveMessage { (content, context, isComplete, error) in
+ if let msg = context?.protocolMetadata(definition: NineProtocol.definition) as? NWProtocolFramer.Message {
+ if(msg.type == nineType.Rerror) {
+ print("Error encountered: \(String(decoding: content!, as: UTF8.self))")
+ } else {
+ item.action(msg, content, error)
+ }
+ self._runjob()
+ }
+ }
+ }
+ }
+}
--- /dev/null
+++ b/Sources/NineSwift/Handle.swift
@@ -1,0 +1,23 @@
+//
+// Handle.swift
+// Altid
+//
+// Created by halfwit on 2024-01-22.
+//
+
+import Foundation
+
+/* Handle to an open nine file */
+struct Handle {
+ let name: String
+ var fid: UInt32
+ var iounit: UInt32
+ var tag: UInt16
+
+ init(fid: UInt32, tag: UInt16, name: String) {
+ self.fid = fid
+ self.tag = tag
+ self.name = name
+ self.iounit = 8168
+ }
+}
--- /dev/null
+++ b/Sources/NineSwift/NineProtocol.swift
@@ -1,0 +1,931 @@
+//
+// NineProtocol.swift
+// Altid - 9p implementation
+//
+// Created by halfwit on 2024-01-03.
+//
+
+import Foundation
+import Network
+
+var MSIZE: UInt32 = 8192
+let version = "9P2000 ".data(using: .utf8)!
+
+enum NineErrors: Error {
+ case decodeError
+ case unknownType
+ case connectError
+ case success
+}
+
+enum nineType: UInt8 {
+ case Tversion = 100
+ case Tauth = 102
+ case Tattach = 104
+ case Tflush = 108
+ case Twalk = 110
+ case Topen = 112
+ case Tcreate = 114
+ case Tread = 116
+ case Twrite = 118
+ case Tclunk = 120
+ case Tremove = 122
+ case Tstat = 124
+ case Twstat = 126
+ case Rversion = 101
+ case Rauth = 103
+ case Rattach = 105
+ case Rerror = 107
+ case Rflush = 109
+ case Rwalk = 111
+ case Ropen = 113
+ case Rcreate = 115
+ case Rread = 117
+ case Rwrite = 119
+ case Rclunk = 121
+ case Rremove = 123
+ case Rstat = 125
+ case Rwstat = 127
+ case invalid = 0
+}
+
+enum fileType: UInt8, Codable {
+ case dir = 128
+ case append = 64
+ case excl = 32
+ case invalid = 16
+ case auth = 8
+ case tmp = 4
+ case file = 0
+}
+
+enum nineMode: UInt8, Codable {
+ case read = 0
+ case write = 1
+ case rdwr = 2
+ case exec = 3
+ case trunc = 0x10
+ case rclose = 0x40
+}
+
+struct nineQid: Codable {
+ var type: fileType
+ var version: UInt32
+ var path: UInt64
+}
+
+struct nineStat: Codable {
+ var size: UInt16
+ var type: UInt16
+ var dev: UInt32
+ var qid: nineQid
+ var mode: UInt32
+ var atime: UInt32
+ var mtime: UInt32
+ var length: UInt64
+ var name: Data
+ var uid: Data
+ var gid: Data
+ var muid: Data
+}
+
+// Main framing protocol
+@available(macOS 10.15, *)
+class NineProtocol: NWProtocolFramerImplementation {
+ static let definition = NWProtocolFramer.Definition(implementation: NineProtocol.self)
+ static var label: String { return "9p" }
+
+ required init(framer: NWProtocolFramer.Instance) {}
+ func start(framer: NWProtocolFramer.Instance) -> NWProtocolFramer.StartResult { return .ready }
+ func wakeup(framer: NWProtocolFramer.Instance) { print("In wakeup")}
+ func stop(framer: NWProtocolFramer.Instance) -> Bool { print("In stop"); return true }
+ func cleanup(framer: NWProtocolFramer.Instance) { print("In cleanup")}
+
+ func handleOutput(framer: NWProtocolFramer.Instance, message: NWProtocolFramer.Message, messageLength: Int, isComplete: Bool) {
+ do {
+ try framer.writeOutputNoCopy(length: messageLength)
+ } catch {
+ print("Heck didn't send")
+ }
+ }
+
+ func handleInput(framer: NWProtocolFramer.Instance) -> Int {
+ let headerSize = 7
+ var count: UInt32 = 0
+ var type: UInt8 = 0
+ var tag: UInt16 = 0
+ var dataSize: Int = 0
+
+ let parsed = framer.parseInput(minimumIncompleteLength: headerSize, maximumLength: headerSize) { (buffer, isComplete) -> Int in
+ guard let buffer = buffer else {
+ return 0
+ }
+ if buffer.isEmpty {
+ return 0
+ }
+ var offset = 0
+ count = r32(buffer: buffer, &offset)
+ type = r8(buffer: buffer, &offset)
+ tag = r16(buffer: buffer, &offset)
+ return offset
+ }
+ guard parsed else {
+ return count > 0 ? Int(count) : headerSize
+ }
+ let message = NWProtocolFramer.Message(count: count, type: type, tag: tag)
+ while true {
+ switch type {
+ case nineType.Rversion.rawValue:
+ let parsed = framer.parseInput(minimumIncompleteLength: 6, maximumLength: 6) { (buffer, isComplete) -> Int in
+ guard let buffer = buffer else {
+ return 0
+ }
+ var offset = 0
+ let msize = r32(buffer: buffer, &offset)
+ MSIZE = (msize != MSIZE) ? msize : MSIZE
+ dataSize = Int(r16(buffer: buffer, &offset))
+ return offset
+ }
+ guard parsed else {
+ return 6
+ }
+ case nineType.Rauth.rawValue:
+ // Not implemented
+ break
+ case nineType.Rattach.rawValue:
+ let parsed = framer.parseInput(minimumIncompleteLength: 13, maximumLength: 13) { (buffer, isComplete) -> Int in
+ guard let buffer = buffer else {
+ return 0
+ }
+ var offset = 0
+ let qid = rqid(buffer: buffer, &offset)
+ message.qids = [qid]
+ return offset
+ }
+ guard parsed else {
+ return 13
+ }
+ case nineType.Rerror.rawValue:
+ let parsed = framer.parseInput(minimumIncompleteLength: 2, maximumLength: 2) { (buffer, isComplete) -> Int in
+ guard let buffer = buffer else {
+ return 0
+ }
+ var offset = 0
+ dataSize = Int(r16(buffer: buffer, &offset))
+ return offset
+ }
+ guard parsed else {
+ return 2
+ }
+ case nineType.Rflush.rawValue:
+ break
+ case nineType.Rwalk.rawValue:
+ var total = 0
+ // 210: qid is 13 bytes. maxpathlen is 16. 2 for nwqid
+ let parsed = framer.parseInput(minimumIncompleteLength: 15, maximumLength: 210) { (buffer, isComplete) -> Int in
+ guard let buffer = buffer else {
+ return 0
+ }
+ var tmpqids = [nineQid]()
+ var offset = 0
+ let nwqid = r16(buffer: buffer, &offset)
+ total = Int(nwqid > 0 ? nwqid * 13 + 2 : 15)
+ if buffer.count < total {
+ return total
+ }
+ for _ in 1...nwqid {
+ let tmpqid = rqid(buffer: buffer, &offset)
+ tmpqids.append(tmpqid)
+ }
+ message.qids = tmpqids
+ return offset
+ }
+ guard parsed else {
+ return total
+ }
+ case nineType.Rcreate.rawValue:
+ fallthrough
+ case nineType.Ropen.rawValue:
+ let parsed = framer.parseInput(minimumIncompleteLength: 17, maximumLength: 17) { (buffer, isComplete) -> Int in
+ guard let buffer = buffer else {
+ return 0
+ }
+ var offset = 0
+ let qid = rqid(buffer: buffer, &offset)
+ message.qids = [qid]
+ message.iounit = r32(buffer: buffer, &offset)
+ return offset
+ }
+ guard parsed else {
+ return 17
+ }
+ case nineType.Rread.rawValue:
+ let parsed = framer.parseInput(minimumIncompleteLength: 4, maximumLength: 4) { (buffer, isComplete) -> Int in
+ guard let buffer = buffer else {
+ return 0
+ }
+ var offset = 0
+ dataSize = Int(r32(buffer: buffer, &offset))
+ return offset
+ }
+ guard parsed else {
+ return 4
+ }
+ case nineType.Rwrite.rawValue:
+ // TODO: How much we wrote returned, this is important for our isComplete stuff
+ let parsed = framer.parseInput(minimumIncompleteLength: 4, maximumLength: 4) { (buffer, isComplete) -> Int in
+ guard let buffer = buffer else {
+ return 0
+ }
+ var offset = 0
+ _ = r32(buffer: buffer, &offset)
+ return offset
+ }
+ guard parsed else {
+ return 4
+ }
+ case nineType.Rclunk.rawValue:
+ // 0 bytes back
+ break
+ case nineType.Rremove.rawValue:
+ break
+ case nineType.Rstat.rawValue:
+ var size: UInt16 = 0
+ let parsed = framer.parseInput(minimumIncompleteLength: 36, maximumLength: .max) { (buffer, isComplete) -> Int in
+ guard let buffer = buffer else {
+ return 0
+ }
+ var offset = 0
+ let msize = r16(buffer: buffer, &offset)
+ size = r16(buffer: buffer, &offset)
+ if buffer.count < msize {
+ return 0
+ }
+ let type = r16(buffer: buffer, &offset) // Used by kernel
+ let dev = r32(buffer: buffer, &offset) // Used by kernel
+ let qid = rqid(buffer: buffer, &offset)
+ let mode = r32(buffer: buffer, &offset)
+ let atime = r32(buffer: buffer, &offset)
+ let mtime = r32(buffer: buffer, &offset)
+ let length = r64(buffer: buffer, &offset)
+
+ let ncount = r16(buffer: buffer, &offset)
+ let name = rstr(buffer: buffer, count: ncount, &offset)
+ let ucount = r16(buffer: buffer, &offset)
+ let uid = rstr(buffer: buffer, count: ucount, &offset)
+ let gcount = r16(buffer: buffer, &offset)
+ let gid = rstr(buffer: buffer, count: gcount, &offset)
+ let mcount = r16(buffer: buffer, &offset)
+ let muid = rstr(buffer: buffer, count: mcount, &offset)
+
+ message.stat = nineStat(size: size, type: type, dev: dev, qid: qid, mode: mode, atime: atime, mtime: mtime, length: length, name: name, uid: uid, gid: gid, muid: muid)
+
+ return offset
+ }
+ guard parsed else {
+ return Int(size+2)
+ }
+ case nineType.Rwstat.rawValue:
+ break
+ default:
+ print("Unhandled Rmessage: \(type)")
+ return 0
+ }
+ if !framer.deliverInputNoCopy(length: dataSize, message: message, isComplete: true) {
+ return 0
+ }
+ print(message.count);
+ return Int(message.count)
+ }
+ }
+}
+
+@available(macOS 10.15, *)
+extension NWProtocolFramer.Message {
+ /* Set completions here */
+ convenience init(count: UInt32, type: UInt8, tag: UInt16, fid: UInt32 = 0, iounit: UInt32 = 0, qids: [nineQid]? = nil, stat: nineStat? = nil) {
+ self.init(definition: NineProtocol.definition)
+ self["count"] = count
+ self["type"] = type
+ self["tag"] = tag
+ self["fid"] = fid
+ self["iounit"] = iounit
+ self["qids"] = qids
+ self["stat"] = stat
+ }
+
+ var count: UInt32 {
+ if let val = self["count"] as? UInt32 {
+ return val
+ }
+ return 0
+ }
+
+ var type: nineType {
+ if let val = self["type"] as? UInt8 {
+ return nineType(rawValue: val) ?? .invalid
+ }
+ return .invalid
+ }
+
+ var tag: UInt16 {
+ if let val = self["tag"] as? UInt16 {
+ return val
+ }
+ return 0
+ }
+
+ var fid: UInt32 {
+ if let val = self["fid"] as? UInt32 {
+ return val
+ }
+ return 0
+ }
+
+ var iounit: UInt32 {
+ get {
+ if let val = self["fid"] as? UInt32 {
+ return val
+ }
+ return 0
+ }
+ set {
+ self["iounit"] = newValue
+ }
+ }
+
+ var qids: [nineQid]? {
+ get {
+ if let qids = self["qids"] as? [nineQid] {
+ return qids
+ }
+ return nil
+ }
+ set {
+ self["qids"] = newValue
+ }
+ }
+ var stat: nineStat? {
+ get {
+ if let stat = self["stat"] as? nineStat {
+ return stat
+ }
+ return nil
+ }
+ set {
+ self["stat"] = newValue
+ }
+ }
+}
+
+@available(macOS 10.15, *)
+struct Tversion: QueueableMessage, Encodable {
+ var minReceiveLength: Int = 13
+
+ var encodedData: Data {
+ let count: UInt32 = UInt32(13 + version.count)
+ var data = Data(count: 0)
+ w32(&data, input: count) // length
+ w8(&data, input: nineType.Tversion.rawValue) // type
+ w16(&data, input: 0) // tag
+ w32(&data, input: MSIZE)
+ wstr(&data, input: version)
+ return data
+ }
+
+ var context: NWConnection.ContentContext {
+ return NWConnection.ContentContext(identifier: "Tversion")
+ }
+}
+
+@available(macOS 10.15, *)
+struct Tauth: QueueableMessage, Encodable {
+ var minReceiveLength: Int = 20
+
+ let length: UInt32
+ let afid: UInt32
+ let uname: Data
+ let aname: Data
+
+ init(afid: UInt32, uname: String, aname: String) {
+
+ let size = aname.count+2 + uname.count+2 + 4+1+2+4
+ self.length = UInt32(size)
+ self.afid = 0xFFFF
+ self.uname = uname.data(using: .utf8)!
+ self.aname = aname.data(using: .utf8)!
+ }
+
+ var encodedData: Data {
+ var data = Data(count: 0)
+ w32(&data, input: length)
+ w8(&data, input: nineType.Tauth.rawValue)
+ w16(&data, input: 0)
+ w32(&data, input: afid)
+ wstr(&data, input: uname)
+ wstr(&data, input: aname)
+ return data
+ }
+
+ var context: NWConnection.ContentContext {
+ return NWConnection.ContentContext(identifier: "Tauth")
+ }
+}
+
+@available(macOS 10.15, *)
+struct Tattach: QueueableMessage, Encodable {
+ var minReceiveLength: Int = 20
+
+ let length: UInt32
+ let fid: UInt32
+ let afid: UInt32
+ let uname: Data
+ let aname: Data
+
+ init(fid: UInt32, afid: UInt32, uname: String, aname: String) {
+
+ let size = aname.count+2 + uname.count+2 + 4+1+2+4+4
+ self.length = UInt32(size)
+ self.fid = fid
+ self.afid = afid /* No auth? Have a global or so to switch */
+ self.uname = uname.data(using: .utf8)!
+ self.aname = aname.data(using: .utf8)!
+ }
+
+ var encodedData: Data {
+ var data = Data(count: 0)
+ w32(&data, input: length)
+ w8(&data, input: nineType.Tattach.rawValue)
+ w16(&data, input: 0)
+ w32(&data, input: 0)
+ w32(&data, input: afid)
+ wstr(&data, input: uname)
+ wstr(&data, input: aname)
+ return data
+ }
+
+ var context: NWConnection.ContentContext {
+ return NWConnection.ContentContext(identifier: "Tattach")
+ }
+}
+
+@available(macOS 10.15, *)
+struct Tflush: QueueableMessage, Encodable {
+ var minReceiveLength: Int = 7
+
+ let tag: UInt16
+ let oldtag: UInt16
+ init(tag: UInt16, oldtag: UInt16) {
+ self.tag = 0
+ self.oldtag = oldtag
+ }
+
+ var encodedData: Data {
+ var data = Data(count: 0)
+ w32(&data, input: 11)
+ w8(&data, input: nineType.Tflush.rawValue)
+ w16(&data, input: tag)
+ w16(&data, input: oldtag)
+ return data
+ }
+
+ var context: NWConnection.ContentContext {
+ return NWConnection.ContentContext(identifier: "Tflush")
+ }
+}
+
+@available(macOS 10.15, *)
+struct Twalk: QueueableMessage, Encodable {
+ var minReceiveLength: Int = 22
+
+ let length: UInt32
+ let tag: UInt16
+ let fid: UInt32
+ let newFid: UInt32
+ let nwnames: UInt16
+ let wnames: [Data]
+
+ init(fid: UInt32, newFid: UInt32, wname: String) {
+ var size = 17
+ self.tag = 0
+ self.fid = fid
+ self.newFid = newFid
+ let names = wname.split(separator: "/")
+ var tmpwnames = [Data]()
+ for name in names {
+ tmpwnames.append(name.data(using: .utf8)!)
+ size += name.count + 2
+ }
+ self.length = UInt32(size)
+ self.nwnames = UInt16(tmpwnames.count)
+ self.wnames = tmpwnames
+ }
+
+ var encodedData: Data {
+ var data = Data(count: 0)
+ w32(&data, input: length)
+ w8(&data, input: nineType.Twalk.rawValue)
+ w16(&data, input: tag)
+ w32(&data, input: fid)
+ w32(&data, input: newFid)
+ w16(&data, input: nwnames)
+ for wname in wnames {
+ wstr(&data, input: wname)
+ }
+ return data
+ }
+
+ var context: NWConnection.ContentContext {
+ return NWConnection.ContentContext(identifier: "Twalk")
+ }
+}
+
+@available(macOS 10.15, *)
+struct Topen: QueueableMessage, Encodable {
+ var minReceiveLength: Int = 24
+
+ let tag: UInt16
+ let fid: UInt32
+ let mode: nineMode
+
+ init(tag: UInt16, fid: UInt32, mode: nineMode) {
+ self.tag = tag
+ self.fid = fid
+ self.mode = mode
+ }
+
+ var encodedData: Data {
+ var data = Data(count: 0)
+ w32(&data, input: 4+1+2+4+1)
+ w8(&data, input: nineType.Topen.rawValue)
+ w16(&data, input: tag)
+ w32(&data, input: fid)
+ w8(&data, input: mode.rawValue)
+ return data
+ }
+
+ var context: NWConnection.ContentContext {
+ return NWConnection.ContentContext(identifier: "Topen")
+ }
+}
+
+@available(macOS 10.15, *)
+struct Tcreate: QueueableMessage, Encodable {
+ var minReceiveLength: Int = 24
+
+ let tag: UInt16
+ let fid: UInt32
+ let name: Data
+ let perm: UInt32
+ let mode: UInt8
+
+ init(tag: UInt16, fid: UInt32, name: String, perm: UInt32, mode: UInt8) {
+ self.tag = tag
+ self.fid = fid
+ self.name = name.data(using: .utf8)!
+ self.perm = perm
+ self.mode = mode
+ }
+
+ var encodedData: Data {
+ var data = Data(count: 0)
+ w32(&data, input: UInt32(name.count + 16))
+ w8(&data, input: nineType.Tcreate.rawValue)
+ w16(&data, input: tag)
+ w32(&data, input: fid)
+ wstr(&data, input: name)
+ w32(&data, input: perm)
+ w8(&data, input: mode)
+ return data
+ }
+ var context: NWConnection.ContentContext {
+ return NWConnection.ContentContext(identifier: "Tcreate")
+ }
+}
+
+@available(macOS 10.15, *)
+struct Tread: QueueableMessage, Encodable {
+ var minReceiveLength: Int = 13
+
+ let tag: UInt16
+ let fid: UInt32
+ let offset: UInt64
+ let count: UInt32
+
+ init(tag: UInt16, fid: UInt32, offset: UInt64, count: UInt32) {
+ self.tag = tag
+ self.fid = fid
+ self.offset = offset
+ self.count = count
+ }
+
+ var encodedData: Data {
+ var data = Data(count: 0)
+ w32(&data, input: 4+1+2+4+8+4)
+ w8(&data, input: nineType.Tread.rawValue)
+ w16(&data, input: tag)
+ w32(&data, input: fid)
+ w64(&data, input: offset)
+ w32(&data, input: count)
+ return data
+ }
+
+ var context: NWConnection.ContentContext {
+ return NWConnection.ContentContext(identifier: "Tread")
+ }
+}
+
+@available(macOS 10.15, *)
+struct Twrite: QueueableMessage, Encodable {
+ var minReceiveLength: Int = 11
+
+ let tag: UInt16
+ let fid: UInt32
+ let offset: UInt64
+ let count: UInt32
+ let bytes: Data
+
+ init(tag: UInt16, fid: UInt32, offset: UInt64, count: UInt32, bytes: Data) {
+ self.tag = tag
+ self.fid = fid
+ self.offset = offset
+ self.count = count
+ self.bytes = bytes
+ }
+
+ var encodedData: Data {
+ var data = Data(count: 0)
+ w32(&data, input: UInt32(bytes.count + 23))
+ w8(&data, input: nineType.Twrite.rawValue)
+ w16(&data, input: tag)
+ w32(&data, input: fid)
+ w64(&data, input: offset)
+ wdata(&data, input: bytes)
+ return data
+ }
+
+ var context: NWConnection.ContentContext {
+ return NWConnection.ContentContext(identifier: "Twrite")
+ }
+}
+
+@available(macOS 10.15, *)
+struct Tclunk: QueueableMessage, Encodable {
+ var minReceiveLength: Int = 7
+
+ let tag: UInt16
+ let fid: UInt32
+ init(tag: UInt16, fid: UInt32) {
+ self.tag = tag
+ self.fid = fid
+ }
+
+ var encodedData: Data {
+ var data = Data(count: 0)
+ w32(&data, input: 11)
+ w8(&data, input: nineType.Tclunk.rawValue)
+ w16(&data, input: tag)
+ w32(&data, input: fid)
+ return data
+ }
+
+ var context: NWConnection.ContentContext {
+ return NWConnection.ContentContext(identifier: "Tclunk")
+ }
+}
+
+@available(macOS 10.15, *)
+struct Tremove: QueueableMessage, Encodable {
+ var minReceiveLength: Int = 7
+
+ let tag: UInt16
+ let fid: UInt32
+ init(tag: UInt16, fid: UInt32) {
+ self.tag = tag
+ self.fid = fid
+ }
+
+ var encodedData: Data {
+ var data = Data(count: 0)
+ w32(&data, input: 11)
+ w8(&data, input: nineType.Tremove.rawValue)
+ w16(&data, input: tag)
+ w32(&data, input: fid)
+ return data
+ }
+
+ var context: NWConnection.ContentContext {
+ return NWConnection.ContentContext(identifier: "Tremove")
+ }
+}
+
+@available(macOS 10.15, *)
+struct Tstat: QueueableMessage, Encodable {
+ var minReceiveLength: Int = 52
+
+ let tag: UInt16
+ let fid: UInt32
+ init(tag: UInt16, fid: UInt32) {
+ self.tag = tag
+ self.fid = fid
+ }
+
+ var encodedData: Data {
+ var data = Data(count: 0)
+ w32(&data, input: 11)
+ w8(&data, input: nineType.Tstat.rawValue)
+ w16(&data, input: tag)
+ w32(&data, input: fid)
+ return data
+ }
+
+ var context: NWConnection.ContentContext {
+ return NWConnection.ContentContext(identifier: "Tstat")
+ }
+}
+
+@available(macOS 10.15, *)
+struct Twstat: QueueableMessage, Encodable {
+ var minReceiveLength: Int = 7
+
+ let tag: UInt16
+ let fid: UInt32
+ let stat: nineStat
+
+ init(tag: UInt16, fid: UInt32, stat: nineStat) {
+ self.tag = tag
+ self.fid = fid
+ self.stat = stat
+ }
+
+ var encodedData: Data {
+ var data = Data(count: 0)
+ let length = 52 + stat.name.count + stat.uid.count + stat.gid.count + stat.muid.count
+ w32(&data, input: UInt32(length))
+ w8(&data, input: nineType.Twstat.rawValue)
+ w16(&data, input: tag)
+ w32(&data, input: fid)
+ w16(&data, input: stat.size)
+ w16(&data, input: stat.type)
+ w32(&data, input: stat.dev)
+ w8(&data, input: stat.qid.type.rawValue)
+ w32(&data, input: stat.qid.version)
+ w64(&data, input: stat.qid.path)
+ w32(&data, input: stat.mode)
+ w32(&data, input: stat.atime)
+ w32(&data, input: stat.mtime)
+ w64(&data, input: stat.length)
+ wstr(&data, input: stat.name)
+ wstr(&data, input: stat.name)
+ wstr(&data, input: stat.uid)
+ wstr(&data, input: stat.gid)
+ wstr(&data, input: stat.muid)
+ return data
+ }
+
+ var context: NWConnection.ContentContext {
+ return NWConnection.ContentContext(identifier: "Twstat")
+ }
+}
+
+/* Utility functions */
+func w8(_ data: inout Data, input: UInt8) {
+ var tempInput = input.littleEndian
+ data.append(Data(bytes: &tempInput, count: MemoryLayout<UInt8>.size))
+}
+
+func w16(_ data: inout Data, input: UInt16) {
+ w8(&data, input: UInt8(input & 0x00ff))
+ w8(&data, input: UInt8(input >> 8))
+}
+
+func w32(_ data: inout Data, input: UInt32) {
+ w16(&data, input: UInt16(input & 0x0000ffff))
+ w16(&data, input: UInt16(input >> 16))
+}
+
+func w64(_ data: inout Data, input: UInt64) {
+ w32(&data, input: UInt32(input & 0x00000000ffffffff))
+ w32(&data, input: UInt32(input >> 32))
+}
+
+func wstr(_ data: inout Data, input: Data) {
+ let rev = input.reversed()
+ w16(&data, input: UInt16(input.count))
+ if input.count == 0 {
+ return
+ }
+ // Write out the rest of our bytes
+ for i in 0...rev.count - 1 {
+ var tempInput = input[i]
+ data.append(Data(bytes: &tempInput, count: MemoryLayout<UInt8>.size))
+
+ }
+}
+
+func wdata(_ data: inout Data, input: Data) {
+ let rev = input.reversed()
+ // Write out the rest of our bytes
+ w32(&data, input: UInt32(input.count))
+ if input.count == 0 {
+ return
+ }
+ for i in 0...rev.count - 1 {
+ var tempInput = input[i]
+ data.append(Data(bytes: &tempInput, count: MemoryLayout<UInt8>.size))
+
+ }
+}
+
+func r8(buffer: UnsafeMutableRawBufferPointer, _ base: inout Int) -> UInt8 {
+ defer {
+ base += 1
+ }
+ return buffer[base]
+}
+
+func r16(buffer: UnsafeMutableRawBufferPointer, _ base: inout Int) -> UInt16 {
+ defer {
+ base += 2
+ }
+ var bytes = [UInt8]()
+ bytes.append(buffer[base])
+ bytes.append(buffer[base+1])
+ let tempVar = bytes.withUnsafeBytes { $0.load(as: UInt16.self) }
+ return tempVar
+}
+
+func r32(buffer: UnsafeMutableRawBufferPointer, _ base: inout Int) -> UInt32 {
+ defer {
+ base += 4
+ }
+ var bytes = [UInt8]()
+ bytes.append(buffer[base])
+ bytes.append(buffer[base+1])
+ bytes.append(buffer[base+2])
+ bytes.append(buffer[base+3])
+ let tempVar = bytes.withUnsafeBytes { $0.load(as: UInt32.self) }
+ return tempVar
+}
+
+/* This is backwards a bit, sooooo */
+func r64(buffer: UnsafeMutableRawBufferPointer, _ base: inout Int) -> UInt64 {
+ defer {
+ base += 8
+ }
+ var bytes = [UInt8]()
+ bytes.append(buffer[base])
+ bytes.append(buffer[base+1])
+ bytes.append(buffer[base+2])
+ bytes.append(buffer[base+3])
+ bytes.append(buffer[base+4])
+ bytes.append(buffer[base+5])
+ bytes.append(buffer[base+6])
+ bytes.append(buffer[base+7])
+ let tempVar = bytes.withUnsafeBytes { $0.load(as: UInt64.self )}
+ return tempVar
+}
+
+func rstr(buffer: UnsafeMutableRawBufferPointer, count: UInt16, _ base: inout Int) -> Data {
+ var data = Data(count: 0)
+ if count < 1 {
+ return data
+ }
+ for _ in 1...count {
+ data.append(buffer[base])
+ base += MemoryLayout<UInt8>.size
+ }
+ return data
+}
+
+func rdata(buffer: UnsafeMutableRawBufferPointer, count: UInt32, _ base: inout Int) -> Data {
+ var data = Data(count: 0)
+ if count < 1 {
+ return data
+ }
+ for _ in 1...count {
+ data.append(buffer[base])
+ base += MemoryLayout<UInt8>.size
+ }
+ return data
+}
+
+func rqid(buffer: UnsafeMutableRawBufferPointer, _ base: inout Int) -> nineQid {
+ let type = r8(buffer: buffer, &base)
+ let vers = r32(buffer: buffer, &base)
+ let path = r64(buffer: buffer, &base)
+ return nineQid(type: fileType(rawValue: type) ?? .invalid, version: vers, path: path)
+}
+
+extension Data {
+ public var bytes: [UInt8]
+ {
+ return [UInt8](self)
+ }
+}
+
+extension UInt8 {
+ var char: Character {
+ return Character(UnicodeScalar(self))
+ }
+}
--- /dev/null
+++ b/Sources/NineSwift/Queue.swift
@@ -1,0 +1,46 @@
+//
+// Queue.swift
+// Altid
+//
+// Created by halfwit on 2024-01-22.
+//
+import Foundation
+import Network
+
+struct Queue<T> {
+ private var elements: [T] = []
+
+ mutating func enqueue(_ value: T) {
+ elements.append(value)
+ }
+
+ mutating func dequeue() -> T? {
+ guard !elements.isEmpty else {
+ return nil
+ }
+ return elements.removeFirst()
+ }
+
+ var size: Int {
+ get {
+ return elements.count
+ }
+ }
+}
+
+@available(macOS 10.15, *)
+protocol QueueableMessage {
+ var encodedData: Data {get}
+ var minReceiveLength: Int {get}
+ var context: NWConnection.ContentContext {get}
+}
+
+@available(macOS 10.15, *)
+struct Enqueued {
+ let message: QueueableMessage
+ let action: (NWProtocolFramer.Message, Data?, NWError?) -> Void
+ init(message: QueueableMessage, action: @escaping (NWProtocolFramer.Message, Data?, NWError?) -> Void) {
+ self.message = message
+ self.action = action
+ }
+}