ref: 960f567d8fa9d3a294e9b2c081f4eede5108b5d0
dir: /Sources/NineSwift/NineProtocol.swift/
//
// NineProtocol.swift
// 9p implementation
//
// Created by halfwit on 2024-01-03.
//
import Foundation
import Network
public var MSIZE: UInt32 = 8192
let version = "9P2000 ".data(using: .utf8)!
public enum NineErrors: Error {
case decodeError
case unknownType
case connectError
case success
}
public 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
}
public 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
}
public enum nineMode: UInt8, Codable {
case read = 0
case write = 1
case rdwr = 2
case exec = 3
case trunc = 0x10
case rclose = 0x40
}
public struct nineQid: Codable {
var type: fileType
var version: UInt32
var path: UInt64
}
public 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, *)
public class NineProtocol: NWProtocolFramerImplementation {
static let definition = NWProtocolFramer.Definition(implementation: NineProtocol.self)
public static var label: String { return "9p" }
required public init(framer: NWProtocolFramer.Instance) {}
public func start(framer: NWProtocolFramer.Instance) -> NWProtocolFramer.StartResult { return .ready }
public func wakeup(framer: NWProtocolFramer.Instance) { print("In wakeup")}
public func stop(framer: NWProtocolFramer.Instance) -> Bool { print("In stop"); return true }
public func cleanup(framer: NWProtocolFramer.Instance) { print("In cleanup")}
public func handleOutput(framer: NWProtocolFramer.Instance, message: NWProtocolFramer.Message, messageLength: Int, isComplete: Bool) {
do {
try framer.writeOutputNoCopy(length: messageLength)
} catch {
print("Heck didn't send")
}
}
public 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, *)
public 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["iounit"] 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, *)
public struct Tversion: QueueableMessage, Encodable {
public var minReceiveLength: Int = 13
public 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
}
public var context: NWConnection.ContentContext {
return NWConnection.ContentContext(identifier: "Tversion")
}
}
@available(macOS 10.15, *)
public struct Tauth: QueueableMessage, Encodable {
public 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)!
}
public 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
}
public var context: NWConnection.ContentContext {
return NWConnection.ContentContext(identifier: "Tauth")
}
}
@available(macOS 10.15, *)
public struct Tattach: QueueableMessage, Encodable {
public 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)!
}
public 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: fid)
w32(&data, input: afid)
wstr(&data, input: uname)
wstr(&data, input: aname)
return data
}
public var context: NWConnection.ContentContext {
return NWConnection.ContentContext(identifier: "Tattach")
}
}
@available(macOS 10.15, *)
public struct Tflush: QueueableMessage, Encodable {
public var minReceiveLength: Int = 7
let tag: UInt16
let oldtag: UInt16
init(tag: UInt16, oldtag: UInt16) {
self.tag = 0
self.oldtag = oldtag
}
public 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
}
public var context: NWConnection.ContentContext {
return NWConnection.ContentContext(identifier: "Tflush")
}
}
@available(macOS 10.15, *)
public struct Twalk: QueueableMessage, Encodable {
public 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
}
public 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
}
public var context: NWConnection.ContentContext {
return NWConnection.ContentContext(identifier: "Twalk")
}
}
@available(macOS 10.15, *)
public struct Topen: QueueableMessage, Encodable {
public 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
}
public 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
}
public var context: NWConnection.ContentContext {
return NWConnection.ContentContext(identifier: "Topen")
}
}
@available(macOS 10.15, *)
public struct Tcreate: QueueableMessage, Encodable {
public 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
}
public 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
}
public var context: NWConnection.ContentContext {
return NWConnection.ContentContext(identifier: "Tcreate")
}
}
@available(macOS 10.15, *)
public struct Tread: QueueableMessage, Encodable {
public 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
}
public 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
}
public var context: NWConnection.ContentContext {
return NWConnection.ContentContext(identifier: "Tread")
}
}
@available(macOS 10.15, *)
public struct Twrite: QueueableMessage, Encodable {
public 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
}
public 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
}
public var context: NWConnection.ContentContext {
return NWConnection.ContentContext(identifier: "Twrite")
}
}
@available(macOS 10.15, *)
public struct Tclunk: QueueableMessage, Encodable {
public var minReceiveLength: Int = 7
let tag: UInt16
let fid: UInt32
init(tag: UInt16, fid: UInt32) {
self.tag = tag
self.fid = fid
}
public 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
}
public var context: NWConnection.ContentContext {
return NWConnection.ContentContext(identifier: "Tclunk")
}
}
@available(macOS 10.15, *)
public struct Tremove: QueueableMessage, Encodable {
public var minReceiveLength: Int = 7
let tag: UInt16
let fid: UInt32
init(tag: UInt16, fid: UInt32) {
self.tag = tag
self.fid = fid
}
public 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
}
public var context: NWConnection.ContentContext {
return NWConnection.ContentContext(identifier: "Tremove")
}
}
@available(macOS 10.15, *)
public struct Tstat: QueueableMessage, Encodable {
public var minReceiveLength: Int = 52
let tag: UInt16
let fid: UInt32
init(tag: UInt16, fid: UInt32) {
self.tag = tag
self.fid = fid
}
public 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
}
public var context: NWConnection.ContentContext {
return NWConnection.ContentContext(identifier: "Tstat")
}
}
@available(macOS 10.15, *)
public struct Twstat: QueueableMessage, Encodable {
public 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
}
public 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.uid)
wstr(&data, input: stat.gid)
wstr(&data, input: stat.muid)
return data
}
public 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) {
w16(&data, input: UInt16(input.count))
if input.count == 0 {
return
}
for byte in input.reversed() {
data.append(byte)
}
}
func wdata(_ data: inout Data, input: Data) {
w32(&data, input: UInt32(input.count))
if input.count == 0 {
return
}
for byte in input.reversed() {
data.append(byte)
}
}
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)
}
private extension Data {
var bytes: [UInt8]
{
return [UInt8](self)
}
}
private extension UInt8 {
var char: Character {
return Character(UnicodeScalar(self))
}
}