summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main/kotlin/IPAddress.kt39
-rw-r--r--src/main/kotlin/NetworkPacketTransporter.kt71
-rw-r--r--src/main/kotlin/NetworkPacketTransporterJavaNet.kt61
-rw-r--r--src/main/kotlin/NinePConnection.kt32
-rw-r--r--src/main/kotlin/NinePTranslator.kt77
-rw-r--r--src/main/kotlin/UnresolvableHostException.kt1
-rw-r--r--src/main/kotlin/Utils.kt47
-rw-r--r--src/main/kotlin/cmd/Main.kt23
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