1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
|
import java.math.BigInteger
import kotlin.math.pow
/**
* An outgoing 9P message with the given type, tag, and fields. The message size is calculated automatically.
*
* Important note: the field names in [fieldValuesInt], [fieldValuesStr], and [fieldValuesRaw] (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 OutMessage(val type: NinePMessageType, val tag: UShort, val fieldNames: List<String>, val fieldValuesInt: Map<String, BigInteger>, val fieldValuesStr: Map<String, String>, val fieldValuesRaw: Map<String, Array<UByte>>, val maxSize: UInt) {
/**
* Intersection between [fieldNames] and [fieldValuesInt]. In other words: the integer fields that are going to be
* used when writing the message.
*/
private val insecInts = fieldNames.intersect(fieldValuesInt.keys)
/**
* Intersection between [fieldNames] and [fieldValuesStr]. In other words: the string fields that are going to be
* used when writing the message.
*/
private val insecStrs = fieldNames.intersect(fieldValuesStr.keys)
/**
* Intersection between [fieldNames] and [fieldValuesRaw]. In other words: the raw fields that are going to be used
* when writing the message.
*/
private val insecRaws = fieldNames.intersect(fieldValuesRaw.keys)
/**
* Send the message using the given networking API.
*
* @param tl The networking API.
* @throws IllegalArgumentException if [fieldNames], [fieldValuesInt], and [fieldValuesStr] are incoherent or the
* final size of the message exceeds the negotiated value.
*/
fun write(tl: TransportLayer) {
// check that names in fieldNames exist as keys in either fieldValuesInt or fieldValuesStr but not both
require(fieldNames.size == insecInts.size + insecStrs.size + insecRaws.size)
val totalSize = size()
if (totalSize > this.maxSize) {
throw IllegalArgumentException("Message size exceeded.")
}
writeMessageSizeType(tl, totalSize, type)
writeInteger(tl, tag.toInt().toBigInteger())
for (field in fieldNames) {
if (field in insecInts) {
writeInteger(tl, fieldValuesInt[field]!!)
} else if (field in insecStrs) {
writeString(tl, fieldValuesStr[field]!!)
} else {
tl.transmit(fieldValuesRaw[field]!!)
}
}
}
/**
* Write the message size and type.
*
* @param tl 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(tl: TransportLayer, size: UInt, type: NinePMessageType) {
writeInteger(tl, size.toInt().toBigInteger())
writeInteger(tl, type.value.toInt().toBigInteger())
}
/**
* Calculate the expected size of the message.
*/
fun size(): UInt {
return 4u + 1u + 2u + this.insecInts.sumOf { this.fieldValuesInt[it]!!.bitLength().toUInt() } + this.insecStrs.sumOf { 2u + this.fieldValuesStr[it]!!.length.toUInt() } + this.insecRaws.sumOf { this.fieldValuesRaw[it]!!.size.toUInt() }
}
companion object {
// TODO: Add size that the value is required to fit in
/**
* Write an integer number to the connection.
*
* In 9P, binary numbers (non-textual) are specified in little-endian order (least significant byte first).
*
* @param tl The networking API.
* @param value The number's value.
*/
fun writeInteger(tl: TransportLayer, value: BigInteger) {
val bytes = value.toByteArray()
tl.transmit(Array(bytes.size) { i -> bytes[i].toUByte() })
}
/**
* 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 tl The networking API.
* @param value The string.
* @throws IllegalArgumentException if the value of the string's size does not fit into 2 bytes.
*/
fun writeString(tl: TransportLayer, value: String) {
require(value.length <= 2.0.pow(16.0) - 1)
writeInteger(tl, value.length.toBigInteger())
val bytes = value.toByteArray()
tl.transmit(Array(bytes.size) { i -> bytes[i].toUByte() })
}
}
}
|