summaryrefslogtreecommitdiff
path: root/lib/src/main/kotlin/Connection.kt
diff options
context:
space:
mode:
authorEdoardo La Greca2025-08-18 21:09:11 +0200
committerEdoardo La Greca2025-08-18 21:09:11 +0200
commit7341ead2aade10ea1b833e94275277658741883a (patch)
tree46495f24c54278d50aa0da5046822fbe502f3f14 /lib/src/main/kotlin/Connection.kt
parent1e50cf9c224d03896f176f3718ff80ef1659e9c2 (diff)
switch to multi-module project structure
Diffstat (limited to 'lib/src/main/kotlin/Connection.kt')
-rw-r--r--lib/src/main/kotlin/Connection.kt319
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