import java.io.IOException import java.math.BigInteger import kotlin.math.pow /** * 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. */ val npt: NetworkPacketTransporter = netPackTrans /** * Has the 9P connection been initialized yet? */ private var hasBeenInitialized = false /** * Disconnect from the remote host, * * @throws IOException if an I/O error occurred while closing the socket. */ fun disconnect() { this.npt.close() } /** * Read an [nBytes]-long unsigned integer number from the connection. * * In 9P, binary numbers (non-textual) are specified in little-endian order (least significant byte first). * * @param nBytes The length of the integer number in bytes. It only works with unsigned numbers strictly greater * than zero (zero is excluded). * @return The number's value. * @throws IllegalArgumentException if [nBytes] is zero or negative. */ private fun readInteger(nBytes: Int): BigInteger { require(nBytes > 0) val bytes = this.npt.receiveFixed(nBytes.toULong()) var number: BigInteger = BigInteger.valueOf(0) for (i in 0.. { return Triple( readInteger(4).toInt().toUInt(), NinePMessageType.fromByte(readInteger(1).toByte()), readInteger(2).toInt().toUInt() ) } /** * 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. */ private fun waitForTag(tag: UInt): Triple { var s = 0u var ty: NinePMessageType? var ta = 0u var found = false while (!found) { val stt = readSizeTypeTag() s = stt.first ty = stt.second ta = stt.third if (ta == tag) { found = true } } return s } /** * 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 */ private fun readError(): Pair { val tag = readInteger(2) val error = readString() return Pair(SizedMessageField(2, tag), error) } /** * Write an integer number to the connection. * * In 9P, binary numbers (non-textual) are specified in little-endian order (least significant byte first). * * @param value The number's value. [SizedMessageField] defines both its actual value and its size. */ private fun writeInteger(value: SizedMessageField) { this.npt.transmit(value.value.toByteArray().toList()) } /** * Write a string to 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. * * @param value The string. * @throws IllegalArgumentException if the value of the string's size does not fit into 2 bytes. */ private fun writeString(value: String) { require(value.length <= 2.0.pow(16.0) - 1) writeInteger(SizedMessageField(2, value.length.toBigInteger())) this.npt.transmit(value.toByteArray().toList()) } /** * Write the message size and type. * * @param size The total message size, including the 4 bytes of this parameter and the type's byte. * @param type The 9P message type as a [NinePMessageType] constant. */ private fun writeMessageSizeType(size: Int, type: NinePMessageType) { writeInteger(SizedMessageField(4, size.toBigInteger())) writeInteger(SizedMessageField(1, type.value.toInt().toBigInteger())) } /** * Write a message of the given fields. * * Important note: the field names in [fieldValuesInt] and [fieldValuesStr] (i.e. the keys of their maps) are * mutually exclusive and the union of these two sets must result exactly in the set of field names listed in * [fieldNames]. * * @param type The 9P message type. * @param fieldNames The names of the message fields, in the same order they are expected to be sent. * @param fieldValuesInt A map of each field name into its value. This map only stores integer values. * @param fieldValuesStr A map of each field name into its value. This map only stores string values. * @throws IllegalArgumentException if [fieldNames], [fieldValuesInt], and [fieldValuesStr] are incoherent. */ private fun writeMessage(type: NinePMessageType, fieldNames: List, fieldValuesInt: Map, fieldValuesStr: Map) { // shorthands val insecInts = fieldNames.intersect(fieldValuesInt.keys) val insecStrs = fieldNames.intersect(fieldValuesStr.keys) // check that names in fieldNames exist as keys in either fieldValuesInt or fieldValuesStr but not both require(insecInts.size == fieldNames.size - insecStrs.size) val totalSize = 4 + 1 + insecInts.sumOf { fieldValuesInt[it]!!.size } + insecStrs.sumOf { 2 + fieldValuesStr[it]!!.length } writeMessageSizeType(totalSize, type) for (field in fieldNames) { if (field in insecInts) { writeInteger(fieldValuesInt[field]!!) } else { writeString(fieldValuesStr[field]!!) } } } override fun version(tag: SizedMessageField, msize: SizedMessageField, version: String): String? { writeMessage(NinePMessageType.VERSION, listOf("tag", "msize", "version"), mapOf( "tag" to tag, "msize" to msize ), mapOf( "version" to version ) ) val se = ignoreUntilType(NinePMessageType.VERSION) if (se.second) { val error = readError() return error.second } } override fun auth() { 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") } }