diff options
author | Edoardo La Greca | 2025-08-18 21:09:11 +0200 |
---|---|---|
committer | Edoardo La Greca | 2025-08-18 21:09:11 +0200 |
commit | 7341ead2aade10ea1b833e94275277658741883a (patch) | |
tree | 46495f24c54278d50aa0da5046822fbe502f3f14 /lib/src/main/kotlin/Connection.kt | |
parent | 1e50cf9c224d03896f176f3718ff80ef1659e9c2 (diff) |
switch to multi-module project structure
Diffstat (limited to 'lib/src/main/kotlin/Connection.kt')
-rw-r--r-- | lib/src/main/kotlin/Connection.kt | 319 |
1 files changed, 319 insertions, 0 deletions
diff --git a/lib/src/main/kotlin/Connection.kt b/lib/src/main/kotlin/Connection.kt new file mode 100644 index 0000000..7f2e505 --- /dev/null +++ b/lib/src/main/kotlin/Connection.kt @@ -0,0 +1,319 @@ +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<String>): List<QID> { + 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<QID> = mutableListOf() + for (i in 0..<nwqid/13) { + val start = i * 13 + val end = ((i+1) * 13) - 1 + val rawQid = rawQids.slice(start..end) + qids += QID(rawQid) + } + return qids.toList() + } + + override fun open(fid: UInt, mode: FileMode): Pair<QID, UInt> { + 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<QID, UInt> { + 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<UByte> { + 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<UByte>): 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) + } +}
\ No newline at end of file |