-
Notifications
You must be signed in to change notification settings - Fork 1
Home
RISP is a streamable binary encoding sub-protocol that can be used to build up more advanced protocols.
RISP, short for Reduced Instruction Set Protocol, is very loosly-based on the concept of the RISC CPU architecture.
It is a command based protocol that handles very simple primitives:
- Parameter-less commands
- Commands with a single Integer parameter (of varying sizes)
- Commands with a binary stream (of varying lengths), also known as a String.
The Binary string parameter can encode another RISP stream, or anything really.
Since RISP is a Command-based protocol, it is expected that each command should do something simple and discreet.
RISP is encoded using a 16-bit (2 byte) command ID. The command ID uses the high-4 bits to indicate what kind of parameters follow the command. This effectively means that there are ranges of Command ID's that are used for particular parameter types.
The effective ranges are:
Range | Type | Integer Bytes following | Mask | Common Name |
---|---|---|---|---|
0x0000 to 0x0fff | Integer | 1 | 0.000.xxxx.xxxx.xxxx |
Char, Byte-Integer (8-bit) |
0x1000 to 0x1fff | Integer | 2 | 0.001.xxxx.xxxx.xxxx |
Short Integer (16-bit) |
0x2000 to 0x2fff | Integer | 4 | 0.010.xxxx.xxxx.xxxx |
Integer (32-bit) |
0x3000 to 0x3fff | Integer | 8 | 0.011.xxxx.xxxx.xxxx |
Long Long (64-bit) |
0x4000 to 0x4fff | Integer | 16 | 0.100.xxxx.xxxx.xxxx |
(128-bit) |
0x5000 to 0x5fff | Integer | 32 | 0.101.xxxx.xxxx.xxxx |
(256-bit) |
0x6000 to 0x6fff | Integer | 64 | 0.110.xxxx.xxxx.xxxx |
(512-bit) |
0x7000 to 0x7fff | None | 0 | 0.111.xxxx.xxxx.xxxx |
|
0x8000 to 0x8fff | String | 1 | 1.000.xxxx.xxxx.xxxx |
String, Max length of 255 bytes |
0x9000 to 0x9fff | String | 2 | 1.001.xxxx.xxxx.xxxx |
String, Max length of 64k |
0xa000 to 0xafff | String | 4 | 1.010.xxxx.xxxx.xxxx |
String, Max Length of 4gb |
0xb000 to 0xbfff | String | 8 | 1.011.xxxx.xxxx.xxxx |
String, Max Length of 16777216tb |
0xc000 to 0xffff | None | 0 | 1.1xx.xxxx.xxxx.xxxx |
Strings work by having an initial parameter which is an integer (of different sizes depending on the ID range). That integer value then indicates how many bytes follow which is the string value. It essentially encodes the length of the string, and then the string itself.
The Length part of the String is always treated as an unsigned Integer.
Integer parameters are treated as signed Integers, however, RISP itself does not care much, you can implement it however you want.
The only exception to this is the Integer part of the Strings. These are always unsigned (negative string lengths don't make sense).
One of the main tenets of RISP is that you make up a complicated protocol by use of small discreet operations. A command should trigger a well defined small task. Many commands would be simply setting some value in the session, and then other commands will trigger actions that will use those values.
A simple example could be:
USERNAME <string>
PASSWORD <string>
LOGIN
In that example, the first command USERNAME simply sets the Username parameter in the session data to that value. (Note that the implementation will need to be sure to keep incoming data separate from running data, just setting a Username variable, shouldn't change the Username that the session is running under. So these operations certainly can have some sanity logic in there.)
The next command can set the PASSWORD parameter.
Then the LOGIN command runs, and it checks the Username and Password params and performs the action. It would likely respond with some command that indicates success or failure. But each individual command does a very discreet task.
In many cases there are advantages in layering the protocol. There is a slight potential performance penalty, but it allows for more detailed protocols.
By Layering, we mean that you might have commands that perform some action, and the parameter for that command is actually a binary stream of a bunch of other RISP commands that set the Data required for that operation.
These protocols are essentially laid out as:
LOGIN <string>
USERNAME <string>
PASSWORD <string>
This also means that each main parameter can have its own protcol insides.
In this example, the RISP stream initially just contains one command, the LOGIN command. That LOGIN command however, has a string parameter. The contents of that string parameter contains more RISP commands, that can be processed again. In this case, that exposes the USERNAME and PASSWORD commands. When all processing of the string parameter is complete, then the LOGIN logic should then attempt to utilise those parameters that were set.
Depending on how complex your operations and protocol is, or your defined security model, each outer command may have completely separate RISP processing of the inner commands. This does generally mean more time developing the code handling the protocol, but ensures that inner commands are not mixed inappropriately.
JSON and other text bases protocols are very flexible, but it is difficult to stream it without something encapsulating it. The reason it is hard to stream is because there is no way of knowing the boundaries of any element within it, including the outer element. This means the entire stream needs to be parsed to even know if it is complete. RISP has an advantage in that it knows exactly how many bytes it needs in the buffer to process a command. If you think of RISP as the commands and JSON as an object, it becomes easy to see the advantages of mixing the two.
An example of this is the communications protocol for OpenCluster. It uses RISP to encapsulate the encryption and authentication, and the commands that control the OpenCluster environment. However, most of the commands have a set of parameters, and it uses JSON to include those parameters. This allows for an extremely flexible, simple and streamable protocol that takes advantage of both RISP and JSON. A purely RISP based protocol would be significantly more complex.