diff options
Diffstat (limited to 'src/main/kotlin')
-rw-r--r-- | src/main/kotlin/IPAddress.kt | 39 | ||||
-rw-r--r-- | src/main/kotlin/NetworkPacketTransporter.kt | 71 | ||||
-rw-r--r-- | src/main/kotlin/NetworkPacketTransporterJavaNet.kt | 61 | ||||
-rw-r--r-- | src/main/kotlin/NinePConnection.kt | 32 | ||||
-rw-r--r-- | src/main/kotlin/NinePTranslator.kt | 77 | ||||
-rw-r--r-- | src/main/kotlin/UnresolvableHostException.kt | 1 | ||||
-rw-r--r-- | src/main/kotlin/Utils.kt | 47 | ||||
-rw-r--r-- | src/main/kotlin/cmd/Main.kt | 23 |
8 files changed, 351 insertions, 0 deletions
diff --git a/src/main/kotlin/IPAddress.kt b/src/main/kotlin/IPAddress.kt new file mode 100644 index 0000000..91226f5 --- /dev/null +++ b/src/main/kotlin/IPAddress.kt @@ -0,0 +1,39 @@ +/** + * An IP address (v4 or v6). + */ +class IPAddress { + private var address4: Array<UByte> = Array(4) { 0u } + private var address6: Array<UByte> = Array(16) { 0u } + private var is4: Boolean + + /** + * @throws NumberFormatException if the IP address follows neither the IPv4 syntax, nor the IPv6 syntax. + */ + constructor(address: String) { + val split4 = address.split('.') + val split6 = address.split(':') + var bytes: List<UByte> = emptyList() + + if (split4.size == 4) { + bytes = split4.map { it.toUByte() } + this.address4 = bytes.toTypedArray() + this.is4 = true + } else if (split6.size == 16) { + bytes = split4.map { it.toUByte(16) } + this.address6 = bytes.toTypedArray() + this.is4 = false + } else { + throw NumberFormatException() + } + } + + override fun toString(): String { + val str: String + if (this.is4) { + str = address4.joinToString(separator = ".") { it.toString() } + } else { + str = address6.joinToString(separator = ":") { it.toString() } + } + return str + } +}
\ No newline at end of file diff --git a/src/main/kotlin/NetworkPacketTransporter.kt b/src/main/kotlin/NetworkPacketTransporter.kt new file mode 100644 index 0000000..86f651a --- /dev/null +++ b/src/main/kotlin/NetworkPacketTransporter.kt @@ -0,0 +1,71 @@ +import java.io.Closeable + +/** + * [NetworkPacketTransporter] is an abstract class for network transport-layer operations. A class that implements this + * class' abstract 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 [UnresolvableHostException] + * if the remote address is a domain name, but it cannot be resolved. + */ +abstract class NetworkPacketTransporter : Closeable { + val address: String + val port: UShort + + /** + * Classic constructor. + * + * @throws UnresolvableHostException if the remote address is a domain name, but it cannot be resolved. + */ + constructor(address: String, port: UShort) { + this.address = address + this.port = port + } + + /** + * This constructor allows addresses specified using one of the styles specified in dial(2). See + * [nineAddressToValues]. + * + * @throws UnresolvableHostException if the remote address is a domain name, but it cannot be resolved. + */ + constructor(fullAddress: String) { + val ap = nineAddressToValues(fullAddress) + this.address = ap.first + this.port = ap.second + } + + /** + * Close the connection. + */ + abstract override fun close() + + /** + * Transmit a payload. + * + * @throws java.io.IOException if the message could not be correctly transmitted. + */ + abstract fun transmit(payload: List<Byte>) + + /** + * 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. + * + * @throws java.io.IOException if the message could not be correctly received. + */ + abstract fun receiveUntil(untilByte: Byte): List<Byte> + + /** + * Receive a payload with fixed length. + * + * @param length The length of the message in bytes. + * + * @throws java.io.IOException if the message could not be correctly received. + */ + abstract fun receiveFixed(length: ULong): List<Byte> +}
\ No newline at end of file diff --git a/src/main/kotlin/NetworkPacketTransporterJavaNet.kt b/src/main/kotlin/NetworkPacketTransporterJavaNet.kt new file mode 100644 index 0000000..6badc3d --- /dev/null +++ b/src/main/kotlin/NetworkPacketTransporterJavaNet.kt @@ -0,0 +1,61 @@ +import java.io.InputStream +import java.io.OutputStream +import java.net.Socket + +/* +TODO: + - add TLS support +*/ + +/** + * An implementation of [NetworkPacketTransporter] written using the [java.net] package. + */ +class NetworkPacketTransporterJavaNet : NetworkPacketTransporter { + /** + * The connection's socket. + */ + private val socket: Socket = Socket(address, 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(address: String, port: UShort) : super(address, port) + constructor(fullAddress: String) : super(fullAddress) + + override fun close() { + if (this.socket.isClosed) { + return + } + this.socket.close() + } + + override fun transmit(payload: List<Byte>) { + this.outStream.write(payload.toByteArray()) + } + + override fun receiveUntil(untilByte: Byte): List<Byte> { + var stop = false + val payload: MutableList<Byte> = 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 receiveFixed(length: ULong): List<Byte> { + return this.inStream.readNBytes(length.toInt()).toList() + } +}
\ No newline at end of file diff --git a/src/main/kotlin/NinePConnection.kt b/src/main/kotlin/NinePConnection.kt new file mode 100644 index 0000000..8514ffd --- /dev/null +++ b/src/main/kotlin/NinePConnection.kt @@ -0,0 +1,32 @@ +import java.io.IOException + +/** + * 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. + */ + val npt: NetworkPacketTransporter = netPackTrans + + /** + * Disconnect from the remote host, + * + * @throws IOException if an I/O error occurred while closing the socket. + */ + fun disconnect() { + this.npt.close() + } + + // TODO: implement methods from NinePTranslator +}
\ No newline at end of file diff --git a/src/main/kotlin/NinePTranslator.kt b/src/main/kotlin/NinePTranslator.kt new file mode 100644 index 0000000..c976cd9 --- /dev/null +++ b/src/main/kotlin/NinePTranslator.kt @@ -0,0 +1,77 @@ +/* +TODO: + - add arguments to methods +*/ + +/** + * The [NinePTranslator] interface provides methods that coincide 1:1 with each request and response type in the 9P + * protocol, except for `Rerror` which is handled at occurrence. + * + * Trivia: comments for each method are taken from each message type's manual page in section 5. + */ +interface NinePTranslator { + /** + * Negotiate protocol version. + */ + fun version() + + /** + * Perform authentication. + */ + fun auth() + + /** + * Abort a message. + */ + fun flush() + + /** + * Establish a connection. + */ + fun attach() + + /** + * Descend a directory hierarchy. + */ + fun walk(path: String) + + /** + * Prepare an FID for I/O on an existing file. + */ + fun open(path: String) + + /** + * Prepare an FID for I/O on a new file. + */ + fun create(path: String) + + /** + * Transfer data from file. + */ + fun read(path: String) + + /** + * Transfer data to file. + */ + fun write(path: String) + + /** + * Forget about an FID. + */ + fun clunk(path: String) + + /** + * Remove a file from a server. + */ + fun remove(path: String) + + /** + * Inquire file attributes. + */ + fun stat(path: String) + + /** + * Change file attributes. + */ + fun wstat(path: String) +}
\ No newline at end of file diff --git a/src/main/kotlin/UnresolvableHostException.kt b/src/main/kotlin/UnresolvableHostException.kt new file mode 100644 index 0000000..22b62b0 --- /dev/null +++ b/src/main/kotlin/UnresolvableHostException.kt @@ -0,0 +1 @@ +class UnresolvableHostException : Exception()
\ No newline at end of file diff --git a/src/main/kotlin/Utils.kt b/src/main/kotlin/Utils.kt new file mode 100644 index 0000000..2f5dbd4 --- /dev/null +++ b/src/main/kotlin/Utils.kt @@ -0,0 +1,47 @@ +import java.net.InetAddress + +const val DEFAULT_9P_PORT: UShort = 564u +const val DEFAULT_9P_TLS_PORT: UShort = 17020u + +/** + * The [nineAddressToValues] function translates an address from one of the dial(2) formats into the respective data. + * For the sake of my sanity, the following list highlights differences from the typical dial(2) formats: + * 1. The "network" part is ignored. + * 2. If nothing is specified in the place of "service", the default unencrypted 9P port is used: 564. + * 3. "Service" can only contain port numbers. + * All the rest is the same. + * + * @return A Pair (A, P) containing the host address (A) and the port (P). + * + * @throws IllegalArgumentException If the address is improperly formatted or has invalid values. + * @throws NumberFormatException If the port's value can't be parsed into an [UShort]. + */ +fun nineAddressToValues(nineAddress: String): Pair<String, UShort> { + val parts = nineAddress.split('!', limit = 3) + val address: String + val port: UShort + when (parts.size) { + 1 -> { + address = parts[0] + port = DEFAULT_9P_PORT + } + 2 -> { + address = parts[1] + port = DEFAULT_9P_PORT + } + 3 -> { + address = parts[1] + port = parts[2].toUShort() + } + else -> throw IllegalArgumentException() + } + + return Pair(address, port) +} + +/** + * Handy function that initializes the 9P connection. + */ +fun initNineP(npconn: NinePConnection) { + // TODO: implement +}
\ No newline at end of file diff --git a/src/main/kotlin/cmd/Main.kt b/src/main/kotlin/cmd/Main.kt new file mode 100644 index 0000000..b9fbd9f --- /dev/null +++ b/src/main/kotlin/cmd/Main.kt @@ -0,0 +1,23 @@ +package cmd + +import NetworkPacketTransporterJavaNet +import NinePConnection +import UnresolvableHostException +import kotlin.system.exitProcess + +fun main(args: Array<String>) { + val myUsername = "" + val myPassword = "" + val npconn: NinePConnection + + try { + npconn = NinePConnection(NetworkPacketTransporterJavaNet("net!9p.mydomain.com!564")) + } catch (uhe: UnresolvableHostException) { + println("failed to connect :(") + exitProcess(1) + } + + // TODO: add walk and stat + + npconn.disconnect() +}
\ No newline at end of file |