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") } }