The protocols for communicating with a SolarTech unit were originally designed for extremely limited systems where both ends were developed in sync, so to save bytes they were designed without an encapsulation scheme. That is, there were no generic headers with sizes that enabled a generic parser to parse the packets without knowing the individual packet definitions. (See the Connectionless Protocol background history for the full story on our limited hardware environments.) While this did allow for the absolute minimum packet size necessary for the job, together with the ability to emit packets larger than RAM buffers on the system would necessarily allow, it has produced a number of problems for the modern environment in which a server is communicating with many units at once. (And the limitations which defined the initial protocols no longer apply.)
The tradeoff that headerless packets make is that in exchange for minimum resource consumption the code to parse them is far more complex. The large amount of state required to process such packets means that the easiest and most reliable way to parse them is using threads. In the original environment where the desktop application Control Center 3000 would be communicating directly with one to at most a dozen or two units at a time, this was not a problem. However, when we moved to Command Center which connected to a centralized server, this resulted in a large number of threads. Because the code to parse packets is so complex, the same code was also re-used for server⇄server and server⇄client communication. The Carrier Protocol was designed to allow the many SecureProtocol connections to go over one TCP connection, which greatly reduced operating system for file descriptors, but it still resulted in one thread per Secure Protocol (i.e. channel in the carrier protocol). This can result in an explosion of threads whenever the Command Center client connects to an organization with 500 units.
All of the above results in a need for a protocol which can be parsed by a single thread and dispatched to multiple units. At the same time, experience with running traditional unit connections over the Carrier Protocol suggests that a stateless protocol would be far preferable to one in which channels are registered. This should result in faster connection setup times which is avantageous both for servers connecting to each other and for clients connecting to the server. These considerations resulted in the Stateless Unit Multiplexing Protocol.
The protocol is intended to take place over a channel in the Carrier Protocol, so authentication and encryption will be handled by the encapsulating carrier protocol connection. The server side of the server⇄client communication will need to guard against rogue unit IDs and verify access authority to every incoming packet.
The Unit Multiplexing Protocol is very simple. It requires that the underlying transport mechanism be sequences and reliable, such as TCP/IP or (the intended use) a channel in the Carrier Protocol. Each packet has a five byte header which consists of a packet ID byte and a length:
Byte Count | Data Type | Value | Description |
1 | unsigned byte | Packet Type | |
4 | unsigned integer | packet length (of the rest of the packet, i.e. minus this header) |
Further encoding is specified on a per-packet basis.
NOTE: all multi-byte values are big endian unless otherwise specified.
So far there is only one packet defined, the unit packet:
Byte Count | Data Type | Value | Description |
1 | unsigned byte | 0 | Packet Type |
4 | unsigned integer | packet length | |
2-65,537 | JString | The ID of the unit (as output by java.io.DataOutput.writeUTF()) | |
1 | unsigned byte | Secure Protocol channel ID | |
variable | data | The embedded packet. | |
Notes: After figuring out which unit to dispatch the embedded packet to by parsing the unit ID, the parsing code should be able to parse this with the same code used to parse a packet in the secure protocol. (In the java codebase, by com.solartechnology.protocols.secure.SecureProtocol.readPacket().) The ID is the unique ID of the unit, i.e. the ID stored in the database. I.e. it's what's returned by com.solartechnology.solarnet.Asset.getMongoId(). The first two fields in this header are simply the universal header fields included for convenient. As such, the packet length is the length of the entire packet minus the header length. |
When delivering packets from this protocol, care should be taken to have the packets processed in order for a given unit. Thus the right strategy is almost certainly to have a packet queue for each unit in the system, for the parsing code to stick the incoming packet onto that packet queue, and then to submit a job to a work queue which will execute all of the packets in that unit's packet queue (this way multiple packets delievered in rapid succession will be guaranteed to be processed in order).
The "control channel" for a client⇄server Command Center connection should be for the unit ID "" (i.e. the empty string) since there is no concept of null within this protocol.