Skip to content

Latest commit

 

History

History
255 lines (183 loc) · 10.1 KB

Protocol.md

File metadata and controls

255 lines (183 loc) · 10.1 KB

Flapi communication protocol

The Flapi protocol is a simple protocol for communication between the Flapi server and Flapi clients.

Version 2.0

Message format

Messages are all constructed in the following format:

Section Request/Response Meaning
Sysex header Both Begin MIDI system-exclusive message and identify the message type.
Message origin Both Shows the origin of the message.
Client ID Both Unique identifier for client.
Continuation byte Both Whether this is message contains a continuation (ie is split across multiple messages).
Message type Both Type of message being sent.
Status code Both Status info about response.
Additional data (optional) Depends Data is dependent by message type, but often uses a base-64 encoded string.
Sysex end byte (0xF7) Both End of MIDI system-exclusive message.

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)
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.

Continuation byte

In Windows, system-exclusive MIDI messages can't have an arbitrary length. Applications must instead pre-allocate a buffer, which the Windows API writes into. If the buffer is too small, the message may be discarded. Most applications only support a buffer size of 1024 bytes.

To account for this, Flapi messages may be split across multiple MIDI messages.

If a message is too long, it is split at 1000 bytes, and the continuation byte is set to 1. The remaining message is sent as a separate message, which only contains the sysex header, message origin, client ID and continuation byte. These split messages must be continuous for a single client, and cannot be interleaved with messages of other types.

The final MIDI message of a Flapi message should have its continuation byte set to 0 to indicate the end of the message.

Message type

The message type 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
0x01 Client goodbye
0x02 Server goodbye
0x03 Version query
0x04 Register message type
0x05 Exec
0x06 Stdout

Later sections describe each of these message types.

Status code

The final byte is a status code, which is used to indicate the status of a response. In requests, this should be set to zero.

Code Meaning Typical additional data
0x00 Success Determined by message type.
0x01 An exception was raised during the operation. Base-64 encoded string of the error message.
0x02 The server failed to process the request Base-64 encoded string of the error message. Default client raises this message as a FlapiServerError.

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 all device 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, represented as as base-64 string to prevent illegal values for high exit codes.

from base64 import b64encode, b64decode

code = 130

# Encode
encoded = b64encode(str(code).encode())

# Decode
decoded = int(b64decode(data).decode())

assert code == decoded

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).

Register message type

This message is sent by clients to register a new type of message. This message type is handled by the server by setting up a new handler for a message.

This can be used to inject code into the Flapi server, to allow for clients to gain deeper control over FL Studio, and to save on repeated operations.

The default client uses this functionality to register a pickle-eval message to evaluate data and encode the results using Pickle.

Note that the server should not share message handlers between clients.

Interface for message handlers

Message handler functions should match the given interface:

def message_handler(
    client_id: int,
    status_code: int,
    msg_data: Optional[bytes],
    scope: dict[str, Any],
) -> int | tuple[int, bytes]:
    ...

Where:

  • client_id is the ID of the client that sent the request
  • status_code is the incoming status code.
  • msg_data is the full message data, if provided. Otherwise, the message data is None. Note that if the data was split across multiple messages, it will be joined before calling this function.
  • scope is a dictionary containing a local scope that should be used when executing arbitrary code.
  • Return type is int for status code, and optionally bytes for response data. Note that if the response needs to be split across multiple messages, this will be handled internally by the server.

Register message type request data

A base-64 string containing the definition of the message handler function.

Register message type response data

A single byte, the message type number that should be used to communicate with this message hander.

Exec

This message is sent by clients, and instructs the server to exec code inside FL Studio.

Exec request data

A base-64 string containing the code to execute.

Exec response data

No data is given on success. In order to get return data, clients should register an eval message type handler.

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.

Additional data is a base-64 string of the content written to stdout.

When sent from the client, it is printed to FL Studio's console, and no response is given.

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 00 Client requests to connect using client ID 01
Server F07D466C617069 01 01 00 00 Server responds, accepting the connection
Client F07D466C617069 00 01 00 03 Client requests server version information
Server F07D466C617069 01 01 00 03 010000 Server responds, saying it is using version 1.0.0
Client F07D466C617069 00 01 00 04 61573...97964413D3D Client requests to execute import transport
Server F07D466C617069 01 01 00 04 00 Server responds, indicating success
Client F07D466C617069 00 01 00 04 64484...A304B436B3D Client requests to execute transport.start()
Server F07D466C617069 01 01 00 04 00 Server responds, indicating success
Client F07D466C617069 00 01 00 01 6741524C4143343D Client disconnects with a code of 0
Server F07D466C617069 01 01 00 01 6741524C4143343D Server acknowledges disconnect