summaryrefslogtreecommitdiff
path: root/src/main/kotlin/NinePMessage.kt
diff options
context:
space:
mode:
authorEdoardo La Greca2025-07-30 16:30:00 +0200
committerEdoardo La Greca2025-07-30 17:58:14 +0200
commit6b3eb12717a40590cd1dc8a7189c0a68e2e6bae6 (patch)
tree5eb1ad81fd348163efec47463118e12f90f88e51 /src/main/kotlin/NinePMessage.kt
parent4267371dfc17faf5b65192e0fba1a511b49993b5 (diff)
move message construction and sending methods to the new NinePMessage class
Diffstat (limited to 'src/main/kotlin/NinePMessage.kt')
-rw-r--r--src/main/kotlin/NinePMessage.kt82
1 files changed, 82 insertions, 0 deletions
diff --git a/src/main/kotlin/NinePMessage.kt b/src/main/kotlin/NinePMessage.kt
new file mode 100644
index 0000000..9edf361
--- /dev/null
+++ b/src/main/kotlin/NinePMessage.kt
@@ -0,0 +1,82 @@
+import kotlin.math.pow
+
+/**
+ * A 9P message with the given type, tag, and fields. The message size is calculated automatically.
+ *
+ * Important note: the field names in [fieldValuesInt] and [fieldValuesStr] (i.e. the keys of their maps) must be
+ * mutually exclusive and the union of these two maps' keys must result in a subset of (or a set equal to)
+ * [fieldNames]. Calling [write] when these conditions are not met throws an exception.
+ *
+ * @param type The 9P message type.
+ * @param tag The tag given to the message.
+ * @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.
+ */
+class NinePMessage(val type: NinePMessageType, val tag: UShort, val fieldNames: List<String>, val fieldValuesInt: Map<String, SizedMessageField>, val fieldValuesStr: Map<String, String>) {
+ /**
+ * Send the message using the given networking API.
+ *
+ * @param npt The networking API.
+ * @throws IllegalArgumentException if [fieldNames], [fieldValuesInt], and [fieldValuesStr] are incoherent.
+ */
+ fun write(npt: NetworkPacketTransporter) {
+ // 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 + 2 + insecInts.sumOf { fieldValuesInt[it]!!.size } + insecStrs.sumOf { 2 + fieldValuesStr[it]!!.length }
+
+ writeMessageSizeType(npt, totalSize, type)
+ writeInteger(npt, SizedMessageField(2, tag.toInt().toBigInteger()))
+ for (field in fieldNames) {
+ if (field in insecInts) {
+ writeInteger(npt, fieldValuesInt[field]!!)
+ } else {
+ writeString(npt, fieldValuesStr[field]!!)
+ }
+ }
+ }
+
+ /**
+ * Write an integer number to the connection.
+ *
+ * In 9P, binary numbers (non-textual) are specified in little-endian order (least significant byte first).
+ *
+ * @param npt The networking API.
+ * @param value The number's value. [SizedMessageField] defines both its actual value and its size.
+ */
+ private fun writeInteger(npt: NetworkPacketTransporter, value: SizedMessageField) {
+ 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 npt The networking API.
+ * @param value The string.
+ * @throws IllegalArgumentException if the value of the string's size does not fit into 2 bytes.
+ */
+ private fun writeString(npt: NetworkPacketTransporter, value: String) {
+ require(value.length <= 2.0.pow(16.0) - 1)
+ writeInteger(npt, SizedMessageField(2, value.length.toBigInteger()))
+ npt.transmit(value.toByteArray().toList())
+ }
+
+ /**
+ * Write the message size and type.
+ *
+ * @param npt The networking API.
+ * @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(npt: NetworkPacketTransporter, size: Int, type: NinePMessageType) {
+ writeInteger(npt, SizedMessageField(4, size.toBigInteger()))
+ writeInteger(npt, SizedMessageField(1, type.value.toInt().toBigInteger()))
+ }
+} \ No newline at end of file