summaryrefslogtreecommitdiff
path: root/src/main/kotlin/Connection.kt
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/kotlin/Connection.kt')
-rw-r--r--src/main/kotlin/Connection.kt246
1 files changed, 246 insertions, 0 deletions
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<UInt, String> = 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..<bytes.size) {
+ value += bytes[i].toInt().toBigInteger().shl(i*8)
+ }
+ return value
+ }
+
+ /**
+ * Read a string from 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.
+ *
+ * @return the string.
+ * @throws java.io.IOException if the message could not be correctly received.
+ */
+ private fun readString(): String {
+ val length = readInteger(2u).toShort().toUShort()
+ val rawString = this.tl.receive(length.toULong())
+ return String(ByteArray(rawString.size) { i -> 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<UInt, NinePMessageType, UShort> {
+ 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<UInt, NinePMessageType> {
+ 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<UShort, String> {
+ 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<UByte>): 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