From 7341ead2aade10ea1b833e94275277658741883a Mon Sep 17 00:00:00 2001 From: Edoardo La Greca Date: Mon, 18 Aug 2025 21:09:11 +0200 Subject: switch to multi-module project structure --- src/main/kotlin/Authenticator.kt | 18 -- src/main/kotlin/Authenticator9PAnyV2DP9IK.kt | 12 - src/main/kotlin/Connection.kt | 321 --------------------- src/main/kotlin/FileMode.kt | 32 -- src/main/kotlin/FilePermissions.kt | 92 ------ src/main/kotlin/IPAddress.kt | 73 ----- src/main/kotlin/NinePMacros.kt | 6 - src/main/kotlin/NinePMessageType.kt | 39 --- src/main/kotlin/PathInfo.kt | 93 ------ src/main/kotlin/ProtocolTranslator.kt | 167 ----------- src/main/kotlin/QID.kt | 105 ------- src/main/kotlin/SizedInteger.kt | 36 --- src/main/kotlin/Stat.kt | 151 ---------- src/main/kotlin/TagGenerator.kt | 63 ---- src/main/kotlin/Utils.kt | 38 --- src/main/kotlin/demo/Main.kt | 23 -- .../kotlin/except/FailedAuthenticationException.kt | 8 - src/main/kotlin/except/InvalidMessageException.kt | 8 - .../kotlin/except/MsizeValueTooBigException.kt | 10 - src/main/kotlin/except/RErrorException.kt | 8 - .../kotlin/except/UnaccessibleFileException.kt | 11 - src/main/kotlin/except/UnknownVersionException.kt | 9 - .../kotlin/except/UnresolvableHostException.kt | 8 - src/main/kotlin/net/InMessage.kt | 167 ----------- src/main/kotlin/net/OutMessage.kt | 127 -------- src/main/kotlin/net/TransportLayer.kt | 59 ---- src/main/kotlin/net/TransportLayerJavaNet.kt | 95 ------ 27 files changed, 1779 deletions(-) delete mode 100644 src/main/kotlin/Authenticator.kt delete mode 100644 src/main/kotlin/Authenticator9PAnyV2DP9IK.kt delete mode 100644 src/main/kotlin/Connection.kt delete mode 100644 src/main/kotlin/FileMode.kt delete mode 100644 src/main/kotlin/FilePermissions.kt delete mode 100644 src/main/kotlin/IPAddress.kt delete mode 100644 src/main/kotlin/NinePMacros.kt delete mode 100644 src/main/kotlin/NinePMessageType.kt delete mode 100644 src/main/kotlin/PathInfo.kt delete mode 100644 src/main/kotlin/ProtocolTranslator.kt delete mode 100644 src/main/kotlin/QID.kt delete mode 100644 src/main/kotlin/SizedInteger.kt delete mode 100644 src/main/kotlin/Stat.kt delete mode 100644 src/main/kotlin/TagGenerator.kt delete mode 100644 src/main/kotlin/Utils.kt delete mode 100644 src/main/kotlin/demo/Main.kt delete mode 100644 src/main/kotlin/except/FailedAuthenticationException.kt delete mode 100644 src/main/kotlin/except/InvalidMessageException.kt delete mode 100644 src/main/kotlin/except/MsizeValueTooBigException.kt delete mode 100644 src/main/kotlin/except/RErrorException.kt delete mode 100644 src/main/kotlin/except/UnaccessibleFileException.kt delete mode 100644 src/main/kotlin/except/UnknownVersionException.kt delete mode 100644 src/main/kotlin/except/UnresolvableHostException.kt delete mode 100644 src/main/kotlin/net/InMessage.kt delete mode 100644 src/main/kotlin/net/OutMessage.kt delete mode 100644 src/main/kotlin/net/TransportLayer.kt delete mode 100644 src/main/kotlin/net/TransportLayerJavaNet.kt (limited to 'src/main') diff --git a/src/main/kotlin/Authenticator.kt b/src/main/kotlin/Authenticator.kt deleted file mode 100644 index 932004f..0000000 --- a/src/main/kotlin/Authenticator.kt +++ /dev/null @@ -1,18 +0,0 @@ -/** - * The Authenticator interface provides methods for authenticating a user over an established protocol connection. - */ -interface Authenticator { - /** - * Authenticate a user identified by the given [username] and whose authenticity is confirmed by the given - * [password]. The authentication protocol can read and write data within the underlying connection using [readFun] - * and [writeFun]. - * - * @param username The name the user goes by. - * @param password The confirmation of the user's authenticity. - * @param readFun A function to read incoming data from the underlying connection. - * @param writeFun A function to write outgoing data into the underlying connection. - * @throws except.FailedAuthenticationException if the authentication could not be performed. A human-readable - * reason for the failure can be provided if necessary. - */ - fun authenticate(username: String, password: String, readFun: () -> List, writeFun: (b: List) -> Unit) -} \ No newline at end of file diff --git a/src/main/kotlin/Authenticator9PAnyV2DP9IK.kt b/src/main/kotlin/Authenticator9PAnyV2DP9IK.kt deleted file mode 100644 index 99c52a0..0000000 --- a/src/main/kotlin/Authenticator9PAnyV2DP9IK.kt +++ /dev/null @@ -1,12 +0,0 @@ -/** - * This class (with an ugly ass name) implements the authentication procedure for the p9any meta-protocol version 2, - * hinting at the usage of dp9ik during negotiation and failing if it's unavailable. - * - * The 9P protocol does not provide a default authentication method. However, since NineKt must work with 9front's - * default authenticated 9P service, it must implement the p9any meta-protocol, preferably version 2. - */ -class Authenticator9PAnyV2DP9IK : Authenticator { - override fun authenticate(username: String, password: String, readFun: () -> List, writeFun: (List) -> Unit) { - - } -} \ No newline at end of file diff --git a/src/main/kotlin/Connection.kt b/src/main/kotlin/Connection.kt deleted file mode 100644 index f2cdd15..0000000 --- a/src/main/kotlin/Connection.kt +++ /dev/null @@ -1,321 +0,0 @@ -import except.MsizeValueTooBigException -import except.RErrorException -import except.UnaccessibleFileException -import except.UnknownVersionException -import net.InMessage -import net.OutMessage -import net.TransportLayer -import java.io.IOException -import java.math.BigInteger - -/** - * This class represents a 9P connection. It provides a practical implementation with networking to the 9P methods - * described in [ProtocolTranslator]. Details about methods related to 9P can be found in [ProtocolTranslator]. Remember - * to disconnect using [disconnect] after use. - * - * Details about network-related topics can be found in [net.TransportLayer] and the implementation of choice. - * - * Details about 9P messages and methods can be found in [ProtocolTranslator]. - * - * @param transLay The networking API backend of choice. - * @throws except.UnresolvableHostException if the host resolution made by [transLay] failed. - */ -class Connection(transLay: TransportLayer) : ProtocolTranslator { - /** - * Networking API. - */ - private val tl: TransportLayer = transLay - - /** - * Tag generator. - */ - private val tagGen = TagGenerator(TagGenerator.TagGenerationMethod.INCREMENTAL, 1u) - - /** - * Maximum size for messages negotiated between the client and the server. - */ - private var maxSize: UInt = 0u - - // 9P constants. - val DEFAULT_VERSION = "9P2000" - val NOTAG = 0.toUShort().inv() - val NOFID = 0.toUInt().inv() - - - /** - * Disconnect from the remote host, - * - * @throws IOException if an I/O error occurred while closing the socket. - */ - fun disconnect() { - this.tl.close() - } - - /** - * Handy function to create an [net.InMessage] instance and check for errors. After successfully using this - * function, it is guaranteed that both no error occurred while reading the incoming message and the message is not - * of type R-error. - * - * It uses [tl] and [maxSize] for instancing the [net.InMessage] class. - * - * @return A pair of: (1) a nullable string (which can be: `null` if no error occurred, empty if an error occurred - * with no message, or non-empty with the error message) and (2) the optional [net.InMessage] instance (null if an - * error occurred). - * @throws except.InvalidMessageException if the received message is invalid. - * @throws except.RErrorException if the received message is an R-error message. - */ - private fun checkedInMessage(reqTag: UShort): InMessage { - val imsg = InMessage(this.tl, this.maxSize, reqTag) - if (imsg.type == NinePMessageType.RERROR) { - imsg.applyField(InMessage.Field("ename", InMessage.Field.Type.STRING, 0u)) - throw RErrorException(imsg.fieldsStr["ename"]) - } - return imsg - } - - override fun version(msize: UInt, version: String) { - val omsg = OutMessage(NinePMessageType.TVERSION, this.NOTAG, listOf("msize", "version"), - mapOf( - "msize" to Pair(BigInteger(msize.toString()), 4u) - ), - mapOf( - "version" to version - ), - emptyMap(), - this.maxSize - ) - omsg.write(this.tl) - val imsg = checkedInMessage(omsg.tag) - imsg.applySchema(listOf( - InMessage.Field("msize", InMessage.Field.Type.INTEGER, 4u), - InMessage.Field("version", InMessage.Field.Type.STRING, 0u) - )) - val remoteMaxSize = imsg.fieldsInt["msize"]!!.toInt().toUInt() - if (remoteMaxSize > this.maxSize) { - throw MsizeValueTooBigException(msize, remoteMaxSize) - } - this.maxSize = remoteMaxSize - if (!imsg.fieldsStr["version"]!!.startsWith("9P2000")) { - throw UnknownVersionException(imsg.fieldsStr["version"]!!) - } - } - - override fun auth(afid: UInt, uname: String, aname: String) { - // TODO: Leave this unimplemented until p9any and TLS are implemented - TODO("Not yet implemented") - } - - override fun flush(oldtag: UShort) { - val omsg = OutMessage(NinePMessageType.TFLUSH, this.tagGen.generate(), listOf("oldtag"), - mapOf( - "oldtag" to Pair(BigInteger(oldtag.toString()), 2u) - ), - emptyMap(), - emptyMap(), - this.maxSize - ) - omsg.write(this.tl) - val imsg = checkedInMessage(omsg.tag) - } - - override fun attach(fid: UInt, afid: UInt, uname: String, aname: String): QID { - val omsg = OutMessage(NinePMessageType.TATTACH, this.tagGen.generate(), listOf("fid", "afid", "uname", "aname"), - mapOf( - "fid" to Pair(BigInteger(fid.toString()), 4u), - "afid" to Pair(BigInteger(afid.toString()), 4u) - ), - mapOf( - "uname" to uname, - "aname" to aname - ), - emptyMap(), - this.maxSize - ) - omsg.write(this.tl) - val imsg = checkedInMessage(omsg.tag) - - imsg.applyField(InMessage.Field("qid", InMessage.Field.Type.RAW, 13u)) - val qid = QID(imsg.fieldsRaw["qid"]!!.toList()) - return qid - } - - override fun walk(fid: UInt, newfid: UInt, wname: List): List { - val nwname = wname.size - val omsg = OutMessage(NinePMessageType.TWALK, this.tagGen.generate(), listOf("fid", "newfid", "nwname"), - mapOf( - "fid" to Pair(BigInteger(fid.toString()), 4u), - "newfid" to Pair(BigInteger(newfid.toString()), 4u), - "nwname" to Pair(BigInteger(nwname.toString()), 2u) - ), - emptyMap(), - emptyMap(), - this.maxSize - ) - omsg.write(this.tl) - for (wn in wname) { // write wname elements - this.tl.transmit(OutMessage.convStringToBytes(wn)) - } - val imsg = checkedInMessage(omsg.tag) - - imsg.applyField(InMessage.Field("nwqid", InMessage.Field.Type.INTEGER, 2u)) - val nwqid = imsg.fieldsInt["nwqid"]!!.toInt() - if (nwqid < nwname) { - throw UnaccessibleFileException(wname.slice(0..nwqid)) - } - imsg.applyField(InMessage.Field("qids", InMessage.Field.Type.RAW, (nwqid * 13).toUInt())) - val rawQids = imsg.fieldsRaw["qids"]!! - val qids: MutableList = mutableListOf() - for (i in 0.. { - val omsg = OutMessage(NinePMessageType.TOPEN, this.tagGen.generate(), listOf("fid", "mode"), - mapOf( - "fid" to Pair(BigInteger(fid.toString()), 4u), - "mode" to Pair(BigInteger(mode.toModeByte().toString()), 1u) - ), - emptyMap(), - emptyMap(), - this.maxSize - ) - omsg.write(this.tl) - val imsg = checkedInMessage(omsg.tag) - imsg.applySchema(listOf( - InMessage.Field("qid", InMessage.Field.Type.RAW, 13u), - InMessage.Field("iounit", InMessage.Field.Type.INTEGER, 4u) - )) - val qid = QID(imsg.fieldsRaw["qid"]!!.toList()) - val iounit = imsg.fieldsInt["iounit"]!!.toInt().toUInt() - return Pair(qid, iounit) - } - - override fun create(fid: UInt, name: String, perm: FilePermissions, mode: FileMode): Pair { - val omsg = OutMessage(NinePMessageType.TCREATE, this.tagGen.generate(), listOf("fid", "name", "perm", "mode"), - mapOf( - "fid" to Pair(BigInteger(fid.toString()), 4u), - "perm" to Pair(BigInteger(perm.toPermissionInt().toString()), 4u), - "mode" to Pair(BigInteger(mode.toModeByte().toString()), 1u) - ), - mapOf( - "name" to name - ), - emptyMap(), - this.maxSize - ) - omsg.write(this.tl) - val imsg = checkedInMessage(omsg.tag) - imsg.applySchema(listOf( - InMessage.Field("qid", InMessage.Field.Type.RAW, 13u), - InMessage.Field("iounit", InMessage.Field.Type.INTEGER, 4u) - )) - val qid = QID(imsg.fieldsRaw["qid"]!!.toList()) - val iounit = imsg.fieldsInt["iounit"]!!.toInt().toUInt() - return Pair(qid, iounit) - } - - override fun read(fid: UInt, offset: ULong, count: UInt): Array { - val omsg = OutMessage(NinePMessageType.TREAD, this.tagGen.generate(), listOf("fid", "offset", "count"), - mapOf( - "fid" to Pair(BigInteger(fid.toString()), 4u), - "offset" to Pair(BigInteger(offset.toString()), 8u), - "count" to Pair(BigInteger(count.toString()), 4u) - ), - emptyMap(), - emptyMap(), - this.maxSize - ) - omsg.write(this.tl) - val imsg = checkedInMessage(omsg.tag) - imsg.applyField(InMessage.Field("count", InMessage.Field.Type.INTEGER, 4u)) - val count = imsg.fieldsInt["count"]!!.toInt().toUInt() - imsg.applyField(InMessage.Field("data", InMessage.Field.Type.RAW, count)) - - return imsg.fieldsRaw["data"]!! - } - - override fun write(fid: UInt, offset: ULong, count: UInt, data: Iterable): UInt { - val data = data.take(count.toInt()).toTypedArray() - val omsg = OutMessage(NinePMessageType.TWRITE, this.tagGen.generate(), listOf("offset", "count", "data"), - mapOf( - "offset" to Pair(BigInteger(offset.toString()), 8u), - "count" to Pair(BigInteger(count.toString()), 4u) - ), - emptyMap(), - mapOf( - "data" to data.toList() - ), - this.maxSize - ) - omsg.write(this.tl) - val imsg = checkedInMessage(omsg.tag) - imsg.applyField(InMessage.Field("count", InMessage.Field.Type.INTEGER, 4u)) - val count = imsg.fieldsInt["count"]!! - - return count.toInt().toUInt() - } - - override fun clunk(fid: UInt) { - val omsg = OutMessage(NinePMessageType.TCLUNK, this.tagGen.generate(), listOf("fid"), - mapOf( - "fid" to Pair(BigInteger(fid.toString()), 4u) - ), - emptyMap(), - emptyMap(), - this.maxSize - ) - omsg.write(this.tl) - val imsg = checkedInMessage(omsg.tag) - } - - override fun remove(fid: UInt) { - val omsg = OutMessage(NinePMessageType.TREMOVE, this.tagGen.generate(), listOf("fid"), - mapOf( - "fid" to Pair(BigInteger(fid.toString()), 4u) - ), - emptyMap(), - emptyMap(), - this.maxSize - ) - omsg.write(this.tl) - val imsg = checkedInMessage(omsg.tag) - } - - override fun stat(fid: UInt): Stat { - val omsg = OutMessage(NinePMessageType.TSTAT, this.tagGen.generate(), listOf("fid"), - mapOf( - "fid" to Pair(BigInteger(fid.toString()), 4u) - ), - emptyMap(), - emptyMap(), - this.maxSize - ) - omsg.write(this.tl) - val imsg = checkedInMessage(omsg.tag) - imsg.applyField(InMessage.Field("nstat", InMessage.Field.Type.INTEGER, 2u)) - val nstat = imsg.fieldsInt["nstat"]!!.toInt().toUInt() - imsg.applyField(InMessage.Field("stat", InMessage.Field.Type.RAW, nstat)) - val rawStat = imsg.fieldsRaw["stat"]!!.toList() - return Stat(fid, rawStat) - } - - override fun wstat(fid: UInt, stat: Stat) { - val omsg = OutMessage(NinePMessageType.TWSTAT, this.tagGen.generate(), listOf("fid", "stat"), - mapOf( - "fid" to Pair(BigInteger(fid.toString()), 4u) - ), - emptyMap(), - mapOf( - "stat" to stat.toRaw() - ), - this.maxSize - ) - omsg.write(this.tl) - val imsg = checkedInMessage(omsg.tag) - } -} \ No newline at end of file diff --git a/src/main/kotlin/FileMode.kt b/src/main/kotlin/FileMode.kt deleted file mode 100644 index 81712e6..0000000 --- a/src/main/kotlin/FileMode.kt +++ /dev/null @@ -1,32 +0,0 @@ -/** - * The mode in which to open or create a file. For directories, it's illegal for directories to be written, truncated, - * or removed on close. - * - * @param mode The actual mode, as described by the [Mode] enum class. - * @param truncate Set or unset the truncation bit. It requires the write permission. - * @param removeClose Set or unset the "remove on close" bit. It requires the remove permission in the parent directory. - */ -data class FileMode(val mode: Mode, val truncate: Boolean, val removeClose: Boolean) { - enum class Mode(val value: UByte) { - READ(0u), - WRITE(1u), - READ_WRITE(2u), - EXECUTE(3u) - } - - /** - * Turn the mode described by the [FileMode] fields into a mode byte. - */ - fun toModeByte(): UByte { - var byte: UByte = 0u - byte = byte.or(this.mode.value) - if (truncate) { - byte = byte.or(0x10u) - } - if (removeClose) { - byte = byte.or(0x40u) - } - - return byte - } -} \ No newline at end of file diff --git a/src/main/kotlin/FilePermissions.kt b/src/main/kotlin/FilePermissions.kt deleted file mode 100644 index 84a7944..0000000 --- a/src/main/kotlin/FilePermissions.kt +++ /dev/null @@ -1,92 +0,0 @@ -/** - * The permissions of a newly created file. - */ -class FilePermissions { - - /** - * The permissions for the file's owning user. - */ - val userPerms: Permissions - - /** - * The permissions for the file's owning group. - */ - val groupPerms: Permissions - - /** - * The permissions for everyone else. - */ - val elsePerms: Permissions - - /** - * Is the file a directory? If not, it's a regular file. - */ - val isDirectory: Boolean - - private val DMDIR: UInt = 0x80000000u - - /** - * Constructor for file permissions with separate fields. - * - * @param userPerms The permissions for the file's owning user. - * @param groupPerms The permissions for the file's owning group. - * @param elsePerms The permissions for everyone else. - * @param isDirectory Is the file a directory? If not, it's a regular file. - */ - constructor(userPerms: Permissions, groupPerms: Permissions, elsePerms: Permissions, isDirectory: Boolean) { - this.userPerms = userPerms - this.groupPerms = groupPerms - this.elsePerms = elsePerms - this.isDirectory = isDirectory - } - - /** - * Constructor for raw file permission data. Only the first 4 elements are read. - * - * @param raw The raw file permission data. - * @throws IllegalArgumentException if [raw] does not have at least 4 elements. - */ - constructor(raw: List) { - require(raw.size >= 4) - val raw = raw.slice(0..4) - val dirValue = raw[0].toUInt().xor(this.DMDIR) - this.isDirectory = dirValue > 0u - this.userPerms = Permissions.fromByte(raw[1]) - this.groupPerms = Permissions.fromByte(raw[2]) - this.elsePerms = Permissions.fromByte(raw[3]) - } - - enum class Permissions(val bits: UByte) { - READ(0x4u), - WRITE(0x2u), - EXECUTE(0x1u), - READ_WRITE(READ.bits.or(WRITE.bits)), - READ_EXECUTE(READ.bits.or(EXECUTE.bits)), - WRITE_EXECUTE(WRITE.bits.or(EXECUTE.bits)), - READ_WRITE_EXECUTE(READ.bits.or(WRITE.bits.or(EXECUTE.bits))); - - companion object { - /** - * Obtain a [Permissions] instance by matching its value. - * - * @throws NoSuchElementException if no such element has the provided value. - */ - fun fromByte(bits: UByte) = Permissions.entries.first { it.bits == bits } - } - } - - /** - * Turn the permissions described by the [FilePermissions] fields into a permission integer (4 bytes). - */ - fun toPermissionInt(): UInt { - val permFileds = listOf(userPerms, groupPerms, elsePerms) - val perms: UInt = 0u - for (i in 0..permFileds.size) { - perms.or(permFileds[i].bits.toUInt().shl(8 * (permFileds.size - 1 - i))) - } - if (isDirectory) { - perms.or(this.DMDIR) - } - return perms - } -} \ No newline at end of file diff --git a/src/main/kotlin/IPAddress.kt b/src/main/kotlin/IPAddress.kt deleted file mode 100644 index 8eb7414..0000000 --- a/src/main/kotlin/IPAddress.kt +++ /dev/null @@ -1,73 +0,0 @@ -/** - * An IP address (v4 or v6). - */ -class IPAddress { - private var address: Array - private var is4: Boolean - - /** - * @throws NumberFormatException if the IP address follows neither the IPv4 syntax, nor the IPv6 syntax. - */ - constructor(address: String) { - if (isAddressV4(address)) { - this.is4 = true - } else if (isAddressV6(address)) { - this.is4 = false - } else { - throw NumberFormatException() - } - - val split4 = address.split('.') - val split6 = address.split(':') - val bytes: List - if (this.is4) { - bytes = split4.map { it.toUByte(10) } - } else { - val shorts = split6.map { it.toShort(16) } - bytes = shorts.flatMap { - listOf( - it.toInt().and(0xFF00).shr(0x08).toUByte(), - it.toInt().and(0x00FF).toUByte() - ) - } - } - this.address = bytes.toTypedArray() - } - - private fun isAddressV4(address: String): Boolean { - val split4 = address.split('.') - var isOK = split4.size == 4 - isOK = isOK && split4.size == split4.filter { it.matches(Regex("^[0-9]+$")) }.size - return isOK - } - - private fun isAddressV6(address: String): Boolean { - val split6 = address.split(':') - var isOK = split6.size == 8 - isOK = isOK && split6.size == split6.filter { it.contains(Regex("^[0-9][a-f]+$")) }.size - if (isOK) { - return true - } - - // try with "::" - val split6Double = address.split("::") - if (split6Double.size != 2) { - return false - } - val omitted = 8 - split6Double[0].split(':').size - split6Double[1].split(':').size - if (omitted < 0) { - return false - } - return true - } - - override fun toString(): String { - val str: String - if (this.is4) { - str = this.address.joinToString(separator = ".") { it.toString() } - } else { - str = this.address.joinToString(separator = ":") { it.toString() } - } - return str - } -} \ No newline at end of file diff --git a/src/main/kotlin/NinePMacros.kt b/src/main/kotlin/NinePMacros.kt deleted file mode 100644 index 738732b..0000000 --- a/src/main/kotlin/NinePMacros.kt +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Handy function that initializes the 9P connection. - */ -fun initNineP(npconn: NinePConnection) { - // TODO: implement -} \ No newline at end of file diff --git a/src/main/kotlin/NinePMessageType.kt b/src/main/kotlin/NinePMessageType.kt deleted file mode 100644 index 4c8c60f..0000000 --- a/src/main/kotlin/NinePMessageType.kt +++ /dev/null @@ -1,39 +0,0 @@ -enum class NinePMessageType(val value: UByte) { - TVERSION(100u), - RVERSION(101u), - TAUTH(102u), - RAUTH(103u), - TATTACH(104u), - RATTACH(105u), - //TERROR(106), <--- illegal - RERROR(107u), - TFLUSH(108u), - RFLUSH(109u), - TWALK(110u), - RWALK(111u), - TOPEN(112u), - ROPEN(113u), - TCREATE(114u), - RCREATE(115u), - TREAD(116u), - RREAD(117u), - TWRITE(118u), - RWRITE(119u), - TCLUNK(120u), - RCLUNK(121u), - TREMOVE(122u), - RREMOVE(123u), - TSTAT(124u), - RSTAT(125u), - TWSTAT(126u), - RWSTAT(127u); - - companion object { - /** - * Obtain a [NinePMessageType] instance by matching its value. - * - * @throws NoSuchElementException if no such element has the provided value. - */ - fun fromByte(value: UByte) = NinePMessageType.entries.first { it.value == value } - } -} \ No newline at end of file diff --git a/src/main/kotlin/PathInfo.kt b/src/main/kotlin/PathInfo.kt deleted file mode 100644 index 2aee406..0000000 --- a/src/main/kotlin/PathInfo.kt +++ /dev/null @@ -1,93 +0,0 @@ -/** - * This class holds all info about paths, their FIDs, and their QIDs. - */ -class PathInfo() { - private val paths: MutableSet = mutableSetOf() - - /** - * Information about a path. Each FID maps to one path but not all paths have FIDs (injective). On the other hand, - * one QID can be shared with different paths and FIDs. - * - * FIDs are optional because some R-messages only return QIDs for certain files (e.g. walk). - * - * @param path The path. - * @param fid The optional FID associated with the path. - * @param qid The QID associated with the path. - */ - data class Path(val path: List, val fid: UInt?, val qid: QID) - - /** - * Add a path. - */ - fun addPath(path: Path) { - this.paths.add(path) - } - - /** - * Add a QID to an existing FID. Do nothing if the given FID does not exist. - */ - fun addQIDToFID(fid: UInt, qid: QID) { - findByFID(fid)?.qid = qid - } - - /** - * Remove a FID. - */ - fun removeFID(fid: UInt) { - this.paths.removeIf { x -> x.fid == fid } - } - - fun find(predicate: (Path) -> Boolean) = this.paths.find(predicate) - - /** - * Find [Path] object by path. - * - * @param path The path to search for. - * @return A path object if [path] exists, or null otherwise. - */ - fun findByPath(path: List): Path? { - return this.paths.find { x -> x.path == path } - } - - /** - * Find [Path] object by FID. - * - * @param fid The FID to search for. - * @return A path object if [fid] exists, or null otherwise. - */ - fun findByFID(fid: UInt): Path? { - return this.paths.find { x -> x.fid == fid } - } - - /** - * Find [Path] object by QID. - * - * @param qid The path to search for. - * @return A path object if [qid] exists, or null otherwise. - */ - fun findByQID(qid: QID): Path? { - return this.paths.find { x -> x.qid == qid } - } - - /** - * Retrieve all paths - */ - fun getAllPaths(): Set { - return this.paths.toSet() - } - - /** - * Generate a new FID which is not already in use. - * - * @return The new FID. - * @throws IllegalStateException if there is no available FID. - */ - fun genFID(): UInt { - for (newFid in 0u..UInt.MAX_VALUE) { - if (findByFID(newFid) == null) { - return newFid - } - } - throw IllegalStateException() - } -} \ No newline at end of file diff --git a/src/main/kotlin/ProtocolTranslator.kt b/src/main/kotlin/ProtocolTranslator.kt deleted file mode 100644 index fee52f4..0000000 --- a/src/main/kotlin/ProtocolTranslator.kt +++ /dev/null @@ -1,167 +0,0 @@ -/* -TODO: - - add arguments to methods - - switch from returned strings to exceptions -*/ - -/** - * The [ProtocolTranslator] interface provides methods that coincide 1:1 with each request type in the 9P protocol. - * Every method that can fail, that is, every request that can receive a response with `Rerror` type instead of the same - * type as itself, returns a non-null `String` that contains the error message received in the response. - * - * Tags and the `msize` value are supposed to be managed internally by the implementing class. - * - * When compared to 9P's formal message descriptions, like those which can be read in Plan 9's manual pages, some of the - * methods might lack parameters. Those which can be inferred from the existing parameters are purposefully omitted. An - * example is [walk], which omits `nwname` because it can be obtained by calculating the size of `wname`. - * - * Trivia: comments for each method are taken from each message type's manual page in section 5. - */ -interface ProtocolTranslator { - /** - * Negotiate protocol version. - * - * This must be the first message sent on the 9P connection and no other requests can be issued until a response has - * been received. - * - * @param msize The maximum length, in bytes, that the client will ever generate or expect to receive in a single - * 9P message. - * @param version Should be "9P2000", which is the only defined value. - * @throws except.InvalidMessageException if the received message is invalid. - * @throws except.RErrorException if the received message is an R-error message. - * @throws except.MsizeValueTooBigException if the received `msize` value is bigger than what the client requested. - * @throws except.UnknownVersionException if the version negotiation failed. - */ - fun version(msize: UInt, version: String) - - /** - * Perform authentication. - */ - fun auth(afid: UInt, uname: String, aname: String) - - /** - * Abort a message. - * - * @param oldtag The tag of the message that needs to be purged. - * @throws except.InvalidMessageException if the received message is invalid. - * @throws except.RErrorException if the received message is an R-error message. - */ - fun flush(oldtag: UShort) - - /** - * Establish a connection. - * - * @param fid FID that represents the root directory of the desired file tree. - * @param afid A FID previously established by an auth message. - * @param uname A user identifier. - * @param aname The desired file tree to access. - * @return The QID of the file tree's root. - * @throws except.InvalidMessageException if the received message is invalid. - * @throws except.RErrorException if the received message is an R-error message. - */ - fun attach(fid: UInt, afid: UInt, uname: String, aname: String): QID - - /** - * Descend a directory hierarchy. - * - * @param fid The existing FID. It must represent a directory. - * @param newfid The proposed FID, which is going to be associated with the result of walking the hierarchy. It must - * be not in use, unless it is the same as [fid]. - * @param wname The successive path name elements which describe the file to walk to. - * @return The QID of each path element that has been walked through. - * @throws except.InvalidMessageException if the received message is invalid. - * @throws except.RErrorException if the received message is an R-error message. - * @throws except.UnaccessibleFileException if [wname] contains one or more path elements that do not exist. - */ - fun walk(fid: UInt, newfid: UInt, wname: List): List - - /** - * Prepare an FID for I/O on an existing file. - * - * @param fid The FID of the file to open. - * @param mode The mode in which the file is opened. - * @return A pair of: (1) the returned QID, and (2) a value called `iounit` that indicates, if non-zero, the maximum - * number of bytes that are guaranteed to be read from or written to the file without breaking the I/O transfer into - * multiple 9P messages. - * @throws except.InvalidMessageException if the received message is invalid. - * @throws except.RErrorException if the received message is an R-error message. - */ - fun open(fid: UInt, mode: FileMode): Pair - - /** - * Prepare an FID for I/O on a new file. - * - * @param fid The FID of the directory that is going to contain the file. The specified directory requires write - * permission. - * @param name The file name. - * @param perm The permissions of the new file. - * @param mode The open mode after successful creation. - * @return A pair of: (1) the QID of the newly created file, and (2) a value called `iounit` that indicates, if - * non-zero, the maximum number of bytes that are guaranteed to be read from or written to the file without breaking - * the I/O transfer into multiple 9P messages. - * @throws except.InvalidMessageException if the received message is invalid. - * @throws except.RErrorException if the received message is an R-error message. - */ - fun create(fid: UInt, name: String, perm: FilePermissions, mode: FileMode): Pair - - /** - * Transfer data from file. Due to the negotiated maximum size of 9P messages, called `msize`, one is supposed to - * call this method multiple times, unless the content is smaller than `msize`. - * - * @return The content read with the call just made. - * @throws except.InvalidMessageException if the received message is invalid. - * @throws except.RErrorException if the received message is an R-error message. - */ - fun read(fid: UInt, offset: ULong, count: UInt): Array - - /** - * Transfer data to file. Due to the negotiated maximum size of 9P messages, called `msize`, this method is supposed - * to be called multiple times, unless the content is smaller than `msize`. - * - * @param fid The FID to write to. - * @param offset The distance between the beginning of the file and the first written byte. - * @param data The raw bytes that are going to be written. - * @return The amount of bytes written with the call just made. - * @throws except.InvalidMessageException if the received message is invalid. - * @throws except.RErrorException if the received message is an R-error message. - */ - fun write(fid: UInt, offset: ULong, count: UInt, data: Iterable): UInt - - /** - * Forget about a FID. - * - * @param fid The FID to forget. - * @throws except.InvalidMessageException if the received message is invalid. - * @throws except.RErrorException if the received message is an R-error message. - */ - fun clunk(fid: UInt) - - /** - * Remove a file from a server. - * - * @param fid The FID of the file to remove. - * @throws except.InvalidMessageException if the received message is invalid. - * @throws except.RErrorException if the received message is an R-error message. - */ - fun remove(fid: UInt) - - /** - * Inquire file attributes. - * - * @param fid The FID of the file to inquire. - * @return All the file attributes of the file associated with [fid]. - * @throws except.InvalidMessageException if the received message is invalid. - * @throws except.RErrorException if the received message is an R-error message. - */ - fun stat(fid: UInt): Stat - - /** - * Change file attributes. - * - * @param fid The FID of the file to set attributes of. - * @param stat The attributes to set. - * @throws except.InvalidMessageException if the received message is invalid. - * @throws except.RErrorException if the received message is an R-error message. - */ - fun wstat(fid: UInt, stat: Stat) -} \ No newline at end of file diff --git a/src/main/kotlin/QID.kt b/src/main/kotlin/QID.kt deleted file mode 100644 index 4d0bd6a..0000000 --- a/src/main/kotlin/QID.kt +++ /dev/null @@ -1,105 +0,0 @@ -import net.InMessage -import net.OutMessage -import java.math.BigInteger - -/** - * This class holds information about a single QID. - */ -class QID { - /** - * Does the QID represent a directory? - */ - val isDirectory get() = getIsDirectory() - - /** - * Does the QID represent an append-only file? - */ - val isAppendOnly get() = getIsAppendOnly() - - /** - * Does the QID represent an exclusive-use file? - */ - val isExclusive get() = getIsExclusive() - - /** - * Does the QID represent a temporary file? - */ - val isTemporary get() = getIsTemporary() - - /** - * The QID type. - */ - val type: UByte - - /** - * The QID file version. - */ - val version: UInt - - /** - * The QID path. - */ - val path: ULong - - /** - * Constructor for a QID with separate fields. See the `stat(5)` manual page for more information about [type], - * [version], and [path]. - * - * @param type The QID type. - * @param version The QID file version. - * @param path The QID path. - */ - constructor(type: UByte, version: UInt, path: ULong) { - this.type = type - this.version = version - this.path = path - } - - /** - * Constructor for raw QID data. Only the first 13 elements are read. - * - * @param raw The raw QID data. - * @throws IllegalArgumentException if [raw] does not have at least 13 elements. - */ - constructor(raw: List) { - require(raw.size >= 13) - this.type = raw.first() - val rawVersion = raw.slice(1..4) - val rawPath = raw.slice(5..12) - this.version = InMessage.convInteger(rawVersion, 0, rawVersion.size).toInt().toUInt() - this.path = InMessage.convInteger(rawPath, 0, rawPath.size).toLong().toULong() - } - - /** - * Check bit values in [type]. In case of multiple bits, the method returns true if at least one of them is 1. - * - * @param bits A byte whose bits set to 1 are checked. - */ - private fun checkTypeBits(bits: UByte): Boolean { - return this.type.and(bits) != 0u.toUByte() - } - - private fun getIsDirectory(): Boolean { - return checkTypeBits(0x80u) - } - - private fun getIsAppendOnly(): Boolean { - return checkTypeBits(0x40u) - } - - private fun getIsExclusive(): Boolean { - return checkTypeBits(0x20u) - } - - private fun getIsTemporary(): Boolean { - return checkTypeBits(0x04u) - } - - fun toRaw(): List { - var bytes: List = emptyList() - bytes += this.type - bytes += OutMessage.convIntegerToBytes(BigInteger(this.version.toString()), 4u) - bytes += OutMessage.convIntegerToBytes(BigInteger(this.path.toString()), 8u) - return bytes - } -} \ No newline at end of file diff --git a/src/main/kotlin/SizedInteger.kt b/src/main/kotlin/SizedInteger.kt deleted file mode 100644 index 48e4e25..0000000 --- a/src/main/kotlin/SizedInteger.kt +++ /dev/null @@ -1,36 +0,0 @@ -import java.math.BigInteger - -/** - * [SizedInteger] represents an unsigned integer number of arbitrary yet fixed length. It's useful for storing integers - * got from a connection as message fields. - * - * @param size The size of the field, measured in bytes. - * @param value The value of the field. - * - * @throws IllegalArgumentException if the size required to store the provided value is bigger than the provided size. - */ -class SizedInteger(val size: ULong, value: BigInteger) { - init { - if (!this.sizeOk(size, value)) { - throw IllegalArgumentException() - } - } - - constructor(size: ULong) : this(size, 0.toBigInteger()) - - /** - * @throws IllegalStateException on set if the declared size is not enough for the new value. - */ - var value = value - set(value) { - if (!this.sizeOk(value)) { - throw IllegalStateException() - } - } - - private fun sizeOk(size: ULong, value: BigInteger): Boolean { - val requiredSize = value.toByteArray().size.toUInt() - return requiredSize <= size - } - private fun sizeOk(value: BigInteger): Boolean = sizeOk(this.size, value) -} \ No newline at end of file diff --git a/src/main/kotlin/Stat.kt b/src/main/kotlin/Stat.kt deleted file mode 100644 index 20692c5..0000000 --- a/src/main/kotlin/Stat.kt +++ /dev/null @@ -1,151 +0,0 @@ -import net.InMessage -import net.OutMessage -import java.math.BigInteger - -// TODO: add time conversion methods -/** - * File attributes. The `type` and `dev` attributes are ignored since they are for kernel use only. Time is measured in - * seconds, since the epoch (Jan 1 00:00 1970 GMT). - */ -class Stat { - /** - * The FID sent the T-stat message. - */ - val fid: UInt - - /** - * The QID of the file. - */ - val qid: QID - - /** - * Permissions and flags. - */ - val mode: FilePermissions - - /** - * Last acces time. - */ - val atime: UInt - - /** - * Last modification time. - */ - val mtime: UInt - - /** - * The length of the file in bytes. - */ - val length: ULong - - /** - * The file name, which is `/` if the file is the root directory. - */ - val name: String - - /** - * The owner's name. - */ - val uid: String - - /** - * The group's name. - */ - val gid: String - - /** - * The name of the user who last modified the file. - */ - val muid: String - - /** - * Make an instance of [Stat] from each of its fields. - * - * @param fid The FID sent the T-stat message. - * @param qid The QID of the file. - * @param mode Permissions and flags. - * @param atime Last acces time. - * @param mtime Last modification time. - * @param length The length of the file in bytes. - * @param name The file name, which is `/` if the file is the root directory. - * @param uid The owner's name. - * @param gid The group's name. - * @param muid The name of the user who last modified the file. - */ - constructor(fid: UInt, qid: QID, mode: FilePermissions, atime: UInt, mtime: UInt, length: ULong, name: String, uid: String, gid: String, muid: String) { - this.fid = fid - this.qid = qid - this.mode = mode - this.atime = atime - this.mtime = mtime - this.length = length - this.name = name - this.uid = uid - this.gid = gid - this.muid = muid - } - - /** - * Make an instance of [Stat] from raw data. - * - * @param fid The FID of the file associated with the stat instance. - * @param raw The raw stat data. - */ - constructor(fid: UInt, raw: List) { - var offset = 0 - val qid = QID(raw.slice(0..<13)) - offset += 13 - val mode = FilePermissions(raw.slice(offset+0.. = mutableListOf() - for (size in intFielSizes) { - intFields.add(InMessage.convInteger(raw, offset, size)) - offset += size - } - val atime = intFields[0].toInt().toUInt() - val mtime = intFields[1].toInt().toUInt() - val length = intFields[2].toLong().toULong() - - val strAmount = 4 - val strFields: MutableList = mutableListOf() - for (i in 0..strAmount) { - val str = InMessage.convString(raw, offset) - strFields.add(str) - offset += str.length - } - val name = strFields[0] - val uid = strFields[1] - val gid = strFields[2] - val muid = strFields[3] - - this.fid = fid - this.qid = qid - this.mode = mode - this.atime = atime - this.mtime = mtime - this.length = length - this.name = name - this.uid = uid - this.gid = gid - this.muid = muid - } - - /** - * Turn a [Stat] instance into raw data. This leaves out the [fid] field. - */ - fun toRaw(): List { - var bytes: List = emptyList() - bytes += this.qid.toRaw() - bytes += OutMessage.convIntegerToBytes(BigInteger(this.mode.toPermissionInt().toString()), 4u) - bytes += OutMessage.convIntegerToBytes(BigInteger(this.atime.toString()), 4u) - bytes += OutMessage.convIntegerToBytes(BigInteger(this.mtime.toString()), 4u) - bytes += OutMessage.convIntegerToBytes(BigInteger(this.length.toString()), 8u) - bytes += OutMessage.convStringToBytes(this.name) - bytes += OutMessage.convStringToBytes(this.uid) - bytes += OutMessage.convStringToBytes(this.gid) - bytes += OutMessage.convStringToBytes(this.muid) - return bytes - } -} \ No newline at end of file diff --git a/src/main/kotlin/TagGenerator.kt b/src/main/kotlin/TagGenerator.kt deleted file mode 100644 index c386b17..0000000 --- a/src/main/kotlin/TagGenerator.kt +++ /dev/null @@ -1,63 +0,0 @@ -import kotlin.random.Random - -/** - * Generate tags for 9P messages. - */ -class TagGenerator(val method: TagGenerationMethod, val initial: UShort) { - - private var current: UShort = initial - private val rng: Random = Random(initial.toInt()) - private val randomReturned: Set = emptySet() - - private val generationFunctions: Map UShort> = mapOf( - TagGenerationMethod.INCREMENTAL to this::generateIncremental, - TagGenerationMethod.RANDOM to this::generateRandom, - TagGenerationMethod.RANDOM_CHECKED to this::generateRandomChecked - ) - - /** - * How are tags generated? - */ - enum class TagGenerationMethod { - /** - * Return the initial value on the first generation. Increment the value on each generation. - */ - INCREMENTAL, - - /** - * Use the initial value as a seed and generate random values from it. - */ - RANDOM, - - /** - * Same as [RANDOM], but checks are added to avoid generating the same value twice. - */ - RANDOM_CHECKED, - } - - /** - * Generate a new tag. - */ - fun generate(): UShort { - return this.generationFunctions.getValue(method).invoke() - } - - private fun generateIncremental(): UShort { - val tmp = this.current - this.current++ - return tmp - } - - private fun generateRandom(): UShort { - return this.rng.nextBits(16).toUShort() - } - - private fun generateRandomChecked(): UShort { - var v: UShort - do { - v = generateRandom() - } while (v in randomReturned) - randomReturned.plus(v) - return v - } -} \ No newline at end of file diff --git a/src/main/kotlin/Utils.kt b/src/main/kotlin/Utils.kt deleted file mode 100644 index 45185de..0000000 --- a/src/main/kotlin/Utils.kt +++ /dev/null @@ -1,38 +0,0 @@ -const val DEFAULT_9P_PORT: UShort = 564u -const val DEFAULT_9P_TLS_PORT: UShort = 17020u - -/** - * The [nineAddressToValues] function translates an address from one of the dial(2) formats into the respective data. - * For the sake of my sanity, the following list highlights differences from the typical dial(2) formats: - * 1. The "network" part is ignored. - * 2. If nothing is specified in the place of "service", the default unencrypted 9P port is used: 564. - * 3. "Service" can only contain port numbers. - * All the rest is the same. - * - * @return A Pair (A, P) containing the host address (A) and the port (P). - * - * @throws IllegalArgumentException If the address is improperly formatted or has invalid values. - * @throws NumberFormatException If the port's value can't be parsed into an [UShort]. - */ -fun nineAddressToValues(nineAddress: String): Pair { - val parts = nineAddress.split('!', limit = 3) - val address: String - val port: UShort - when (parts.size) { - 1 -> { - address = parts[0] - port = DEFAULT_9P_PORT - } - 2 -> { - address = parts[1] - port = DEFAULT_9P_PORT - } - 3 -> { - address = parts[1] - port = parts[2].toUShort() - } - else -> throw IllegalArgumentException() - } - - return Pair(address, port) -} \ No newline at end of file diff --git a/src/main/kotlin/demo/Main.kt b/src/main/kotlin/demo/Main.kt deleted file mode 100644 index 640ec25..0000000 --- a/src/main/kotlin/demo/Main.kt +++ /dev/null @@ -1,23 +0,0 @@ -package demo - -import NetworkPacketTransporterJavaNet -import NinePConnection -import UnresolvableHostException -import kotlin.system.exitProcess - -fun main(args: Array) { - val myUsername = "" - val myPassword = "" - val npconn: NinePConnection - - try { - npconn = NinePConnection(NetworkPacketTransporterJavaNet("net!9p.mydomain.com!564")) - } catch (uhe: UnresolvableHostException) { - println("failed to connect :(") - exitProcess(1) - } - - // TODO: add walk and stat - - npconn.disconnect() -} \ No newline at end of file diff --git a/src/main/kotlin/except/FailedAuthenticationException.kt b/src/main/kotlin/except/FailedAuthenticationException.kt deleted file mode 100644 index 94935c3..0000000 --- a/src/main/kotlin/except/FailedAuthenticationException.kt +++ /dev/null @@ -1,8 +0,0 @@ -package except - -/** - * The authentication with the remote part failed. - * - * @param reason A human-readable reason for which the authentication failed. - */ -class FailedAuthenticationException(val reason: String) : Exception("Authentication with remote host failed: $reason") \ No newline at end of file diff --git a/src/main/kotlin/except/InvalidMessageException.kt b/src/main/kotlin/except/InvalidMessageException.kt deleted file mode 100644 index 02a3cc4..0000000 --- a/src/main/kotlin/except/InvalidMessageException.kt +++ /dev/null @@ -1,8 +0,0 @@ -package except - -/** - * The packet that is currently being read is not valid. - * - * @param reason The reason for which the packet is invalid. - */ -class InvalidMessageException(val reason: String) : Exception("Invalid packet: $reason") \ No newline at end of file diff --git a/src/main/kotlin/except/MsizeValueTooBigException.kt b/src/main/kotlin/except/MsizeValueTooBigException.kt deleted file mode 100644 index f1f6da1..0000000 --- a/src/main/kotlin/except/MsizeValueTooBigException.kt +++ /dev/null @@ -1,10 +0,0 @@ -package except - -/** - * This exception is thrown when the `msize` value sent by the server is bigger than that sent by the client during the - * version transaction. - * - * @param maxClientValue The value requested by the client. - * @param receivedValue The value sent by the server. - */ -class MsizeValueTooBigException(val maxClientValue: UInt, val receivedValue: UInt) : Exception("Msize value too big: $receivedValue > $maxClientValue") \ No newline at end of file diff --git a/src/main/kotlin/except/RErrorException.kt b/src/main/kotlin/except/RErrorException.kt deleted file mode 100644 index c15cbc2..0000000 --- a/src/main/kotlin/except/RErrorException.kt +++ /dev/null @@ -1,8 +0,0 @@ -package except - -/** - * This exception represents an error sent by the remote server as an R-error message. - * - * @param message The message sent by the server. - */ -class RErrorException(val rErrorMessage: String?) : Exception("R-error message received: $rErrorMessage") \ No newline at end of file diff --git a/src/main/kotlin/except/UnaccessibleFileException.kt b/src/main/kotlin/except/UnaccessibleFileException.kt deleted file mode 100644 index 07d5d13..0000000 --- a/src/main/kotlin/except/UnaccessibleFileException.kt +++ /dev/null @@ -1,11 +0,0 @@ -package except - -/** - * This exception is thrown when the file that the client is trying to open (or walk through, in case of a directory) - * cannot be accessed. - * - * @param path The path, as a list of path elements, that the client tried to access, up to and including the first - * element that cannot be accessed (e.g. if the path the user wants to access is `["usr", "foo", "bar", "zib"]` but - * `bar` does not exist, then [path] must be `["usr", "foo", "bar"]`). - */ -class UnaccessibleFileException(val path: List) : Exception("Could not walk to file ${path.joinToString(separator = "/")}.") \ No newline at end of file diff --git a/src/main/kotlin/except/UnknownVersionException.kt b/src/main/kotlin/except/UnknownVersionException.kt deleted file mode 100644 index 5023bc5..0000000 --- a/src/main/kotlin/except/UnknownVersionException.kt +++ /dev/null @@ -1,9 +0,0 @@ -package except - -/** - * This exception is thrown when the remote server sent either an "unknown" version back during the version negotiation - * procedure or a version unknown to this client implementation. - * - * @param version The version sent by the server. - */ -class UnknownVersionException(val version: String) : Exception("Unknown version: $version") \ No newline at end of file diff --git a/src/main/kotlin/except/UnresolvableHostException.kt b/src/main/kotlin/except/UnresolvableHostException.kt deleted file mode 100644 index 19dd3b6..0000000 --- a/src/main/kotlin/except/UnresolvableHostException.kt +++ /dev/null @@ -1,8 +0,0 @@ -package except - -/** - * The specified domain, which identifies the host's address, could not be resolved. - * - * @param address The unresolvable address. - */ -class UnresolvableHostException(val address: String) : Exception("Hostname $address unresolvable.") \ No newline at end of file diff --git a/src/main/kotlin/net/InMessage.kt b/src/main/kotlin/net/InMessage.kt deleted file mode 100644 index 771c670..0000000 --- a/src/main/kotlin/net/InMessage.kt +++ /dev/null @@ -1,167 +0,0 @@ -package net - -import NinePMessageType -import except.InvalidMessageException -import java.math.BigInteger - -/** - * An incoming 9P message. Upon instancing this class only one message is read, and it's represented in a way similar to - * that of [OutMessage]. This class is supposed to be complementary, and opposite, to [OutMessage]. - * - * @param tl The transport layer API. - * @param maxSize The maximum message size negotiated with the remote part. - * @param reqTag The required tag. - * @throws InvalidMessageException if the message that is currently being read is invalid. - */ -class InMessage(val tl: TransportLayer, maxSize: UInt, val reqTag: UShort) { - /** - * The total size of the message. - */ - val size: UInt - - /** - * The message type. - */ - val type: NinePMessageType - - /** - * The message tag. - */ - val tag: UShort - - /** - * A map of each integer field's name to its value. - */ - var fieldsInt: MutableMap = mutableMapOf() - private set - - /** - * A map of each string field's name to its value. - */ - var fieldsStr: MutableMap = mutableMapOf() - private set - - /** - * A map of each raw field's name to its value. - */ - var fieldsRaw: MutableMap> = mutableMapOf() - private set - - /** - * An ordered collection of raw bytes that still need to be interpreted as values. - */ - private var rawData: List - - init { - size = convInteger(this.tl.receiver(), 0, 4).toInt().toUInt() - if (this.size > maxSize) { - throw InvalidMessageException("Size greater than maximum size (${this.size} > ${maxSize}).") - } - try { - this.type = NinePMessageType.fromByte(convInteger(this.tl.receiver(), 0, 1).toInt().toUByte()) - } catch (_: NoSuchElementException) { - throw InvalidMessageException("Invalid 9P message type.") - } - tag = convInteger(this.tl.receiver(), 0, 2).toInt().toUShort() - if (tag != reqTag) { - // TODO: what do we do now? - } - this.rawData = this.tl.receive((size - (4u + 1u + 2u)).toULong()).toList() - } - - /** - * Field of an incoming 9P message. An ordered collection of fields makes a schema. - * - * @param name The field's name. It's typically the same you can find in the manual pages. - * @param type The field's type. - * @param size The field's size in bytes. If the type is [Type.STRING], this parameter is ignored. - */ - data class Field(val name: String, val type: Type, val size: UInt) { - - enum class Type { - INTEGER, - STRING, - RAW - } - } - - /** - * Apply the given field to the raw data and put it in one of [fieldsInt], [fieldsStr], or [fieldsRaw]. Fields must - * be applied strictly in order, as their application is not commutative. - * - * Each time a field is applied, the initial part of raw data that coincides with that field is removed. - * - * @param field The given field. - */ - fun applyField(field: Field) { - val size: Int - when (field.type) { - Field.Type.STRING -> { - val str = convString(this.rawData.toList(), 0) - size = 2 + str.length - this.fieldsStr[field.name] = str - } - Field.Type.INTEGER -> { - size = field.size.toInt() - this.fieldsInt[field.name] = convInteger(this.rawData.toList(), 0, size) - } - Field.Type.RAW -> { - size = field.size.toInt() - this.fieldsRaw[field.name] = this.rawData.take(size).toTypedArray() - } - } - this.rawData = this.rawData.drop(size) - } - - /** - * Apply the given message schema to the raw data and fill [fieldsInt], [fieldsStr], and [fieldsRaw]. - * - * Note: This method could have been avoided by making a giant `when` block in the class constructor. However, I'd - * rather let the caller, which is usually a method that makes a request and reads its response, decide the schema. - * In this way, each method that needs to read a response of a specific type (and there is usually one method per - * response type) declares its own schema, while those which cannot be easily represented by a schema (e.g. `Rwalk`) - * are simply going to be read in a field-by-field fashion. - * - * @param schema The desired ordered collection of fields. - */ - fun applySchema(schema: Iterable) { - for (field in schema) { - applyField(field) - } - } - - companion object { - /** - * Convert an [len] bytes long unsigned integer number from raw bytes. - * - * In 9P, binary numbers (non-textual) are specified in little-endian order (least significant byte first). - * - * @param len The length of the integer number in bytes. If zero, nothing is read. - * @return the number's value. - * @throws IllegalArgumentException if either [offset] or [len] are negative. - */ - fun convInteger(bytes: Iterable, offset: Int, len: Int): BigInteger { - val bytes = bytes.drop(offset).take(len) - var value = 0.toBigInteger() - for (i in 0.., offset: Int): String { - val length = convInteger(bytes, 0, 2).toInt() - val bytes = bytes.drop(offset).take(length) - return String(ByteArray(bytes.size) { i -> bytes[i].toByte() }) - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/net/OutMessage.kt b/src/main/kotlin/net/OutMessage.kt deleted file mode 100644 index c9ae879..0000000 --- a/src/main/kotlin/net/OutMessage.kt +++ /dev/null @@ -1,127 +0,0 @@ -package net - -import NinePMessageType -import java.math.BigInteger -import kotlin.math.pow - -/** - * An outgoing 9P message with the given type, tag, and fields. The message size is calculated automatically. - * - * Important note: the field names in [fieldValuesInt], [fieldValuesStr], and [fieldValuesRaw] (i.e. the keys of their - * maps) must be mutually exclusive and the union of these two maps' keys must result in a subset of (or a set equal to) - * [fieldNames]. Calling [write] when these conditions are not met throws an exception. - * - * @param type The 9P message type. - * @param tag The tag given to the message. - * @param fieldNames The names of the message fields, in the same order they are expected to be sent. - * @param fieldValuesInt A map of each integer field's name into its value and size in bytes. - * @param fieldValuesStr A map of each string field's name into its value. - * @param fieldValuesRaw A map of each raw field's name into its value. - * @param maxSize The maximum message size. - */ -class OutMessage(val type: NinePMessageType, val tag: UShort, val fieldNames: List, val fieldValuesInt: Map>, val fieldValuesStr: Map, val fieldValuesRaw: Map>, val maxSize: UInt) { - /** - * Intersection between [fieldNames] and [fieldValuesInt]. In other words: the integer fields that are going to be - * used when writing the message. - */ - private val insecInts = fieldNames.intersect(fieldValuesInt.keys) - - /** - * Intersection between [fieldNames] and [fieldValuesStr]. In other words: the string fields that are going to be - * used when writing the message. - */ - private val insecStrs = fieldNames.intersect(fieldValuesStr.keys) - - /** - * Intersection between [fieldNames] and [fieldValuesRaw]. In other words: the raw fields that are going to be used - * when writing the message. - */ - private val insecRaws = fieldNames.intersect(fieldValuesRaw.keys) - - /** - * Send the message using the given networking API. - * - * @param tl The networking API. - * @throws IllegalArgumentException if [fieldNames], [fieldValuesInt], and [fieldValuesStr] are incoherent or the - * final size of the message exceeds the negotiated value. - */ - fun write(tl: TransportLayer) { - // check that names in fieldNames exist as keys in either fieldValuesInt or fieldValuesStr but not both - require(fieldNames.size == insecInts.size + insecStrs.size + insecRaws.size) - - val totalSize = size() - if (totalSize > this.maxSize) { - throw IllegalArgumentException("Message size exceeded.") - } - writeMessageSizeTypeTag(tl, totalSize, type, tag) - for (field in fieldNames) { - tl.transmit( - if (field in insecInts) { - val valsize = fieldValuesInt[field]!! - convIntegerToBytes(valsize.first, valsize.second) - } else if (field in insecStrs) { - convStringToBytes(fieldValuesStr[field]!!) - } else { - fieldValuesRaw[field]!!.toList() - } - ) - } - } - - /** - * Write the message size and type. - * - * @param tl The networking API. - * @param size The total message size, including the 4 bytes of this parameter and the type's byte. - * @param type The 9P message type as a [NinePMessageType] constant. - * @param tag The 9P message tag. - */ - private fun writeMessageSizeTypeTag(tl: TransportLayer, size: UInt, type: NinePMessageType, tag: UShort) { - var bytes: List = emptyList() - bytes += convIntegerToBytes(BigInteger(size.toString()), 4u) - bytes += convIntegerToBytes(BigInteger(type.value.toString()), 1u) - bytes += convIntegerToBytes(BigInteger(tag.toString()), 2u) - tl.transmit(bytes) - } - - /** - * Calculate the expected size of the message. - */ - fun size(): UInt { - return 4u + 1u + 2u + this.insecInts.sumOf { this.fieldValuesInt[it]!!.second } + this.insecStrs.sumOf { 2u + this.fieldValuesStr[it]!!.length.toUInt() } + this.insecRaws.sumOf { this.fieldValuesRaw[it]!!.size.toUInt() } - } - - companion object { - // TODO: Add size that the value is required to fit in - - /** - * Convert an integer number to its byte representation. - * - * In 9P, binary numbers (non-textual) are specified in little-endian order (least significant byte first). - * - * @param value The number's value. - * @param size The number's size in bytes. - */ - fun convIntegerToBytes(value: BigInteger, size: UInt): List { - var bytes: List = value.toByteArray().toList().map { x -> x.toUByte() } - bytes += List(size.toInt() - bytes.size, {0u}) // add padding for missing bytes - return bytes - } - - /** - * Write a string to the connection. - * - * In 9P, strings are represented as a 2-byte integer (the string's size) followed by the actual UTF-8 string. The - * null terminator is forbidden in 9P messages. - * - * @param value The string. - * @throws IllegalArgumentException if the value of the string's size does not fit into 2 bytes. - */ - fun convStringToBytes(value: String): List { - require(value.length <= 2.0.pow(16.0) - 1) - var bytes = convIntegerToBytes(value.length.toBigInteger(), 2u) - bytes += value.toByteArray().toList().map { x -> x.toUByte() } - return bytes - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/net/TransportLayer.kt b/src/main/kotlin/net/TransportLayer.kt deleted file mode 100644 index 90dcd11..0000000 --- a/src/main/kotlin/net/TransportLayer.kt +++ /dev/null @@ -1,59 +0,0 @@ -package net - -import java.io.Closeable - -/** - * [TransportLayer] is an interface for network transport-layer operations. A class that implements these methods, once - * instantiated, establishes and manages a connection with a remote endpoint defined by an address and a port and allows - * to send and receive network messages (also called "payloads"). - * - * The address of the remote endpoint can be an IP address (v4 or v6) or a domain name, in which case it is resolved to - * an IP address right before initializing the connection. Every constructor should throw an - * [except.UnresolvableHostException] if the remote address is formatted as an actual domain name, but it cannot be - * resolved (e.g. it doesn't exist, or it contains forbidden characters). - * - * Depending on the specific given implementation, the constructor of this class might throw other exceptions (e.g. the - * [java.net.Socket] constructor in [TransportLayerJavaNet]). - */ -interface TransportLayer : Closeable { - /** - * Close the connection. - */ - abstract override fun close() - - /** - * Transmit a payload. - * - * @throws java.io.IOException if the message could not be correctly transmitted. - */ - fun transmit(payload: Iterable) -/* - /** - * Receive a payload until a byte occurs, which marks the end of the message. The byte is discarded after being read - * and is not returned. - * - * If you know both which byte marks the end of the message and the message length, it is advised to use - * [receiveFixed] instead, which is usually more efficient. - * - * @param untilByte The byte that marks the end of the message. - * @return the received payload. - * @throws java.io.IOException if the message could not be correctly received. - */ - abstract fun receiveUntil(untilByte: UByte): Array -*/ - /** - * Receive a payload with fixed length. If zero, nothing is read. - * - * @param length The length of the message in bytes. - * @return the received payload. - * @throws java.io.IOException if the message could not be correctly received. - */ - fun receive(length: ULong): Array - - /** - * Gives the caller a "receiver" (i.e. an instance of Iterable) from which raw data of any length can be read. - * - * @return The receiver. - */ - fun receiver(): Iterable -} \ No newline at end of file diff --git a/src/main/kotlin/net/TransportLayerJavaNet.kt b/src/main/kotlin/net/TransportLayerJavaNet.kt deleted file mode 100644 index 3d2867a..0000000 --- a/src/main/kotlin/net/TransportLayerJavaNet.kt +++ /dev/null @@ -1,95 +0,0 @@ -package net - -import nineAddressToValues -import java.io.InputStream -import java.io.OutputStream -import java.net.Socket -import kotlin.math.min - -/* -TODO: - - add TLS support -*/ - -/** - * An implementation of [TransportLayer] written using the [java.net] package. - */ -class TransportLayerJavaNet(val address: String, val port: UShort) : TransportLayer { - /** - * The connection's socket. - */ - private val socket: Socket = Socket(this.address, this.port.toInt()) - - /** - * The connection's input stream. - */ - private val inStream: InputStream = this.socket.inputStream - - /** - * The connection's output stream. - */ - private val outStream: OutputStream = this.socket.outputStream - - constructor(fullAddress: String) : this(nineAddressToValues(fullAddress).first, nineAddressToValues(fullAddress).second) - - private class InStreamIterator(val inStream: InputStream) : Iterator { - override fun next(): UByte { - return this.inStream.readNBytes(1).first().toUByte() - } - - override fun hasNext(): Boolean { - return this.inStream.available() > 0 - } - } - - override fun close() { - if (this.socket.isClosed) { - return - } - this.socket.close() - } - - override fun transmit(payload: Iterable) { - val payload = payload.toList() - val bytes = ByteArray(payload.size, { i -> payload[i].toByte() }) - this.outStream.write(bytes) - } - -/* - override fun receiveUntil(untilByte: UByte): Array { - var stop = false - val payload: Array = MutableList(0, { 0 }) - while (!stop) { - val b = this.inStream.readNBytes(1)[0] - if (b == untilByte) { - stop = true - continue - } else { - payload.add(b) - } - } - return payload - } -*/ - - override fun receive(length: ULong): Array { - var length = length - val intMax = Int.MAX_VALUE.toULong() - val bytes: MutableList = MutableList(0) { 0 } - // readNBytes only takes Int values, so it must be called multiple times if the length is greater than Int's - // maximum value - while (length > 0u) { - // the min function ensures that the value passed to readNBytes never exceeds Int's maximum value while also - // switching to the length variable when its value eventually becomes less than Int's maximum value, which - // avoids writing duplicated readNBytes calls in the code - val lenMin = min(length, intMax) - bytes += this.inStream.readNBytes(lenMin.toInt()).toMutableList() - length -= intMax - } - return Array(bytes.size) { i -> bytes[i].toUByte() } - } - - override fun receiver(): Iterable { - return Iterable { InStreamIterator(this.inStream) } - } -} \ No newline at end of file -- cgit v1.2.3