EMC is a library that encapsulates a lightweight protocol for communication between devices, as well as the basic software building blocks to support and extend it. Core design principles are as follows:
- human readable I/O, but not exclusively (support high-bandwidth data transfers as well, either directly or by sidecar connections);
- operate on top of a lower level P2P or broadcast protocol (network, UART or even SPI) but do not monopolize it - i.e. allow other traffic on the interface as well;
- a single EMC pipeline is allowed on the same interface;
- low overhead;
- accept both buffered and unbuffered input;
- allow support for encryption, trancoding, compression, filtering, etc. on the interface.
EMC processes messages in a pipeline, comprising of one or more independent stages. A reactor is responsible for instantiating and managing a pipeline. A reactor allows an arbitrary number of stages to be attached to it and linked into a pipeline. Messages in a pipeline flow from one stage to the next. The default stage object will simply relay any inbound messages to the next stage. By overriding this behaviour, stages can be defined to perform any custom operation on the inbound or outbound messages or extend the base protocol with new services.
Depending on the function of the message pipeline - i.e. whether it is meant to serve particular functionality to a controlling device or act as a bridge towards an EMC-enabled server, a pipeline can have one of the following major roles:
- host role: serve a set of functions to a connecting instance;
- user role: drive a connection to a remote EMC-enabled device.
A hybrid, proxy role can be defined by externally connecting two pipelines (one with a host and one with an user role) back to back.
On the forward path, messages flow sequentially from the first to the last stage of the pipeline. Each stage is responsible for passing the message along to the next stage.
Stage1 Stage2 Stage3 ... StageN
| | | |
|----->| | |
| |----->| |
| | |-----> |
... ... ... ... ...
| | | ----->|
On the return path, messages flow sequentially from the originating stage of the pipeline to the previous. Each stage is responsible for passing the return message along to the previous stage.
Stage1 Stage2 Stage3 ... StageN
| | | |
| | | <-----|
... ... ... ... ...
| | |<----- |
| |<-----| |
|<-----| | |
The reactor component instantiates a raw
pipline. As the name implies, raw pipeline
operates with raw messages - i.e. by default it does not expect inbound messages to comply with
any particular format - EMC or otherwise. This pipeline is useful for defining custom protocol
streams (i.e. filter stages, encoding stages, data encryption stages).
The emc
pipeline, which implements the EMC protocol is branched out from the raw
pipeline,
at the end of the transcoding chain (if any).
An emc
pipeline can be enabled by attaching a gateway
stage to the raw
pipeline.
The raw
pipeline operates with the following events:
join
: triggered by some reactors or interface stages when a socket or device connection becomes available to a client; Pipelines in the_host_
orproxy
roles are considered to be implicitely connected, so this event will not be fired for them;recv
: inbound message received onto the aux channel (typicallystderr
);feed
: inbound message received;send
: outbound message to be sent out on the return flow;drop
: connection to the server closed or lost.
The events are accessible via the rawstage
interface:
bool emc_raw_resume(reactor*): non-negociable resume callback
void emc_raw_join()
void emc_raw_recv(std::uint8_t*, int)
int emc_raw_feed(std::uint8_t*, int)
int emc_raw_send(std::uint8_t*, int)
void emc_raw_drop()
void emc_raw_suspend(reactor*)
void emc_raw_event(int, void*)
void emc_raw_sync(float)
The emc
pipeline operates with the following events:
join
: relayed from the raw pipeline when a socket or device connection becomes available to a client; Pipelines in the_host_
role are considered to be implicitely "connected", so this event will is not required to be fired for them;connect
: EMC handshake successful (i.e. theINFO
response received) in the user role;process_message
: callback for a received message, in string form (useful for monitors and loggers)process_error
: callback for a received stderr message, in the string form (useful for monitors and loggers)process_request
: received an inbound query decoded into an EMC request (for pipelines in the host role);process_response
received an inbound query decoded into an EMC response (for pipelines in the user role);process_comment
: callback for a received query that is neither a request nor a response in EMC message format;process_packet
: received a data packet;return_message
: response to an inbound message to be returned (on the return flow);return_packet
: response to an inbound packet to be returned (on the return flow);disconnect
: connection to the server still up, but EMC interface no longer responding (user role);drop
: connection to the server closed or lost.
The events are accessible via the emcstage
interface:
bool emc_std_resume(gateway*) noexcept
void emc_std_join()
void emc_std_connect(const char*, const char*, int)
void emc_std_process_message(const char*, int)
void emc_std_process_error(const char*, int)
int emc_std_process_request(int, const sys::argv&)
int emc_std_process_response(int, const sys::argv&)
void emc_std_process_comment(const char*, int)
int emc_std_process_packet(int, int, std::uint8_t*)
int emc_std_return_message(const char*, int)
int emc_std_return_packet(int, int, std::uint8_t*)
void emc_std_disconnect()
void emc_std_drop()
void emc_std_suspend(gateway*) noexcept
void emc_std_event(int, void*)
void emc_std_sync(float)
emi_ring_network
: peer is running remotely, no shm, ipc or direct filesystem access available between peersemi_ring_machine
: peer is attached to the same machine - shm and ipc mechanisms available, filesystem paths are absolute and restrictedemi_ring_session
: peer is attached to the same session - shm and ipc mechanisms available, filesystem paths can be relative to the session pathemi_ring_process
: peer is within the same address space
Controllers are a subset of EMC stages which provide a services to the agent(s) through the command line interface. Each controller extends the base protocol with an unique set of commands through which it can be interacted with, called a Layer.
get_layer_name()
: name for the controller/layer; ifnullptr
, the service will be hidden from the support listget_enabled()
: indicates whether or not the service is active
Devices implement high bandwith software interfaces to streams on the machine. As opposed to generic controllers, Devices do not need to define their own commands, but are instead accessible via the "dev" layer, using commands such as:
support
describe [<device>]
control
open
close
sync
EMC manages devices and streams via special entity called a mapper, which can be further specialized for a multitude of device types and functions.
Services are bits of functionality that an agent can make available to an EMC device in order to fulfill certain tasks; Services are advertised by the controlling device (agent) upon connect via the s+
command.
agent | device
EMC
[service] <-----> [interface]
RQID := [A-Za-z?]+ ; request identifier
RSID := [A-Za-z0-9]+ ; response identifier
SPC := [\s\t]+
EOL := [\r] | [\n] | [\r\n]
REQUEST := '?' RQID ... EOL
RESPONSE := ']' RSID ... EOL
PROTOCOL := [0-9a-z_-]+
DECIMAL := [0-9]
VERSION := DECIMAL '.' DECIMAL DECIMAL
NAME := [0-9A-Za-z_-]{1..23}
TYPE := [0-9A-Za-z_-]{1..7}
ARCHITECTURE := IDENT
BYTE_ORDER := "le" | "be"
MTU := [0-9A-Fa-f]+
RESPONSE := ']' 'i' SPC PROTOCOL SPC 'v' VERSION SPC NAME SPC TYPE SPC ARCHITECTURE '_' ORDER SPC MTU EOL
LAYER := [A-Za-z_][0-9A-Za-z_]*
RESPONSE := ']' 'c' SPC [LAYER [SPC LAYER]...] EOL
RESPONSE := ']' 'g' SPC [^SPC]+ EOL
HEX := [0-9A-F]
RESPONSE := ']' HEX{2} .* EOL
RESPONSE := ']' 'z' .* EOL
o [...] o * [...] o 0 [...]
ctl +sync ctl -sync
r
w
x
HEX := [0-9A-Fa-f]
SIZE := BYTE{3}
RESPONSE := [\x80-\xfe] SIZE DATA{SIZE} EOL
CHANNEL = EOF - C
- INFO
- CAPS
- ...
- EOD