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() } /** * Handy function to create an [InMessage] instance and check for errors. It uses [tl] and [maxSize] for instancing * the [InMessage] class. * * @return A pair of: (1) a nullable string (which can be: `null` if no error occurred, empty if an error occurred * with no message, or non-empty with the error message) and (2) the optional [InMessage] instance (null if an error * occurred). */ private fun checkedInMessage(reqTag: UShort): Pair { val imsg: InMessage try { imsg = InMessage(this.tl, this.maxSize, reqTag) } catch (ime: InvalidMessageException) { return Pair(ime.message!!, null) } if (imsg.type == NinePMessageType.RERROR) { imsg.applyField(InMessage.Field("ename", InMessage.Field.Type.STRING, 0u)) return Pair(imsg.fieldsStr["ename"]!!, null) } return Pair(null, imsg) } override fun version(msize: UInt, version: String): String? { val omsg = OutMessage(NinePMessageType.TVERSION, this.NOTAG, listOf("msize", "version"), mapOf( "msize" to BigInteger(msize.toString()) ), mapOf( "version" to version ), emptyMap(), this.maxSize ) val imsg: InMessage try { imsg = InMessage(this.tl, msize, omsg.tag) } catch (ime: InvalidMessageException) { return ime.message } imsg.applySchema(listOf( InMessage.Field("msize", InMessage.Field.Type.INTEGER, 4u), InMessage.Field("version", InMessage.Field.Type.STRING, 0u) )) val remoteMaxSize = imsg.fieldsInt["msize"]!!.toInt().toUInt() if (remoteMaxSize > this.maxSize) { return "Invalid remote msize value (too big)." } this.maxSize = remoteMaxSize if (imsg.fieldsStr["version"] == "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): Pair> { val omsg = OutMessage(NinePMessageType.TREAD, this.tagGen.generate(), listOf("fid", "offset", "count"), mapOf( "fid" to BigInteger(fid.toString()), "offset" to BigInteger(offset.toString()), "count" to BigInteger(count.toString()) ), emptyMap(), emptyMap(), this.maxSize ) val checkErr = checkedInMessage(omsg.tag) if (checkErr.first != null) { return Pair(checkErr.first, emptyArray()) } val imsg = checkErr.second!! imsg.applyField(InMessage.Field("count", InMessage.Field.Type.INTEGER, 4u)) val count = imsg.fieldsInt["count"]!!.toInt().toUInt() imsg.applyField(InMessage.Field("data", InMessage.Field.Type.RAW, count)) return Pair(null, imsg.fieldsRaw["data"]!!) } override fun write(fid: UInt, offset: ULong, count: UInt, data: Iterable): Pair { val data = data.take(count.toInt()).toTypedArray() val omsg = OutMessage(NinePMessageType.TWRITE, this.tagGen.generate(), listOf("offset", "count", "data"), mapOf( "offset" to BigInteger(offset.toString()), "count" to BigInteger(count.toString()), ), emptyMap(), mapOf( "data" to data ), this.maxSize ) val checkErr = checkedInMessage(omsg.tag) if (checkErr.first != null) { return Pair(checkErr.first, 0u) } val imsg = checkErr.second!! imsg.applyField(InMessage.Field("count", InMessage.Field.Type.INTEGER, 4u)) val count = imsg.fieldsInt["count"]!! return Pair(null, count.toInt().toUInt()) } 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") } }