import except.MsizeValueTooBigException import except.RErrorException import except.UnaccessibleFileException import except.UnknownVersionException import net.InMessage import net.OutMessage import net.TransportLayer 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 [net.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 except.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 // 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 [net.InMessage] instance and check for errors. After successfully using this * function, it is guaranteed that both no error occurred while reading the incoming message and the message is not * of type R-error. * * It uses [tl] and [maxSize] for instancing the [net.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 [net.InMessage] instance (null if an * error occurred). * @throws except.InvalidMessageException if the received message is invalid. * @throws except.RErrorException if the received message is an R-error message. */ private fun checkedInMessage(reqTag: UShort): InMessage { val imsg = InMessage(this.tl, this.maxSize, reqTag) if (imsg.type == NinePMessageType.RERROR) { imsg.applyField(InMessage.Field("ename", InMessage.Field.Type.STRING, 0u)) throw RErrorException(imsg.fieldsStr["ename"]) } return imsg } override fun version(msize: UInt, version: String) { val omsg = OutMessage(NinePMessageType.TVERSION, this.NOTAG, listOf("msize", "version"), mapOf( "msize" to Pair(BigInteger(msize.toString()), 4u) ), mapOf( "version" to version ), emptyMap(), this.maxSize ) omsg.write(this.tl) val imsg = checkedInMessage(omsg.tag) 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) { throw MsizeValueTooBigException(msize, remoteMaxSize) } this.maxSize = remoteMaxSize if (!imsg.fieldsStr["version"]!!.startsWith("9P2000")) { throw UnknownVersionException(imsg.fieldsStr["version"]!!) } } override fun auth(afid: UInt, uname: String, aname: String) { } override fun flush(oldtag: UShort) { val omsg = OutMessage(NinePMessageType.TFLUSH, this.tagGen.generate(), listOf("oldtag"), mapOf( "oldtag" to Pair(BigInteger(oldtag.toString()), 2u) ), emptyMap(), emptyMap(), this.maxSize ) omsg.write(this.tl) val imsg = checkedInMessage(omsg.tag) } override fun attach(fid: UInt, afid: UInt, uname: String, aname: String): QID { val omsg = OutMessage(NinePMessageType.TATTACH, this.tagGen.generate(), listOf("fid", "afid", "uname", "aname"), mapOf( "fid" to Pair(BigInteger(fid.toString()), 4u), "afid" to Pair(BigInteger(afid.toString()), 4u) ), mapOf( "uname" to uname, "aname" to aname ), emptyMap(), this.maxSize ) omsg.write(this.tl) val imsg = checkedInMessage(omsg.tag) imsg.applyField(InMessage.Field("qid", InMessage.Field.Type.RAW, 13u)) val qid = QID(imsg.fieldsRaw["qid"]!!.toList()) return qid } override fun walk(fid: UInt, newfid: UInt, wname: List): List { val nwname = wname.size val omsg = OutMessage(NinePMessageType.TWALK, this.tagGen.generate(), listOf("fid", "newfid", "nwname"), mapOf( "fid" to Pair(BigInteger(fid.toString()), 4u), "newfid" to Pair(BigInteger(newfid.toString()), 4u), "nwname" to Pair(BigInteger(nwname.toString()), 2u) ), emptyMap(), emptyMap(), this.maxSize ) omsg.write(this.tl) for (wn in wname) { // write wname elements this.tl.transmit(OutMessage.convStringToBytes(wn)) } val imsg = checkedInMessage(omsg.tag) imsg.applyField(InMessage.Field("nwqid", InMessage.Field.Type.INTEGER, 2u)) val nwqid = imsg.fieldsInt["nwqid"]!!.toInt() if (nwqid < nwname) { throw UnaccessibleFileException(wname.slice(0..nwqid)) } imsg.applyField(InMessage.Field("qids", InMessage.Field.Type.RAW, (nwqid * 13).toUInt())) val rawQids = imsg.fieldsRaw["qids"]!! val qids: MutableList = mutableListOf() for (i in 0.. { val omsg = OutMessage(NinePMessageType.TOPEN, this.tagGen.generate(), listOf("fid", "mode"), mapOf( "fid" to Pair(BigInteger(fid.toString()), 4u), "mode" to Pair(BigInteger(mode.toModeByte().toString()), 1u) ), emptyMap(), emptyMap(), this.maxSize ) omsg.write(this.tl) val imsg = checkedInMessage(omsg.tag) imsg.applySchema(listOf( InMessage.Field("qid", InMessage.Field.Type.RAW, 13u), InMessage.Field("iounit", InMessage.Field.Type.INTEGER, 4u) )) val qid = QID(imsg.fieldsRaw["qid"]!!.toList()) val iounit = imsg.fieldsInt["iounit"]!!.toInt().toUInt() return Pair(qid, iounit) } override fun create(fid: UInt, name: String, perm: FilePermissions, mode: FileMode): Pair { val omsg = OutMessage(NinePMessageType.TCREATE, this.tagGen.generate(), listOf("fid", "name", "perm", "mode"), mapOf( "fid" to Pair(BigInteger(fid.toString()), 4u), "perm" to Pair(BigInteger(perm.toPermissionInt().toString()), 4u), "mode" to Pair(BigInteger(mode.toModeByte().toString()), 1u) ), mapOf( "name" to name ), emptyMap(), this.maxSize ) omsg.write(this.tl) val imsg = checkedInMessage(omsg.tag) imsg.applySchema(listOf( InMessage.Field("qid", InMessage.Field.Type.RAW, 13u), InMessage.Field("iounit", InMessage.Field.Type.INTEGER, 4u) )) val qid = QID(imsg.fieldsRaw["qid"]!!.toList()) val iounit = imsg.fieldsInt["iounit"]!!.toInt().toUInt() return Pair(qid, iounit) } override fun read(fid: UInt, offset: ULong, count: UInt): Array { val omsg = OutMessage(NinePMessageType.TREAD, this.tagGen.generate(), listOf("fid", "offset", "count"), mapOf( "fid" to Pair(BigInteger(fid.toString()), 4u), "offset" to Pair(BigInteger(offset.toString()), 8u), "count" to Pair(BigInteger(count.toString()), 4u) ), emptyMap(), emptyMap(), this.maxSize ) omsg.write(this.tl) val imsg = checkedInMessage(omsg.tag) 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 imsg.fieldsRaw["data"]!! } override fun write(fid: UInt, offset: ULong, count: UInt, data: Iterable): UInt { val data = data.take(count.toInt()).toTypedArray() val omsg = OutMessage(NinePMessageType.TWRITE, this.tagGen.generate(), listOf("offset", "count", "data"), mapOf( "offset" to Pair(BigInteger(offset.toString()), 8u), "count" to Pair(BigInteger(count.toString()), 4u) ), emptyMap(), mapOf( "data" to data.toList() ), this.maxSize ) omsg.write(this.tl) val imsg = checkedInMessage(omsg.tag) imsg.applyField(InMessage.Field("count", InMessage.Field.Type.INTEGER, 4u)) val count = imsg.fieldsInt["count"]!! return count.toInt().toUInt() } override fun clunk(fid: UInt) { val omsg = OutMessage(NinePMessageType.TCLUNK, this.tagGen.generate(), listOf("fid"), mapOf( "fid" to Pair(BigInteger(fid.toString()), 4u) ), emptyMap(), emptyMap(), this.maxSize ) omsg.write(this.tl) val imsg = checkedInMessage(omsg.tag) } override fun remove(fid: UInt) { val omsg = OutMessage(NinePMessageType.TREMOVE, this.tagGen.generate(), listOf("fid"), mapOf( "fid" to Pair(BigInteger(fid.toString()), 4u) ), emptyMap(), emptyMap(), this.maxSize ) omsg.write(this.tl) val imsg = checkedInMessage(omsg.tag) } override fun stat(fid: UInt): Stat { val omsg = OutMessage(NinePMessageType.TSTAT, this.tagGen.generate(), listOf("fid"), mapOf( "fid" to Pair(BigInteger(fid.toString()), 4u) ), emptyMap(), emptyMap(), this.maxSize ) omsg.write(this.tl) val imsg = checkedInMessage(omsg.tag) imsg.applyField(InMessage.Field("nstat", InMessage.Field.Type.INTEGER, 2u)) val nstat = imsg.fieldsInt["nstat"]!!.toInt().toUInt() imsg.applyField(InMessage.Field("stat", InMessage.Field.Type.RAW, nstat)) val rawStat = imsg.fieldsRaw["stat"]!!.toList() return Stat(fid, rawStat) } override fun wstat(fid: UInt, stat: Stat) { val omsg = OutMessage(NinePMessageType.TWSTAT, this.tagGen.generate(), listOf("fid", "stat"), mapOf( "fid" to Pair(BigInteger(fid.toString()), 4u) ), emptyMap(), mapOf( "stat" to stat.toRaw() ), this.maxSize ) omsg.write(this.tl) val imsg = checkedInMessage(omsg.tag) } }