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