The main idea behind the wrapper is to allow for the server processes to run independently of the daemon process. On linux, this can be done using tools like screen and tmux, but for a cross platform implementation, we are writing our own process wrapper. Due to the size of the JVM memory footprint, and the fact that there will be a process wrapper instance running for each server instance, the wrapper will be written in C, and the correct binary launched depending on platform.
Using this approach, Should the daemon crash or need to be restarted, the wrapper and server instances will remain running, and the daemon will be able to re-establish a connection to their IO streams on startup.
The wrapper communicated with the daemon via UDP using the loopback address. There are no plans to allow the wrapper to bind to any other address. There is an will be no security layer for daemon/wrapper communications. Control of remote systems will be accomplished via a connection to the daemon instance running on the remote system, which in turn has control over the wrapper instances on that system.
Since UDP uses a fixed buffer size, our packets will also have a fixed maximum size. The idea is to use a packet size that is as small as possible, while still allowing for average sized messages to fit in a single packet.
I'm currently going with a packet size of 255 bytes. We can adjust this later if needed.
The packet structure is fairly simple. The message itself can span as many packets as are needed, up to 100 packets. The 100 packet limit due to the packet ID being 1 byte in length, and how we handle dropped packets and packet ordering.
The packet header is inserted at the beginning of each packet before the message payload. It contains information used to identify and sort the packets. Even though we are talking only on loopback, UDP still offers no guarantee for packet delivery or proper ordering. The header consists of:
The message itself contains a "Word" byte (this identifies what type of message is being sent), followed by a set of key value pairs. A message can span multiple packets. Delimiter bytes are used to separate the message components so that deserialization can be performed keying on this bytes without needing to know the length of the data beforehand. The message portion of the packet is structured as follows:
After this, we either start or resume the sets of key value pairs:
Then at the end of the packet:
The message continues byte can appear anywhere in the message structure after the message word byte. The idea for deserialization is: if you see a message continues byte, move to the next packet and continue parsing from after the message resumes byte until you find a message end byte.
RESERVED BYTES: DATA VALUES:
< = Message Start W = Message Word (Type) Byte
0x01 - Start of heading I = Packet Order Byte (increments each packet)
> = Message End K = Property Key Text (variable length)
0x04 - End of transmission V = Property Value Text (variable length)
| = Delimiter P = PID (32-bit)
0x1F - Unit separator S = Message Start Byte (Order byte of first packet in message)
@ = Message Continues L = Message Length (In packets)
0x0E - Shift out
$ = Message Resumes ?... = Variable length data of preceding type
0x0F - Shift in
SIMPLE MESSAGE:
PPPPISL<WK...|V...|>
SPLIT MESSAGE:
Packet 1:
PPPPISL<WK...|V...@
Packet 2:
PPPPISL$V...|>
MULTIPLE PROPERTIES:
PPPPISL<WK...|V...|K...|V...|>
MULTIPLE PROPERTIES, SPLIT MESSAGE:
Packet 1:
PPPPISL<WK...|V...|K...@
Packet 2:
PPPPISL$K...|V...|K...|V...@
Packet 3:
PPPPISL$|K...|V...|>