diff options
Diffstat (limited to 'src/main/kotlin/Connection.kt')
-rw-r--r-- | src/main/kotlin/Connection.kt | 246 |
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 |