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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
|
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 [NinePTranslator]. Details about methods related to 9P can be found in [NinePTranslator]. Remember to
* disconnect using [disconnect] after use.
*
* Details about network-related topics can be found in [NetworkPacketTransporter] and the implementation of choice.
*
* Details about 9P messages and methods can be found in [NinePTranslator].
*
* @param netPackTrans The networking API backend of choice.
*
* @throws UnresolvableHostException if the host resolution made by [netPackTrans] failed.
*/
class NinePConnection(netPackTrans: NetworkPacketTransporter) : NinePTranslator {
/**
* Networking API.
*/
private val npt: NetworkPacketTransporter = netPackTrans
/**
* 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
val NOTAG = 0.toUShort().inv()
/**
* Disconnect from the remote host,
*
* @throws IOException if an I/O error occurred while closing the socket.
*/
fun disconnect() {
this.npt.close()
}
/**
* Read an [len] bytes long unsigned integer number from the connection.
*
* In 9P, binary numbers (non-textual) are specified in little-endian order (least significant byte first).
*
* @param len The length of the integer number in bytes. If zero, nothing is read.
* @return the number's value.
* @throws java.io.IOException if the message could not be correctly received.
*/
private fun readInteger(len: ULong): BigInteger {
val bytes = this.npt.receive(len)
var value = 0.toBigInteger()
for (i in 0..<bytes.size) {
value += bytes[i].toInt().toBigInteger().shl(i*8)
}
return value
}
/**
* Read a string from 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.
*
* @return The string.
*/
private fun readString(): String {
val length = readInteger(2).toInt()
return String(this.npt.receiveFixed(length.toULong()).toByteArray())
}
/**
* Read a message size, type, and tag.
*
* @return A triple in which the first element is the message size in bytes, the second is the message type as a
* [NinePMessageType] constant, and the third element is the message tag.
* @throws java.io.IOException if the message could not be correctly received.
*/
private fun readSizeTypeTag(): Triple<UInt, NinePMessageType, UShort> {
return Triple(
readInteger(4u).toInt().toUInt(),
NinePMessageType.fromByte(readInteger(1u).toByte().toUByte()),
readInteger(2u).toShort().toUShort()
)
}
/**
* Wait for a 9P message with the same tag from the server.
*
* All messages prior to the returned one are discarded.
*
* @param tag The tag to wait for.
* @return A pair of (1) the size and (2) the type of the message that matches the given tag.
*/
private fun waitForTag(tag: UShort): Pair<UInt, NinePMessageType> {
var s = 0u
var ty: NinePMessageType? = null
var ta: UShort = 0u
var found = false
while (!found) {
val stt = readSizeTypeTag()
s = stt.first
ty = stt.second
ta = stt.third
if (ta == tag) {
found = true
}
}
return Pair(s, ty ?: NinePMessageType.RERROR)
}
/**
* Read a 9P message of type Rerror, after the message size and type have already been read.
*
* @return A pair of: (1) the message tag and (2) the error message.
* @throws java.io.IOException if the message could not be correctly received.
*/
private fun readError(): Pair<UShort, String> {
val tag = readInteger(2u).toInt().toUShort()
val error = readString()
return Pair(tag, error)
}
/**
* Send 9P request and check for exceptions or an Rerror response.
*/
private fun responseError(msg: NinePMessage): String? {
try {
msg.write(this.npt)
} catch (ex: Exception) {
return "Exception thrown while sending message: (" + ex.javaClass.toString() + ") " + ex.message
}
val se = waitForTag(msg.tag)
if (se.second == NinePMessageType.RERROR) {
val error = readError()
return error.second
}
return null
}
override fun version(msize: UInt, version: String): String? {
val msg = NinePMessage(NinePMessageType.TVERSION, this.NOTAG, listOf("msize", "version"),
mapOf(
"msize" to BigInteger(msize.toString())
),
mapOf(
"version" to version
),
this.maxSize
)
val error = responseError(msg)
if (error != null) {
return error
}
val rmsize = readInteger(4u).toInt().toUInt()
val rversion = readString()
// this check should not be necessary, but you never know
this.maxSize = ( if (rmsize < msize) msize else rmsize )
if (rversion == "unknown") {
return "Unknown version."
}
return null
}
override fun auth() {
TODO("Not yet implemented")
}
override fun flush() {
TODO("Not yet implemented")
}
override fun attach() {
TODO("Not yet implemented")
}
override fun walk(path: String) {
TODO("Not yet implemented")
}
override fun open(path: String) {
TODO("Not yet implemented")
}
override fun create(path: String) {
TODO("Not yet implemented")
}
override fun read(path: String) {
TODO("Not yet implemented")
}
override fun write(path: String) {
TODO("Not yet implemented")
}
override fun clunk(path: String) {
TODO("Not yet implemented")
}
override fun remove(path: String) {
TODO("Not yet implemented")
}
override fun stat(path: String) {
TODO("Not yet implemented")
}
override fun wstat(path: String) {
TODO("Not yet implemented")
}
}
|