From 10d9b3c1dcce9918222b85074c8d4bda07c934f9 Mon Sep 17 00:00:00 2001 From: Edoardo La Greca Date: Tue, 5 Aug 2025 15:28:45 +0200 Subject: expand doc and change return type for read method --- src/main/kotlin/Connection.kt | 246 ++++++++++++++++++++++++++++++++++ src/main/kotlin/NinePConnection.kt | 221 ------------------------------ src/main/kotlin/ProtocolTranslator.kt | 8 +- 3 files changed, 252 insertions(+), 223 deletions(-) create mode 100644 src/main/kotlin/Connection.kt delete mode 100644 src/main/kotlin/NinePConnection.kt (limited to 'src') diff --git a/src/main/kotlin/Connection.kt b/src/main/kotlin/Connection.kt new file mode 100644 index 0000000..7addc10 --- /dev/null +++ b/src/main/kotlin/Connection.kt @@ -0,0 +1,246 @@ +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 [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 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 + + /** + * Each FID associated with each file. + */ + val fids: Map = emptyMap() + + // 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() + } + + /** + * Read an [len] bytes long unsigned integer number from the connection. + * + * 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 java.io.IOException if the message could not be correctly received. + */ + private fun readInteger(len: ULong): BigInteger { + val bytes = this.tl.receive(len) + var value = 0.toBigInteger() + for (i in 0.. rawString[i].toByte() }) + } + + /** + * Read a message size, type, and tag. + * + * @return A triple in which the first element is the message size in bytes, the second is the message type as a + * [NinePMessageType] constant, and the third element is the message tag. + * @throws java.io.IOException if the message could not be correctly received. + */ + private fun readSizeTypeTag(): Triple { + return Triple( + readInteger(4u).toInt().toUInt(), + NinePMessageType.fromByte(readInteger(1u).toByte().toUByte()), + readInteger(2u).toShort().toUShort() + ) + } + + /** + * Wait for a 9P message with the same tag from the server. + * + * All messages prior to the returned one are discarded. + * + * @param tag The tag to wait for. + * @return A pair of (1) the size and (2) the type of the message that matches the given tag. + * @throws java.io.IOException if the message could not be correctly received. + */ + private fun waitForTag(tag: UShort): Pair { + var s = 0u + var ty: NinePMessageType? = null + var ta: UShort + var found = false + while (!found) { + val stt = readSizeTypeTag() + s = stt.first + ty = stt.second + ta = stt.third + + if (ta == tag) { + found = true + } else { + // discard? + } + } + return Pair(s, ty ?: NinePMessageType.RERROR) + } + + /** + * Read a 9P message of type Rerror, after the message size and type have already been read. + * + * @return A pair of: (1) the message tag and (2) the error message. + * @throws java.io.IOException if the message could not be correctly received. + */ + private fun readError(): Pair { + val tag = readInteger(2u).toInt().toUShort() + val error = readString() + return Pair(tag, error) + } + + /** + * Send 9P request and check for exceptions or an Rerror response. + */ + private fun responseError(msg: Message): String? { + try { + msg.write(this.tl) + } catch (ex: Exception) { + return "Exception thrown while sending message: (" + ex.javaClass.toString() + ") " + ex.message + } + val se = waitForTag(msg.tag) + if (se.second == NinePMessageType.RERROR) { + val error = readError() + return error.second + } + return null + } + + override fun version(msize: UInt, version: String): String? { + val msg = Message(NinePMessageType.TVERSION, this.NOTAG, listOf("msize", "version"), + mapOf( + "msize" to BigInteger(msize.toString()) + ), + mapOf( + "version" to version + ), + this.maxSize + ) + val error = responseError(msg) + if (error != null) { + return error + } + waitForTag(msg.tag) + val rmsize = readInteger(4u).toInt().toUInt() + val rversion = readString() + // this check should not be necessary, but you never know + this.maxSize = ( if (rmsize < msize) msize else rmsize ) + if (rversion == "unknown") { + return "Unknown version." + } + return null + } + + 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() { + TODO("Not yet implemented") + } + + override fun attach() { + TODO("Not yet implemented") + } + + override fun walk(path: String) { + TODO("Not yet implemented") + } + + override fun open(path: String) { + TODO("Not yet implemented") + } + + override fun create(path: String) { + TODO("Not yet implemented") + } + + override fun read(fid: UInt, offset: ULong, count: UInt): String? { + val msg = Message(NinePMessageType.TREAD, tagGen.generate(), listOf("fid", "offset", "count"), + mapOf( + "fid" to BigInteger(fid.toString()), + "offset" to BigInteger(offset.toString()), + "count" to BigInteger(count.toString()) + ), + emptyMap(), + this.maxSize + ) + val error = responseError(msg) + if (error != null) { + return error + } + + return null + } + + override fun write(fid: UInt, offset: ULong, count: UInt, data: Iterable): String? { + + } + + override fun clunk(path: String) { + TODO("Not yet implemented") + } + + override fun remove(path: String) { + TODO("Not yet implemented") + } + + override fun stat(path: String) { + TODO("Not yet implemented") + } + + override fun wstat(path: String) { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/src/main/kotlin/NinePConnection.kt b/src/main/kotlin/NinePConnection.kt deleted file mode 100644 index 90b734f..0000000 --- a/src/main/kotlin/NinePConnection.kt +++ /dev/null @@ -1,221 +0,0 @@ -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 [NinePTranslator]. Details about methods related to 9P can be found in [NinePTranslator]. Remember to - * disconnect using [disconnect] after use. - * - * Details about network-related topics can be found in [NetworkPacketTransporter] and the implementation of choice. - * - * Details about 9P messages and methods can be found in [NinePTranslator]. - * - * @param netPackTrans The networking API backend of choice. - * - * @throws UnresolvableHostException if the host resolution made by [netPackTrans] failed. - */ -class NinePConnection(netPackTrans: NetworkPacketTransporter) : NinePTranslator { - /** - * Networking API. - */ - private val npt: NetworkPacketTransporter = netPackTrans - - /** - * 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 - val NOTAG = 0.toUShort().inv() - - - /** - * Disconnect from the remote host, - * - * @throws IOException if an I/O error occurred while closing the socket. - */ - fun disconnect() { - this.npt.close() - } - - /** - * Read an [len] bytes long unsigned integer number from the connection. - * - * 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 java.io.IOException if the message could not be correctly received. - */ - private fun readInteger(len: ULong): BigInteger { - val bytes = this.npt.receive(len) - var value = 0.toBigInteger() - for (i in 0.. rawString[i].toByte() }) - } - - /** - * Read a message size, type, and tag. - * - * @return A triple in which the first element is the message size in bytes, the second is the message type as a - * [NinePMessageType] constant, and the third element is the message tag. - * @throws java.io.IOException if the message could not be correctly received. - */ - private fun readSizeTypeTag(): Triple { - return Triple( - readInteger(4u).toInt().toUInt(), - NinePMessageType.fromByte(readInteger(1u).toByte().toUByte()), - readInteger(2u).toShort().toUShort() - ) - } - - /** - * Wait for a 9P message with the same tag from the server. - * - * All messages prior to the returned one are discarded. - * - * @param tag The tag to wait for. - * @return A pair of (1) the size and (2) the type of the message that matches the given tag. - * @throws java.io.IOException if the message could not be correctly received. - */ - private fun waitForTag(tag: UShort): Pair { - var s = 0u - var ty: NinePMessageType? = null - var ta: UShort - var found = false - while (!found) { - val stt = readSizeTypeTag() - s = stt.first - ty = stt.second - ta = stt.third - - if (ta == tag) { - found = true - } - } - return Pair(s, ty ?: NinePMessageType.RERROR) - } - - /** - * Read a 9P message of type Rerror, after the message size and type have already been read. - * - * @return A pair of: (1) the message tag and (2) the error message. - * @throws java.io.IOException if the message could not be correctly received. - */ - private fun readError(): Pair { - val tag = readInteger(2u).toInt().toUShort() - val error = readString() - return Pair(tag, error) - } - - /** - * Send 9P request and check for exceptions or an Rerror response. - */ - private fun responseError(msg: NinePMessage): String? { - try { - msg.write(this.npt) - } catch (ex: Exception) { - return "Exception thrown while sending message: (" + ex.javaClass.toString() + ") " + ex.message - } - val se = waitForTag(msg.tag) - if (se.second == NinePMessageType.RERROR) { - val error = readError() - return error.second - } - return null - } - - override fun version(msize: UInt, version: String): String? { - val msg = NinePMessage(NinePMessageType.TVERSION, this.NOTAG, listOf("msize", "version"), - mapOf( - "msize" to BigInteger(msize.toString()) - ), - mapOf( - "version" to version - ), - this.maxSize - ) - val error = responseError(msg) - if (error != null) { - return error - } - val rmsize = readInteger(4u).toInt().toUInt() - val rversion = readString() - // this check should not be necessary, but you never know - this.maxSize = ( if (rmsize < msize) msize else rmsize ) - if (rversion == "unknown") { - return "Unknown version." - } - return null - } - - 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() { - TODO("Not yet implemented") - } - - override fun attach() { - TODO("Not yet implemented") - } - - override fun walk(path: String) { - TODO("Not yet implemented") - } - - override fun open(path: String) { - TODO("Not yet implemented") - } - - override fun create(path: String) { - TODO("Not yet implemented") - } - - override fun read(path: String) { - TODO("Not yet implemented") - } - - override fun write(path: String) { - TODO("Not yet implemented") - } - - override fun clunk(path: String) { - TODO("Not yet implemented") - } - - override fun remove(path: String) { - TODO("Not yet implemented") - } - - override fun stat(path: String) { - TODO("Not yet implemented") - } - - override fun wstat(path: String) { - TODO("Not yet implemented") - } - -} \ No newline at end of file diff --git a/src/main/kotlin/ProtocolTranslator.kt b/src/main/kotlin/ProtocolTranslator.kt index bc64002..3dd5d70 100644 --- a/src/main/kotlin/ProtocolTranslator.kt +++ b/src/main/kotlin/ProtocolTranslator.kt @@ -57,9 +57,13 @@ interface ProtocolTranslator { fun create(path: String) /** - * Transfer data from file. + * 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 A pair of: (1) an optional error message and (2) the content read with the call just made. If the string + * is not null, an error occurred and the array is going to be empty. */ - fun read(fid: UInt, offset: ULong, count: UInt): String? + fun read(fid: UInt, offset: ULong, count: UInt): Pair> /** * Transfer data to file. -- cgit v1.2.3