From 540c8b898caf526c06a8bf590ccea5def46f9ad4 Mon Sep 17 00:00:00 2001 From: Miguel Guthridge Date: Sun, 31 Mar 2024 00:08:50 +1100 Subject: [PATCH] Define new v1.0 protocol --- Protocol.md | 211 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 Protocol.md diff --git a/Protocol.md b/Protocol.md new file mode 100644 index 0000000..de91839 --- /dev/null +++ b/Protocol.md @@ -0,0 +1,211 @@ +# Flapi communication protocol + +The Flapi protocol is a simple protocol for communication between the Flapi +server and Flapi clients. + +Version 1.0. + +## Message format + +Messages are all constructed in the following format: + +| Section | Meaning | +|-----------------------------------|---------| +| [Sysex header](#sysex-header) | Begin MIDI system-exclusive message and identify the message type. | +| [Message origin](#message-origin) | Shows the origin of the message. | +| [Client ID](#client-id) | Unique identifier for client. | +| [Message type](#message-type) | Type of message being sent. | +| Additional data (optional) | Data is dependent by message type, but often uses [status info](#status-info) and [Python encoded data](#python-encoded-data). | + +### Sysex header + +The following values are sent at the start of all messages in order to identify +that the message is associated with Flapi. Messages without this header are +ignored. + +| Byte | Meaning | +|--------|---------| +| `0xF0` | System exclusive message start | +| `0x7D` | Non-commercial use byte number (see the [MIDI 1.0 core specification](https://midi.org/specifications/midi1-specifications/midi-1-0-core-specifications)) | +| `0x46` | `'F'` | +| `0x6C` | `'l'` | +| `0x61` | `'a'` | +| `0x70` | `'p'` | +| `0x69` | `'i'` | + +### Message origin + +One of the listed values is used. + +| Value | Meaning | +|--------|---------| +| `0x00` | Message originates from client. | +| `0x01` | Message originates from server. | +| `0x02` | Message is internal to server (client should disregard). | + +### Client ID + +The ID of the client. This should be randomly generated by the client at the +start of a session, and allows multiple clients to connect simultaneously. +Clients should ignore responses targeting a different client ID. + +This should be a random byte, with a value between `0x01` and `0x7F`. +The ID `0x00` is reserved for universal messages, which target all clients. + +Upon receiving a request with a given client ID, the server will give a +response with the same client ID. + +### Message type + +The final common byte describes the type of message. The value can be one of +the following values. Other values are reserved for future Flapi versions. + +| Value | Meaning | +|--------|---------| +| `0x00` | [Client hello](#client-hello) | +| `0x01` | [Client goodbye](#client-goodbye) | +| `0x02` | [Server goodbye](#server-goodbye) | +| `0x03` | [Version query](#version-query) | +| `0x04` | [Exec](#exec) | +| `0x05` | [Eval](#eval) | +| `0x06` | [Stdout](#stdout) | + +The following sections describe each of these message types. + +### Status info + +Many data formats for message types use a status code within the response, +which indicates the results of the operation. This typically has additional +data + +| Code | Meaning | Typical additional data | +|--------|---------|-------------------------| +| `0x00` | Success | Usually [Python encoded data](#python-encoded-data), depending on the message type. | +| `0x01` | An exception was raised during the operation | [Python encoded data](#python-encoded-data) of the exception. The exception is raised within the default client. | +| `0x02` | The server failed to process the request | [Python encoded data](#python-encoded-data) of the error message. Default client raises this message as a `FlapiServerError`. | + +## Python encoded data + +In order to transfer data between the client and the server, the following +system is used. + +* Python objects are encoded using `pickle` +* The resulting string is encoded using base-64 + +In code, this can be represented as: + +```py +import pickle +from base64 import b64encode + +data = { "a": 1, "b": 2, "c": 3 } + +# Encode +encoded = b64encode(pickle.dumps(data)) + +# Decode +decoded = pickle.loads(b64decode(encoded)) + +assert data == decoded +``` + +### Client hello + +This message is sent when a client wants to connect to the Flapi server. For +client hello messages, no additional data is present. + +This message type is the only message type to which the server may not respond. +In particular, the server will not respond if that client ID is already in use +by another active client. If a client does not receive a response, it should +generate a new client ID and try again. If the client repeatedly doesn't +receive a response, it is likely that FL Studio isn't running (or there are +tens of thousands of clients connected to the Flapi server, meaning all +available client IDs are taken). + +If the connection is accepted, the server will give an empty response targeted +to the newly connected client. This means the server has registered that client +ID session. + +### Client goodbye + +This message is sent when a client is exiting, and wants to disconnect from the +Flapi server. As additional data, it contains an `int` exit code as +[Python encoded data](#python-encoded-data). + +The server will respond with an identical reply. The default Flapi client +(provided by this library) raises a `FlapiClientExit` exception when this +happens, which will (by default) cause the program to exit with the given exit +code. + +### Server goodbye + +This message should never be sent by clients. Instead, it is sent by the server +when FL Studio is exiting. The default Flapi client raises a `FlapiServerExit` +exception when this happens, which (by default) is not handled. + +### Version query + +This message is sent by clients to query the version of the Flapi server. + +The server will respond with 3 bytes as the message data: +`(major, minor, revision)`. + +### Exec + +This message is sent by clients, and instructs the server to `exec` code inside +FL Studio. Unlike `eval`, `exec` does not produce a return value, and so should +be used to execute statements, not expressions. + +#### Exec request data + +[Python encoded data](#python-encoded-data) containing a string with code to +execute. + +#### Exec response data + +[Status info](#status-info). For success, no data is given. + +### Eval + +This message is sent by clients, and instructs the server to `eval` code inside +FL Studio. Unlike `exec`, `eval` produces a return value, and so should be used +to evaluate expressions, not statements. + +#### Eval request data + +[Python encoded data](#python-encoded-data) containing a string with code to +evaluate. + +#### Eval response data + +[Status info](#status-info). For success, the return value of `eval` is given. + +### Stdout + +This message is sent from the server to the client to notify it of `stdout` +originating from FL Studio's console. This can be sent at any time, and so +clients should be prepared to handle it at any point when receiving a message. + +It contains [Python encoded data](#python-encoded-data) of the string that was +printed to stdout. + +## Example + +To help clarify, here is an example demonstrating the basic functionality of +the protocol. + +Messages are shown as a list of bytes in hexadecimal, with spaces used to +show different sections of the message. + +| Origin | Message | Meaning | +|--------|---------------------------------------------------------|---------| +| Client | `F07D466C617069` `00` `01` `00` | Client requests to connect using client ID `01` | +| Server | `F07D466C617069` `01` `01` `00` | Server responds, accepting the connection | +| Client | `F07D466C617069` `00` `01` `03` | Client requests server version information | +| Server | `F07D466C617069` `01` `01` `03` `010000` | Server responds, saying it is using version `1.0.0` | +| Client | `F07D466C617069` `00` `01` `04` `67415...3554C673D3D` | Client requests to execute `import transport` | +| Server | `F07D466C617069` `01` `01` `04` `00` | Server responds, indicating success | +| Client | `F07D466C617069` `00` `01` `05` `67415...7706C43343D` | Client requests to evaluate `transport.start()` | +| Server | `F07D466C617069` `01` `01` `05` `00` `6741524C4343343D` | Server responds, indicating a result of `8` | +| Client | `F07D466C617069` `00` `01` `01` `6741524C4143343D` | Client disconnects with a code of `0` | +| Server | `F07D466C617069` `01` `01` `01` `6741524C4143343D` | Server acknowledges disconnect |