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
|
Design
======
Many products are subject to design choices in order to give the impression of consistent outer interface and inner
behavior. Likewise, the development of NineKt itself has also been subject to design choices. This document intends to
report the most important design choices that made NineKt what it is today
Premises
--------
These choices are a starting point and give a general shape to NineKt, while also choosing consistent routes for many
design decision junctions. Most of these decisions were made before beginning the development.
1. NineKt must implement the 9P protocol in the most compliant way (which, ideally, is *completely* compliant) with what
is described in [section 5 of 9front's manual pages](https://man.9front.org/5/).
2. Since 9P itself does not specify protocols for authentication or cryptography, NineKt's implementation of 9P must
include the default authentication ("p9any") and cryptographic (TLS) protocols employed by 9front. (See
`/rc/bin/service/tcp17020` in the source tree and the `listen(8)`, `tlssrv(8)`, `authsrv(6)` manual pages.)
3. NineKt can implement some variations in the 9P protocol, as long as they are widely used and sensible.
Design choices
--------------
1. Kotlin's main programming paradigm is object-oriented. Therefore, NineKt must implement 9P using an object-oriented
design.
- The request types (T-messages) should be written in code as methods of an interface, which is then implemented by a
class that manages the connection. Calls to these methods should reflect, and happen as a consequence of, the file
system operations performed on the local synthetic file tree, once the 9P initialization process (version and
message size negotiation, authentication, etc.) is complete.
- The message types (for both T-messages and R-messages) should be defined as an enumeration class which also holds
their respective values in 9P messages.
- Messages ready to be sent should be encapsulated into the instantiation of a special class, because most messages
*can* be abstracted into a general data structure. Such class holds the independent pieces of each message: the
message type, a collection of the message fields (actually, their names) in the same order they are expected to be
sent, the value for each field in a map, and the maximum message size, as negotiated by the initialization
procedure.
- The transport layer methods, which send and receive raw bytes of data, as well as other methods taking care of the
security of the communication and the authentication of both parts, should follow the same realization in code as
the T-message methods: an interface provides signatures, while classes provide a specific implementation for each
of them. The case of security is special, as it does not need any additional method other than those defined by the
transport layer's interface. In fact, the idea of reading from the connection and writing to it is already enough
and initialization, whether it is of the transport layer protocol or the security layer on top of it, is performed
in the implementor class' constructor, which additional arguments for specifying possible variations in the
security implementations' behavior.
2. For the 9P initialization procedure, which hardly mutates between usages, at least in the way it is performed rather
than in the data being exchanged, there should be a function that acts as a *macro* for the whole procedure, which
calls the appropriate methods synchronously.
Asynchronicity
--------------
|