From 6b3eb12717a40590cd1dc8a7189c0a68e2e6bae6 Mon Sep 17 00:00:00 2001 From: Edoardo La Greca Date: Wed, 30 Jul 2025 16:30:00 +0200 Subject: move message construction and sending methods to the new NinePMessage class --- src/main/kotlin/NinePConnection.kt | 76 +---------------------------------- src/main/kotlin/NinePMessage.kt | 82 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 74 deletions(-) create mode 100644 src/main/kotlin/NinePMessage.kt (limited to 'src/main/kotlin') diff --git a/src/main/kotlin/NinePConnection.kt b/src/main/kotlin/NinePConnection.kt index b23fd1f..7f52ad5 100644 --- a/src/main/kotlin/NinePConnection.kt +++ b/src/main/kotlin/NinePConnection.kt @@ -1,6 +1,5 @@ import java.io.IOException import java.math.BigInteger -import kotlin.math.pow /** * This class represents a 9P connection. It provides a practical implementation with networking to the 9P methods @@ -118,86 +117,15 @@ class NinePConnection(netPackTrans: NetworkPacketTransporter) : NinePTranslator return Pair(SizedMessageField(2, tag), error) } - /** - * Write an integer number to the connection. - * - * In 9P, binary numbers (non-textual) are specified in little-endian order (least significant byte first). - * - * @param value The number's value. [SizedMessageField] defines both its actual value and its size. - */ - private fun writeInteger(value: SizedMessageField) { - this.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 value The string. - * @throws IllegalArgumentException if the value of the string's size does not fit into 2 bytes. - */ - private fun writeString(value: String) { - require(value.length <= 2.0.pow(16.0) - 1) - writeInteger(SizedMessageField(2, value.length.toBigInteger())) - this.npt.transmit(value.toByteArray().toList()) - } - - /** - * Write the message size and type. - * - * @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(size: Int, type: NinePMessageType) { - writeInteger(SizedMessageField(4, size.toBigInteger())) - writeInteger(SizedMessageField(1, type.value.toInt().toBigInteger())) - } - - /** - * Write a 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]. - * - * @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. - * @throws IllegalArgumentException if [fieldNames], [fieldValuesInt], and [fieldValuesStr] are incoherent. - */ - private fun writeMessage(type: NinePMessageType, tag: UShort, fieldNames: List, fieldValuesInt: Map, fieldValuesStr: Map) { - // 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(totalSize, type) - writeInteger(SizedMessageField(2, tag.toInt().toBigInteger())) - for (field in fieldNames) { - if (field in insecInts) { - writeInteger(fieldValuesInt[field]!!) - } else { - writeString(fieldValuesStr[field]!!) - } - } - } - override fun version(msize: SizedMessageField, version: String): String? { - writeMessage(NinePMessageType.TVERSION, this.NOTAG, listOf("msize", "version"), + NinePMessage(NinePMessageType.TVERSION, this.NOTAG, listOf("msize", "version"), mapOf( "msize" to msize ), mapOf( "version" to version ) - ) + ).write(this.npt) val se = waitForTag(this.NOTAG) if (se.second == NinePMessageType.RERROR) { val error = readError() 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, val fieldValuesInt: Map, val fieldValuesStr: Map) { + /** + * 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 -- cgit v1.2.3