summaryrefslogtreecommitdiff
path: root/src/main/kotlin/net
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/kotlin/net')
-rw-r--r--src/main/kotlin/net/InMessage.kt167
-rw-r--r--src/main/kotlin/net/OutMessage.kt127
-rw-r--r--src/main/kotlin/net/TransportLayer.kt59
-rw-r--r--src/main/kotlin/net/TransportLayerJavaNet.kt95
4 files changed, 0 insertions, 448 deletions
diff --git a/src/main/kotlin/net/InMessage.kt b/src/main/kotlin/net/InMessage.kt
deleted file mode 100644
index 771c670..0000000
--- a/src/main/kotlin/net/InMessage.kt
+++ /dev/null
@@ -1,167 +0,0 @@
-package net
-
-import NinePMessageType
-import except.InvalidMessageException
-import java.math.BigInteger
-
-/**
- * An incoming 9P message. Upon instancing this class only one message is read, and it's represented in a way similar to
- * that of [OutMessage]. This class is supposed to be complementary, and opposite, to [OutMessage].
- *
- * @param tl The transport layer API.
- * @param maxSize The maximum message size negotiated with the remote part.
- * @param reqTag The required tag.
- * @throws InvalidMessageException if the message that is currently being read is invalid.
- */
-class InMessage(val tl: TransportLayer, maxSize: UInt, val reqTag: UShort) {
- /**
- * The total size of the message.
- */
- val size: UInt
-
- /**
- * The message type.
- */
- val type: NinePMessageType
-
- /**
- * The message tag.
- */
- val tag: UShort
-
- /**
- * A map of each integer field's name to its value.
- */
- var fieldsInt: MutableMap<String, BigInteger> = mutableMapOf()
- private set
-
- /**
- * A map of each string field's name to its value.
- */
- var fieldsStr: MutableMap<String, String> = mutableMapOf()
- private set
-
- /**
- * A map of each raw field's name to its value.
- */
- var fieldsRaw: MutableMap<String, Array<UByte>> = mutableMapOf()
- private set
-
- /**
- * An ordered collection of raw bytes that still need to be interpreted as values.
- */
- private var rawData: List<UByte>
-
- init {
- size = convInteger(this.tl.receiver(), 0, 4).toInt().toUInt()
- if (this.size > maxSize) {
- throw InvalidMessageException("Size greater than maximum size (${this.size} > ${maxSize}).")
- }
- try {
- this.type = NinePMessageType.fromByte(convInteger(this.tl.receiver(), 0, 1).toInt().toUByte())
- } catch (_: NoSuchElementException) {
- throw InvalidMessageException("Invalid 9P message type.")
- }
- tag = convInteger(this.tl.receiver(), 0, 2).toInt().toUShort()
- if (tag != reqTag) {
- // TODO: what do we do now?
- }
- this.rawData = this.tl.receive((size - (4u + 1u + 2u)).toULong()).toList()
- }
-
- /**
- * Field of an incoming 9P message. An ordered collection of fields makes a schema.
- *
- * @param name The field's name. It's typically the same you can find in the manual pages.
- * @param type The field's type.
- * @param size The field's size in bytes. If the type is [Type.STRING], this parameter is ignored.
- */
- data class Field(val name: String, val type: Type, val size: UInt) {
-
- enum class Type {
- INTEGER,
- STRING,
- RAW
- }
- }
-
- /**
- * Apply the given field to the raw data and put it in one of [fieldsInt], [fieldsStr], or [fieldsRaw]. Fields must
- * be applied strictly in order, as their application is not commutative.
- *
- * Each time a field is applied, the initial part of raw data that coincides with that field is removed.
- *
- * @param field The given field.
- */
- fun applyField(field: Field) {
- val size: Int
- when (field.type) {
- Field.Type.STRING -> {
- val str = convString(this.rawData.toList(), 0)
- size = 2 + str.length
- this.fieldsStr[field.name] = str
- }
- Field.Type.INTEGER -> {
- size = field.size.toInt()
- this.fieldsInt[field.name] = convInteger(this.rawData.toList(), 0, size)
- }
- Field.Type.RAW -> {
- size = field.size.toInt()
- this.fieldsRaw[field.name] = this.rawData.take(size).toTypedArray()
- }
- }
- this.rawData = this.rawData.drop(size)
- }
-
- /**
- * Apply the given message schema to the raw data and fill [fieldsInt], [fieldsStr], and [fieldsRaw].
- *
- * Note: This method could have been avoided by making a giant `when` block in the class constructor. However, I'd
- * rather let the caller, which is usually a method that makes a request and reads its response, decide the schema.
- * In this way, each method that needs to read a response of a specific type (and there is usually one method per
- * response type) declares its own schema, while those which cannot be easily represented by a schema (e.g. `Rwalk`)
- * are simply going to be read in a field-by-field fashion.
- *
- * @param schema The desired ordered collection of fields.
- */
- fun applySchema(schema: Iterable<Field>) {
- for (field in schema) {
- applyField(field)
- }
- }
-
- companion object {
- /**
- * Convert an [len] bytes long unsigned integer number from raw bytes.
- *
- * 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 IllegalArgumentException if either [offset] or [len] are negative.
- */
- fun convInteger(bytes: Iterable<UByte>, offset: Int, len: Int): BigInteger {
- val bytes = bytes.drop(offset).take(len)
- var value = 0.toBigInteger()
- for (i in 0..<bytes.size) {
- value += bytes[i].toInt().toBigInteger().shl(i*8)
- }
- return value
- }
-
- /**
- * Convert a string from raw bytes.
- *
- * 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.
- * @throws IllegalArgumentException if either [offset] is negative.
- */
- fun convString(bytes: Iterable<UByte>, offset: Int): String {
- val length = convInteger(bytes, 0, 2).toInt()
- val bytes = bytes.drop(offset).take(length)
- return String(ByteArray(bytes.size) { i -> bytes[i].toByte() })
- }
- }
-} \ No newline at end of file
diff --git a/src/main/kotlin/net/OutMessage.kt b/src/main/kotlin/net/OutMessage.kt
deleted file mode 100644
index c9ae879..0000000
--- a/src/main/kotlin/net/OutMessage.kt
+++ /dev/null
@@ -1,127 +0,0 @@
-package net
-
-import NinePMessageType
-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 integer field's name into its value and size in bytes.
- * @param fieldValuesStr A map of each string field's name into its value.
- * @param fieldValuesRaw A map of each raw field's name into its value.
- * @param maxSize The maximum message size.
- */
-class OutMessage(val type: NinePMessageType, val tag: UShort, val fieldNames: List<String>, val fieldValuesInt: Map<String, Pair<BigInteger, UInt>>, val fieldValuesStr: Map<String, String>, val fieldValuesRaw: Map<String, List<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.")
- }
- writeMessageSizeTypeTag(tl, totalSize, type, tag)
- for (field in fieldNames) {
- tl.transmit(
- if (field in insecInts) {
- val valsize = fieldValuesInt[field]!!
- convIntegerToBytes(valsize.first, valsize.second)
- } else if (field in insecStrs) {
- convStringToBytes(fieldValuesStr[field]!!)
- } else {
- fieldValuesRaw[field]!!.toList()
- }
- )
- }
- }
-
- /**
- * 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.
- * @param tag The 9P message tag.
- */
- private fun writeMessageSizeTypeTag(tl: TransportLayer, size: UInt, type: NinePMessageType, tag: UShort) {
- var bytes: List<UByte> = emptyList()
- bytes += convIntegerToBytes(BigInteger(size.toString()), 4u)
- bytes += convIntegerToBytes(BigInteger(type.value.toString()), 1u)
- bytes += convIntegerToBytes(BigInteger(tag.toString()), 2u)
- tl.transmit(bytes)
- }
-
- /**
- * Calculate the expected size of the message.
- */
- fun size(): UInt {
- return 4u + 1u + 2u + this.insecInts.sumOf { this.fieldValuesInt[it]!!.second } + 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
-
- /**
- * Convert an integer number to its byte representation.
- *
- * In 9P, binary numbers (non-textual) are specified in little-endian order (least significant byte first).
- *
- * @param value The number's value.
- * @param size The number's size in bytes.
- */
- fun convIntegerToBytes(value: BigInteger, size: UInt): List<UByte> {
- var bytes: List<UByte> = value.toByteArray().toList().map { x -> x.toUByte() }
- bytes += List(size.toInt() - bytes.size, {0u}) // add padding for missing bytes
- return bytes
- }
-
- /**
- * 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.
- */
- fun convStringToBytes(value: String): List<UByte> {
- require(value.length <= 2.0.pow(16.0) - 1)
- var bytes = convIntegerToBytes(value.length.toBigInteger(), 2u)
- bytes += value.toByteArray().toList().map { x -> x.toUByte() }
- return bytes
- }
- }
-} \ No newline at end of file
diff --git a/src/main/kotlin/net/TransportLayer.kt b/src/main/kotlin/net/TransportLayer.kt
deleted file mode 100644
index 90dcd11..0000000
--- a/src/main/kotlin/net/TransportLayer.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-package net
-
-import java.io.Closeable
-
-/**
- * [TransportLayer] is an interface for network transport-layer operations. A class that implements these methods, once
- * instantiated, establishes and manages a connection with a remote endpoint defined by an address and a port and allows
- * to send and receive network messages (also called "payloads").
- *
- * The address of the remote endpoint can be an IP address (v4 or v6) or a domain name, in which case it is resolved to
- * an IP address right before initializing the connection. Every constructor should throw an
- * [except.UnresolvableHostException] if the remote address is formatted as an actual domain name, but it cannot be
- * resolved (e.g. it doesn't exist, or it contains forbidden characters).
- *
- * Depending on the specific given implementation, the constructor of this class might throw other exceptions (e.g. the
- * [java.net.Socket] constructor in [TransportLayerJavaNet]).
- */
-interface TransportLayer : Closeable {
- /**
- * Close the connection.
- */
- abstract override fun close()
-
- /**
- * Transmit a payload.
- *
- * @throws java.io.IOException if the message could not be correctly transmitted.
- */
- fun transmit(payload: Iterable<UByte>)
-/*
- /**
- * Receive a payload until a byte occurs, which marks the end of the message. The byte is discarded after being read
- * and is not returned.
- *
- * If you know both which byte marks the end of the message and the message length, it is advised to use
- * [receiveFixed] instead, which is usually more efficient.
- *
- * @param untilByte The byte that marks the end of the message.
- * @return the received payload.
- * @throws java.io.IOException if the message could not be correctly received.
- */
- abstract fun receiveUntil(untilByte: UByte): Array<UByte>
-*/
- /**
- * Receive a payload with fixed length. If zero, nothing is read.
- *
- * @param length The length of the message in bytes.
- * @return the received payload.
- * @throws java.io.IOException if the message could not be correctly received.
- */
- fun receive(length: ULong): Array<UByte>
-
- /**
- * Gives the caller a "receiver" (i.e. an instance of Iterable) from which raw data of any length can be read.
- *
- * @return The receiver.
- */
- fun receiver(): Iterable<UByte>
-} \ No newline at end of file
diff --git a/src/main/kotlin/net/TransportLayerJavaNet.kt b/src/main/kotlin/net/TransportLayerJavaNet.kt
deleted file mode 100644
index 3d2867a..0000000
--- a/src/main/kotlin/net/TransportLayerJavaNet.kt
+++ /dev/null
@@ -1,95 +0,0 @@
-package net
-
-import nineAddressToValues
-import java.io.InputStream
-import java.io.OutputStream
-import java.net.Socket
-import kotlin.math.min
-
-/*
-TODO:
- - add TLS support
-*/
-
-/**
- * An implementation of [TransportLayer] written using the [java.net] package.
- */
-class TransportLayerJavaNet(val address: String, val port: UShort) : TransportLayer {
- /**
- * The connection's socket.
- */
- private val socket: Socket = Socket(this.address, this.port.toInt())
-
- /**
- * The connection's input stream.
- */
- private val inStream: InputStream = this.socket.inputStream
-
- /**
- * The connection's output stream.
- */
- private val outStream: OutputStream = this.socket.outputStream
-
- constructor(fullAddress: String) : this(nineAddressToValues(fullAddress).first, nineAddressToValues(fullAddress).second)
-
- private class InStreamIterator(val inStream: InputStream) : Iterator<UByte> {
- override fun next(): UByte {
- return this.inStream.readNBytes(1).first().toUByte()
- }
-
- override fun hasNext(): Boolean {
- return this.inStream.available() > 0
- }
- }
-
- override fun close() {
- if (this.socket.isClosed) {
- return
- }
- this.socket.close()
- }
-
- override fun transmit(payload: Iterable<UByte>) {
- val payload = payload.toList()
- val bytes = ByteArray(payload.size, { i -> payload[i].toByte() })
- this.outStream.write(bytes)
- }
-
-/*
- override fun receiveUntil(untilByte: UByte): Array<UByte> {
- var stop = false
- val payload: Array<UByte> = MutableList(0, { 0 })
- while (!stop) {
- val b = this.inStream.readNBytes(1)[0]
- if (b == untilByte) {
- stop = true
- continue
- } else {
- payload.add(b)
- }
- }
- return payload
- }
-*/
-
- override fun receive(length: ULong): Array<UByte> {
- var length = length
- val intMax = Int.MAX_VALUE.toULong()
- val bytes: MutableList<Byte> = MutableList(0) { 0 }
- // readNBytes only takes Int values, so it must be called multiple times if the length is greater than Int's
- // maximum value
- while (length > 0u) {
- // the min function ensures that the value passed to readNBytes never exceeds Int's maximum value while also
- // switching to the length variable when its value eventually becomes less than Int's maximum value, which
- // avoids writing duplicated readNBytes calls in the code
- val lenMin = min(length, intMax)
- bytes += this.inStream.readNBytes(lenMin.toInt()).toMutableList()
- length -= intMax
- }
- return Array(bytes.size) { i -> bytes[i].toUByte() }
- }
-
- override fun receiver(): Iterable<UByte> {
- return Iterable { InStreamIterator(this.inStream) }
- }
-} \ No newline at end of file