diff options
author | Edoardo La Greca | 2025-07-25 17:01:03 +0200 |
---|---|---|
committer | Edoardo La Greca | 2025-07-25 17:01:03 +0200 |
commit | 8f106f2fea4e2bcf780a3b27d3cf1105e0b827ef (patch) | |
tree | 607720bfa386645cdbc1ed4eb8703da70f93b438 /src | |
parent | 3abb34a08e2446b8a07a0d9701783defc5d856fb (diff) |
add write operations
Diffstat (limited to 'src')
-rw-r--r-- | src/main/kotlin/NinePConnection.kt | 70 |
1 files changed, 70 insertions, 0 deletions
diff --git a/src/main/kotlin/NinePConnection.kt b/src/main/kotlin/NinePConnection.kt index c5d330e..2a28df8 100644 --- a/src/main/kotlin/NinePConnection.kt +++ b/src/main/kotlin/NinePConnection.kt @@ -1,5 +1,6 @@ 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 @@ -114,4 +115,73 @@ class NinePConnection(netPackTrans: NetworkPacketTransporter) : NinePTranslator val error = readString() 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 of the given fields. + * + * Important note: the field names in [fieldValuesInt] and [fieldValuesStr] (i.e. the keys of their maps) are + * mutually exclusive and the union of these two sets must result exactly in the set of field names listed in + * [fieldNames]. + * + * @param type The 9P message type. + * @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, fieldNames: List<String>, fieldValuesInt: Map<String, SizedMessageField>, fieldValuesStr: Map<String, String>) { + // 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 + insecInts.sumOf { fieldValuesInt[it]!!.size } + insecStrs.sumOf { 2 + fieldValuesStr[it]!!.length } + + writeMessageSizeType(totalSize, type) + for (field in fieldNames) { + if (field in insecInts) { + writeInteger(fieldValuesInt[field]!!) + } else { + writeString(fieldValuesStr[field]!!) + } + } + } }
\ No newline at end of file |