Difference between revisions of "Embedded Systems C Bridge API"

From expecco Wiki (Version 2.x)
Jump to navigation Jump to search
(One intermediate revision by the same user not shown)
Line 583: Line 583:
 
return REQUEST_OK;
 
return REQUEST_OK;
 
}
 
}
  +
  +
=== Protocol Examples ===
  +
  +
==== Connect ====
  +
connect to bridge@localhost:8855...
  +
wait for connectAck of bridge@localhost:8855...
  +
## CBridge wait
  +
  +
<< CBridge: received: >>145 {"type":"event","event":"hello","seq":1,"body":{"bridge":{"name":"cBridge","version":"1.1"},"protocols":["simpleText/json","simpleBinary/json"]}}<<
  +
<< CBridge: got payload: {"type":"event","event":"hello","seq":1,"body":{"bridge":{"name":"cBridge","version":"1.1"},"protocols":["simpleText/json","simpleBinary/json"]}}
  +
Incoming Message: Payload=>{"type":"event","event":"hello","seq":1,"body":{"bridge":{"name":"cBridge","version":"1.1"},"protocols":["simpleText/json","simpleBinary/json"]}}<
  +
check protocols: simpleText/json
  +
check protocols: simpleBinary/json
  +
## CBridge wait
  +
  +
==== ui.getTopElements ====
  +
>> 56 {"seq":1,"type":"request","command":"ui.getTopElements"}
  +
<< CBridge: received: >>179 {"type":"response","requestSeq":1,"command":"ui.getTopElements","body":[{"id":"1","name":"Address Book - common lib","type":"MainWindow","text":"","visible":true}],"success":true}<<
  +
<< CBridge: got payload: {"type":"response","requestSeq":1,"command":"ui.getTopElements","body":[{"id":"1","name":"Address Book - common lib","type":"MainWindow","text":"","visible":true}],"success":true}
  +
Incoming Message: Payload=>{"type":"response","requestSeq":1,"command":"ui.getTopElements","body":[{"id":"1","name":"Address Book - common lib","type":"MainWindow","text":"","visible":true}],"success":true}<
  +
Response:
  +
## CBridge wait
  +
  +
==== ui.getChildElements ====
  +
>> 81 {"seq":2,"type":"request","command":"ui.getChildElements","arguments":{"id":"1"}}
  +
<< CBridge: received: >>223 {"type":"response","requestSeq":2,"command":"ui.getChildElements","body":[{"id":"2","name":"","type":"AddressWidget","text":"","visible":true},{"id":"3","name":"","type":"QMenuBar","text":"","visible":true}],"success":true}<<
  +
<< CBridge: got payload: {"type":"response","requestSeq":2,"command":"ui.getChildElements","body":[{"id":"2","name":"","type":"AddressWidget","text":"","visible":true},{"id":"3","name":"","type":"QMenuBar","text":"","visible":true}],"success":true}
  +
Incoming Message: Payload=>{"type":"response","requestSeq":2,"command":"ui.getChildElements","body":[{"id":"2","name":"","type":"AddressWidget","text":"","visible":true},{"id":"3","name":"","type":"QMenuBar","text":"","visible":true}],"success":true}<
  +
Response:
  +
## CBridge wait
  +
  +
==== ui.getAppProperties ====
  +
>> 58 {"seq":3,"type":"request","command":"ui.getAppProperties"}
  +
<< CBridge: received: >>2108 {"type":"response","requestSeq":3,"command":"ui.getAppProperties","body":[{"name":"QApplication","properties":[{"propertyName":"autoSipEnabled","propertyValue":"true","propertyType":"bool"},{"propertyName":"cursorFlashTime","propertyValue":"1200","propertyType":"int"}, ... {"propertyName":"windowIcon","propertyValue":"","propertyType":"QIcon"}]},{"name":"QGuiApplication","properties":[ ... ]},{"name":"QCoreApplication","properties":[ ... ]},{"name":"QObject","properties":[ ... ]}],"success":true}<<
  +
<< CBridge: got payload: {"type":"response","requestSeq":3,"command":"ui.getAppProperties","body":[{"name":"QApplication","properties":[{"propertyName":"autoSipEnabled","propertyValue":"true","propertyType":"bool"},{"propertyName":"cursorFlashTime","propertyValue":"1200","propertyType":"int"}, ... {"propertyName":"windowIcon","propertyValue":"","propertyType":"QIcon"}]},{"name":"QGuiApplication","properties":[ ... ]},{"name":"QCoreApplication","properties":[ ... ]},{"name":"QObject","properties":[ ... ]}],"success":true}
  +
Incoming Message: Payload=>{"type":"response","requestSeq":3,"command":"ui.getAppProperties","body":[{"name":"QApplication","properties":[{"propertyName":"autoSipEnabled","propertyValue":"true","propertyType":"bool"},{"propertyName":"cursorFlashTime","propertyValue":"1200","propertyType":"int"}, ... {"propertyName":"windowIcon","propertyValue":"","propertyType":"QIcon"}]},{"name":"QGuiApplication","properties":[ ... ]},{"name":"QCoreApplication","properties":[ ... ]},{"name":"QObject","properties":[ ... ]}],"success":true}<
  +
Response:
  +
## CBridge wait
  +
  +
==== ui.getProperties ====
  +
>> 78 {"seq":4,"type":"request","command":"ui.getProperties","arguments":{"id":"1"}}
  +
<< CBridge: received: >>6022 {"type":"response","requestSeq":4,"command":"ui.getProperties","body":[{"name":"DynamicProperties","properties":[{"propertyName":"expecco-id","propertyValue":"1","propertyType":"QString"}, ... ]},{"name":"QMainWindow","properties":[ ... ]}, ... ],"success":true}<<
  +
<< CBridge: got payload: {"type":"response","requestSeq":4,"command":"ui.getProperties","body":[{"name":"DynamicProperties","properties":[{"propertyName":"expecco-id","propertyValue":"1","propertyType":"QString"}, ... ]},{"name":"QMainWindow","properties":[ ... ]}, ... ],"success":true}
  +
Incoming Message: Payload=>{"type":"response","requestSeq":4,"command":"ui.getProperties","body":[{"name":"DynamicProperties","properties":[{"propertyName":"expecco-id","propertyValue":"1","propertyType":"QString"}, ... ]},{"name":"QMainWindow","properties":[ ... ]}, ... ],"success":true}<
  +
Response:
  +
## CBridge wait
  +
  +
==== ui.getElementByXPath ====
  +
>> 95 {"seq":5,"type":"request","command":"ui.getElementByXPath","arguments":{"xpath":"/MainWindow"}}
  +
<< CBridge: received: >>182 {"type":"response","requestSeq":5,"command":"ui.getElementByXPath","body":[{"id":"1","name":"Address Book - common lib","type":"MainWindow","text":"","visible":true}],"success":true}<<
  +
<< CBridge: got payload: {"type":"response","requestSeq":5,"command":"ui.getElementByXPath","body":[{"id":"1","name":"Address Book - common lib","type":"MainWindow","text":"","visible":true}],"success":true}
  +
Incoming Message: Payload=>{"type":"response","requestSeq":5,"command":"ui.getElementByXPath","body":[{"id":"1","name":"Address Book - common lib","type":"MainWindow","text":"","visible":true}],"success":true}<
  +
Response:
  +
## CBridge wait
  +
  +
==== ui.getElementsByXPath ====
  +
>> 96 {"seq":6,"type":"request","command":"ui.getElementsByXPath","arguments":{"xpath":"/MainWindow"}}
  +
<< CBridge: received: >>183 {"type":"response","requestSeq":6,"command":"ui.getElementsByXPath","body":[{"id":"1","name":"Address Book - common lib","type":"MainWindow","text":"","visible":true}],"success":true}<<
  +
<< CBridge: got payload: {"type":"response","requestSeq":6,"command":"ui.getElementsByXPath","body":[{"id":"1","name":"Address Book - common lib","type":"MainWindow","text":"","visible":true}],"success":true}
  +
Incoming Message: Payload=>{"type":"response","requestSeq":6,"command":"ui.getElementsByXPath","body":[{"id":"1","name":"Address Book - common lib","type":"MainWindow","text":"","visible":true}],"success":true}<
  +
## CBridge wait
  +
  +
==== ui.handleRemoteAction ====
  +
>> 244 {"seq":4,"type":"request","command":"ui.handleRemoteAction","arguments":{"action":"QT::OpenMenuOrTriggerAction","parameters":"path=/MainWindow[1]/QMenuBar[1]/QAction[@name='&Tools']","path":"/MainWindow[1]/QMenuBar[1]/QAction[@name='&Tools']"}}
  +
<< CBridge: received: >>83 {"type":"response","requestSeq":4,"command":"ui.handleRemoteAction","success":true}<<
  +
<< CBridge: got payload: {"type":"response","requestSeq":4,"command":"ui.handleRemoteAction","success":true}
  +
Incoming Message: Payload=>{"type":"response","requestSeq":4,"command":"ui.handleRemoteAction","success":true}<
  +
  +
==== Disconnect ====
  +
## CBridge terminate reader
  +
## CBridge close socket
  +
## CBridge reader process got TerminateProcessRequest
  +
## CBridge reader process finished
   
 
== Sample Application with GUI Automation Support ==
 
== Sample Application with GUI Automation Support ==

Revision as of 14:19, 24 March 2020

Contents

Embedded Systems C Bridge API[edit]

We provide the CBridge code both as a DLL (shared object) and as a static link library upon request. If linked against the application, or dynamically loaded into the application, expecco can interact with it - even if the target is an embedded system.

In addition, a fully linked executable called "cBridge" ("cBridge.exe" for Windows) is provided to be used as a default bridge for elementary C actions. This executable is found in the expecco installation folder.

Architecture[edit]

The CBridge opens a socket and listens for an incoming connection. Only one such connection may be active at any time (per bridge). If multiple bridges are to be used on the same host machine, each must use a different port number, which will typically be passed via a command line argument or via a configuration setting inside the target.

Expect will connect to a bridge, given hostname and port. For the implicit default bridge, expecco uses "localhost:8855". Once connected, expecco will send requests and receive responses, which are exchanged in human readable JSON format, which is similar to the ChromeV8 debug or VSCode debug interface message format.

The set of supported messages is defined by the target system - therefore customers have full control over which messages are allowed to be interchanged, and how they are handled. This is done before the bridge is activated by so called "message registration" calls.

The bridge code as provided by exept includes handlers for various common tasks, which can be registered as option. Except also provides a ready-to-run cBridge executable, which is configured to register handlers for remote code execution (i.e. for Bridged C Actions).

As an alternative to using the exept library, customers can also implement the wire protocol, which is described below.

CBridge Message Scheme[edit]

The protocol consists of messages, which have a type field of "request", "event" or "response". All requests will be answered by a response, which contains a success status field and optional additional data. Events are not answered. In theory, the protocol allows for multiple requests to be sent and answered asynchronously, and even out of order, as every request includes a sequence number, which is reflected in the corresponding response. However, expecco does not (and will not) make use of this, so the cBridge code in the client can safely assume that no new request will be sent until the current request is answered. This makes the target implementation easier and especially the memory allocation patterns deterministic.

CBridge Message Interchange Sequence Diagram[edit]

In the following swim lane diagram,

  • function calls are marked as "=====>"
  • socket messages as "------>"
  • parallel execution as "||"
  • thread blocking (suspend / wait) as "***"
  • normal thread execution as "..."
expecco side         target                                cBridge library          cBridge thread
--------------------+-------------------------------------+------------------------+----------------------------
     ...            |    ...                              |                        | 
                    |initialization:                      |                        |
                    |  cBrige_registerHandler(...) =======> register handler       |
                    |                                     | immediate return       |
                    |                              <=======                        |
                    |     ...                             |                        | 
                    |  cBrige_registerHandler(...) =======> register handler       |
                    |                                     | immediate return       |
                    |                              <=======                        |
                    |     ...                             |                        | 
                    |  cBrige_startAsync(...)      =======> starts cBridge thread  | thread started    
                    |                                     | immediate return       | opens listen socket
                    |     ...                      <=======                        | await connection       
                    |  target runs as usual               |                        | ***
                    |     ...                             |                        | await incoming message
                    |                                     |                        | ***
     ...            |                                     |                        |
   send request -------------------------------------------------------------------> receive request (JSON)
   await response   |                                     |                        | decode request
     ***            |                                     |                        | prepare response object
                    |                                     |                        | call handler(req, resp)
                    |     ... || handler in target        <=========================
                    |     ... || fills response           |                        |
                    |     ... ||                          =========================> send response
                    |                                     |                        | encode response
   send request <------------------------------------------------------------------- send response (JSON)
     ...            |     ...                             |                        | await incoming message
                    |                                     |                        | ***

Different Threading Models[edit]

The bridge library was designed to be usable in near real time environments and prevents blocking on I/O operations. Depending on constraints of the target system, it can be used in various different configurations:

Threaded with separate Socket handling code[edit]

This is the easiest mode to use. The socket handler is started as a separate thread, which blocks in the listen(), read() and possibly in write() calls to the socket.

The target's code must be changed to perform a few calls into the cLibrary (to start the asynchronous bridge thread and for handler registration). This is usually done at the very beginning of the target's operation (i.e. in its main() function) or whenever a debug option is enabled in it (eg. via a menu function). Typically targets will either take an additional command line argument or a configuration file setting to enable the cBridge.

This mode should be used if the target is a Windows program or if the target's operating system supports Posix threads.

Non-Threaded with Socket Polling[edit]

In this mode, the main application should poll (or select on) the socket itself, and call into the cLibrary when either a connection is established, or an incoming message is received.

In addition to the handler registration calls and handler code implementation, the target needs to be changed to call the library's incomingMessage handler and to provide a function for outgoing messages to be sent back to expecco.

This mode can be used when no thread support is provided by the operating system, or if there already exists an event handling loop which selects on input streams (eg. Qt, X11 or OSX main event loops), and sockets are to be used for bridge communication. This mode can also be used in systems without thread support, which have a cyclic execution scheme, such as many programmable control systems.

Non Threaded with Packet Delivery (non-Socket Communication)[edit]

In this mode, the main application should use any mechanism available to exchange messages via any external port (USB, serial, socket) or via shared memory, and call into the cLibrary when an incoming message arrived. It must also register a packet delivery function, for the cLibrary to send back responses.

This mode must be used, if no socket communication is possible. An adaptor will be needed on the expecco side to replace the socket communication code. This needs to be implemented upon request and as per specification and is not provided out of the standard deployed box.

Different Memory Allocation Models[edit]

Message interchange between expecco and the cBridge is encoded as JSON objects. Because these JSON objects vary in size and structure, they must use dynamically allocated memory. Because many embedded systems will not allow or support the use of "malloc/free", the library can be configured to:

  • either use malloc / free dynamically (i.e. for every object) (dynamic malloc scheme)
  • or to use a single malloc'd area, which is allocated once at startup (single malloc scheme)
  • or to not use malloc/free at all, but instead use a static memory area for temporary data (static data scheme)

Memory needs to be dynamically allocated whenever an incoming message is parsed from JSON into a jsonObject, when additional fields are added to a jsonObject (i.e. when a requestHandler constructs a response object), or when a jsonObject response is finally encoded into JSON and sent back to expecco.

All memory which is allocated for the request and response objects will be released when the message handler's response object has been sent to expecco. Pointers into it and field references are no longer valid, as the memory will be reused for the next request/response.

Dynamic Malloc Scheme[edit]

This is the standard behavior when the target runs on Windows or Unix systems.

Allocations will be done by calling "malloc" multiple times, once for each individual jsonObject field and possibly for field values. The objects will be freed after the transaction.

Single Malloc Scheme[edit]

This should be used if the target system does provide malloc, but dynamic malloc is not allowed or not wanted due to other constraints (thread safety, predictability, real time constraints etc.).

A single bigger memory area is allocated using "malloc" at startup time. All jsonObject fields will be allocated inside this arena, which will never be freed. The size of the arena defines the maximum JSON message size which can be interchanged, and an operation may fail due to that constraint.

Static Data Scheme[edit]

This mode should be used, if no malloc is available or allowed at all inside the target system.

Similar to the previous scheme, but static memory (a data buffer provided by the target) is used, and malloc/free are never called.

CBridge Library[edit]

Initialization & Configuration[edit]

The bridge code is configured and initialized typically inside the target's main() function. Usually dependent on either a command line argument, or a setting in a configuration file. In targets with a GUI, it may also be called due to the user selecting a menu function or hot key.

cBridge_config[edit]
cBridge_config(cBridgeConfigParameter what, void* valuePtr);

to configure bridge parameters. Some parameters must/can be set before the cBridge server is started. This function can be called multiple times (with different what parameters. Some of those config calls must be done before cBridge_startXXXServer() is called.

Currently supported configuration parameters are:

  • BRIDGE_PAYLOAD_LIMIT
    valuePtr must point to an integer (i.e. it must be an int*), which holds the max. payload length. This will be sent to a client in the handshake. If the client does not honor this limit, incoming packets will be ignored or responded with an error. Must be set before the cBridge server is started.
  • BRIDGE_LOGGING
    valuePtr must point to an integer (an int*), which holds a boolean (0 or 1) to disable/enable logging. Can be called at any time.
  • BRIDGE_PORT
    valuePtr must point to an integer (an int*), which holds the port. The default port is (currently) 8855. If a port different from the default is to be used, this must be set before the cBridge server is started.
  • SYSTEM_NAME
    valuePtr points to the name of the system (a char*). This is only used to display friendly connection names to humans inside expecco - it has no semantic meaning. This will be sent to expecco when the next connection is established and is remembered in the expecco side in the bridge connection object. It may be used by the test suite in combination with SYSTEM_VERION below, to adapt the test cases dynamically to specific hard- or software configurations.
  • SYSTEM_VERSION
    valuePtr (a char*) points to the version name. Same use as SYSTEM_NAME above.
cBridge_registerHandler[edit]

Before the cBridge server is started, callback handlers must be registered. The initialization code should call:

cBridge_registerHandler(char* command, handlerFunction handler);

to define a handler for a particular message.

The handlerFunction argument must be a function defined as:

requestStatus handler(jsonObject request, jsonObject response)

This handler will be called when a corresponding request is received (i.e. with "request":"<command>" or with "event":"<command>"). There are no disjoint "namespaces" for requests and events. The handler will receive the request/event object and an empty response object, which it should fill with values.

Because the command is also found inside the passed request object, it is possible to define a single handler function for all requests and events. In such a configuration, the handler function should itself extract the "command"/"event" from the JSON object and switch internally. However, the code is usually better structured and easier to extent later, if individual handlers are registered.

Request handlers must return a requestStatus, which is an enum { REQUEST_OK, REQUEST_ERROR }. This will be returned to expecco as "success" status of the request (i.e. in the response's "success" slot). For events, no response is sent back to expecco, and the returned status is ignored.

cBridge_registerStandardActionHandlers[edit]
cBridge_registerStandardActionHandlers();

to define standard handlers for action definition and calling (for "bridged C action" support in expecco).

Startup[edit]

cBridge_startAsyncServer[edit]

to start the server in a separate thread.

int cBridge_startAsyncServer(int port, bool_t keepAlive, int memoryLimitOrZero);
  • keepAlive
    if true, the server stays in it wait-for-connection loop and will serve multiple sessions (in sequence, but only one at a time). If false, the server will close the listening socket after the first session and will finish its operation. I.e. will only serve one single session.
cBridge_isAsyncServerRunning[edit]

To check if a server is already running:

bool_t cBridge_isAsyncServerRunning();

Returns true (1) if a server is already running in the background thread.

cBridge_waitForAsyncServerToFinish[edit]
int cBridge_waitForAsyncServerToFinish();

will block and wait until the background server thread finishes. With keepAlive being false, this will wait until one session is complete.

cBridge_server[edit]

To enter the bridge server loop,

cBridge_server(int port, bool_t keepAlive, int memoryLimitOrZero);

This will block on a socket and is internally called by startAsyncServer(). Simple non-threaded "bridge-only" systems can call this from their main thread.

  • keepAlive as described above.

JSON Library[edit]

Messages are interchanged as JSON encoded packages. For this, a JSON library is part of the cLibrary, to parse and generate JSON.

Bridge Wire Protocol[edit]

The following wire protocol is used for all C, Node, Python and Smalltalk bridges (by the time of writing). The .NET and Java bridges use a much more sophisticated protocol, which is not described here.


Frame Format[edit]

Messages are interchanged in frames which contain the length of a payload and the payload itself. A packet's payload is always encoded as a JSON object.

This payload can be (currently) delivered in one of the following frames:

  • HTTP/REST frame
    the payload is the POST data of an HTTP packet; the payload length is provided by the HTTP header.
  • a binary frame, consisting of 4 bytes of payload length, followed by the payload.
  • a textual frame, consisting of the payload length as ascii string, a space as separator, followed by the payload.

Both the expecco side and the cLibrary support both the binary and textual frame formats. By default (unless otherwise configured), both sides use the textual frame format initially. Every bridge implemntation must at least implement this textual frame format, and must come up with this initially.

Future extensions will be backward compatible.

Currently, the HTML/REST format is not yet supported directly by the cBridge library. However, it can be used if the target app already includes an HTTP/REST service and passes incoming payload to the cLibrary.

Handshake[edit]

The bridge client (cLibrary) usually opens a TCP socket and listens for a connection. After accept, a single hello message is sent to the expecco side. By default, this initial hello message is ALWAYS encoded as a textFrame. (however, depending on the hello message's payload, the protocol may be switched to another format for further messages).

Requests (General Format)[edit]

Requests can be sent from either side. On the expecco side, a request call will block the calling thread, until a response is received. On the cBridge side, a callback will be called, when the response is received. In theory, the protocol allows for multiple request to be sent and outstanding until corresponding responses are received. However this is actually not needed and therefore currently not supported by either expecco, not the cBridge library.

All requests must conform to the format:

{
    "type":        "request",                    // required
    "command":     "<nameOfCommand>",            // required
    "seq":         sequenceNr                    // required

    "body":        { ... }                       // optional
    "part":        partNr                        // optional
    "more":        boolean                       // optional
}

where:

  • command the name as given to the "cBridge_registerHandler() function previously,
  • seq a unique number assigned to every request/event by the sender side (usually simply a running number, or UUID-string). The value of "seq" will later be found in the corresponding response object.
  • part if present, this must be a running number, sequencing the parts of a single message. Numbers start with 1.
  • more if present, informs that more parts are to be expected for the current message. If missing and this is a part-package, it is assumed that this was the last part and the message is now complete (i.e. defaults to false)
  • body request specific

Long packets may be split into and sent as a sequence of smaller packets, if a partner has limited resources in memory) or to reserve bandwidth for other communication. It should then add both a "part" field (containing a part-sequence number, starting at 1 with every message) and a "more" boolean flag, which tells the other side, that more packets are to be received for the same message. Messages with huge amounts of data will split this way (eg. screenDump). The details on how that bulk data is split among the individual parts is to be documented in the concrete message.

Response (General Format)[edit]

Responses are only sent as an answer to a previous incoming request. Every request MUST be answered by a response.

All responses must contain the following fields:

{
    "type":        "response",                   // required
    "requestSeq":  sequenceNr                    // required
    "success":     boolean,                      // required

    "command":     "<nameOfCommand>",            // optional (but recommended)
    "body":        { ... },                      // optional (usually present if success == true)
    "errorMessage":"...",                        // optional (if success == false)
    "part":        partNr                        // optional
    "more":        boolean                       // optional
}

where:

  • command the command from the corresponding request, for which this is the response.
  • requestSeq the seqNr of the corresponding request, for which this is the response.
  • success boolean true or false
  • errorMessage if success is false
  • part and more as described above
  • body response specific

Event (General Format)[edit]

Events can be sent at any time (i.e. while waiting for a response, while executing a request action, or at any other time). The bridge ensures, that an ongoing message is not disturbed (i.e. the actual packet sending is atomic). Events are not answered by a response.

All responses must contain the following fields:

{
    "type":        "event",                      // required
    "seq":         sequenceNr                    // required
    "event":       "<nameOfEvent>",              // required

    "arguments":   { ... },                      // optional
    "part":        partNr                        // optional
    "more":        boolean                       // optional
}

where:

  • seq same as in requests. Event sequence numbers may not be the same as request sequence numbers (i.e. you can use one single sequence-nr generator, which simply increments on every packet)
  • arguments event specific arguments

Multipart Packages[edit]

Long messages may be split into multiple parts. Then, the first N-1 packets contain the fields:

   "part": <nr>
   "more": true

and the last part contains:

   "part": <N>
   "more": false

where <nr> is a sequence nr starting with 1. Each part should contain a "data": field (or similar), where the values are concatenated to form the effective long data. Typically, the splitted data is a long string containing Base64 encoded bytes (such as a big screenshot's PNG image). The name of the field which contains the splitted data depends on the message type and is specified there. Clients which want to receive splitted packets (due to internal receive buffer limitations) must announce this in their initial handshake, by adding a corresponding limits field.

Requests (from expecco to cBridge)[edit]

The following lists well known messages only. The format of user-defined messages (i.e. for which a handler was registered) is variable and opaque to the bridge.

REQUEST: protocol[edit]

Sent from expecco to change the protocol. This will only be sent if the bridge announced the availability of another protocol (via a HELLO event), AND expecco decides to use another.

Expecco will choose the first within the list of available protocols, which is also supported by the expecco side.

{
    "type":    "request",                       // required
    "command": "protocol",                      // required
    "seq":     sequenceNr                       // required
    "body":    {                                // required
                 "protocol":  "protocolToUse",      // required
               }
}

when this message arrives, the response MUST be sent in the current protocol mode. Then, any further communication MUST be performed in the new protocol.
Currently, this is not used by expecco when running against the existing bridges. The text/json protocol will always be used (if presented as first message in the hello announcement).

REQUEST: defineAction[edit]

This is an internal request for Bridged C actions. Its format is not part of the stable API. The bridge will immediately send a response.

REQUEST: callAction[edit]

This is an internal request for Bridged C actions. Its format is not part of the stable API. The bridge will immediately send a response, call the defined action and send a finished event when done (i.e. this is an async call finished notification).

REQUEST: callFunction[edit]

This is an internal request for Script languages. Its format is not part of the stable API. The bridge will call the named function and send a response when done (i.e. a synchronous call finished response).
The current CBridge does not need or support this request, but it may be implemented in future versions.

REQUEST: invokeMethod[edit]

This is an internal request for Script languages. Its format is not part of the stable API. The bridge will invoke a named method and send a response when done (i.e. a synchronous call finished response).
The current CBridge does not need or support this request, but it may be implemented in future versions (for C++).

REQUEST: queryMethod[edit]

This is an internal request for Script languages. Its format is not part of the stable API. The bridge will check for the presence of a named method and send a response containing detail information.
The current CBridge does not need or support this request, but it may be implemented in future versions (for C++).

REQUEST: evalCode[edit]

This is an internal request for Script languages. Its format is not part of the stable API. The bridge will evaluate (i.e. interpret) the code and send a response when done (i.e. a synchronous call finished response).
The CBridge does not need or support this request (and likely never will) - it is used for other script languages..

REQUEST: getProperty[edit]

This is an internal request to access slots of an object. Its format is not part of the stable API. The bridge will retrieve an object's slot and return it in its synchronous response.
The CBridge does not need or support this request (and likely never will) - it is used for other script languages.

REQUEST: getPropertyNames[edit]

This is an internal request to retrieve slot names o an object. Its format is not part of the stable API. The bridge will retrieve an object's slot names and return it in its synchronous response.
The CBridge does not need or support this request (and likely never will) - it is used for other script languages..

REQUEST: releaseReference[edit]

This is an internal request used with script languages with garbage collectors. Its format is not part of the stable API.
The CBridge does not need or support this request, but it may be implemented in future versions (for C++).

Events (from cBridge to expecco)[edit]

EVENT: hello[edit]

Sent from the bridge to expecco to provide version and protocol formats.

{
    "type":      "event",                      // required
    "event":     "hello",                      // required
    "seq":       sequenceNr                    // required
    "body":      {                             // optional
                  "protocols": [ "<supported1>", ... ]       // optional
                  "limits":    [ <limit1>, ... ]             // optional
                  "bridge":    {                             // optional
                                "name":     "<bridgeName>"          // optional
                                "version":  "<version string>",     // optional
                               }
                  "system":    {                             // optional
                                "name":     "<systemName>"     }    // optional
                                "version":  "<systemVersion>"  }    // optional
                               }
                 }
}

Used to announce version information and available protocols.

  • version: a string of the form "<major>.<minor>". For same major numbers, any minor less or equal to a partner's own minor version must be supported. The major version MUST be incremented for incompatible (format) changes. The minor version should be incremented if new protocol messages or new fields are added. Expecco will fall back to the cBridge protocol version, iff the cBridge's hello-announced version is smaller than expecco's own implemented protocol version.
  • protocols: list of supported protocols, in order of preference. Must include at least 'text/json'.
  • limits: list of limits

Each <supported> element describes a protocol which is supported by the target/cLibrary, in order of preference (the targets preference). The <supported> strings are of the form: "frameFmt/payloadFmt[/version]", where frameFmt describes the frame format, payloadFmt the encoding of the payload and the optional version further details on the used payload format. A missing version part defaults to "1".

Currently known protocols are:

  • text/json/1 - JSON encoded payload, in a text frame (i.e. prefixed by payload size in ascii and space)
  • binary/json/1 - JSON encoded payload, in a binary frame (i.e. prefixed by 4 bytes of payload size)
  • http/json/1 - JSON encoded payload, in an HTTP frame

The partner (expecco) may decide to switch the protocol, based on the returned information.

The optional <limit> elements can be used to order the partner to not exceed certain limits. Either side may refuse and ignore packets (or respond with an error) if a requested limit is exceeded. Currently known limits are:

  • { "payloadSize": maxSize }
    tells the partner to not send packets longer than that size. If required, packets must then be split when into smaller continuation packets. This can be used to limit the amount of memory needed inside a partner when converting to/from JSON (i.e. when running in an embedded system). As the overall packet size is hard to predict (due to sequence numbers), this limit will be applied differently depending on the concrete message (see notes there).
  • the optional bridge and system objects allow for additional informal to be sent back. These are optional and may be used by expecco to present nicer connection names or other user hints.
EVENT: finished[edit]

This is an internal event for Bridged C actions. Its format is not part of the stable API.

EVENT: out[edit]

This is an internal event for Bridged C actions. Its format is not part of the stable API. It is sent from the bridge to send a value to an output pin.

EVENT: output[edit]

Sent from the bridge to expecco to output a string to the expecco Transcript (console), to expecco's stdout or to expecco's stderr.

{
    "type":      "event",                      // required
    "event":     "output",                     // required
    "seq":       sequenceNr                    // required
    "body":      {
                  "message":   "<how>",                      // required
                  "target":    "<stream>",                   // required
                  "args":      ["<fmt>",...]                 // required for show or showCR
                 }
}
  • message one of "cr", "show" or "showCR"
  • target one of "Transcript", "Stdout" or "Stderr"
  • args the arguments for the string to be sent. If the first argument ("<fmt>") contains placeholders like "%1", these are replaced by corresponding values from the remaining args (i.e "some error in %1"). Otherwise, the arguments are concatenated to form a single string.

Requests (from cBridge to expecco)[edit]

The CBridge uses these to send messages to the expecco Transcript (console), to add entries to the activity log or to write data to output pins.

GUI Automation Support[edit]

GUI automation support for QT, OpenETS and Smalltalk use the same protocol and message format as described above. Bridges may choose to implement (or support) either the above code execution messages, or the GUI messages described here, or both.

If a bridge supports only the GUI messages, no remote code execution will be possible. However, the GUI browser will work with the bridged application.

On the other hand, if only the above code execution messages are supported, no GUI browsing will be possible, but remote code execution will still work.

Thus, GUI automation support does NOT require the above code execution protocol as a prerequisite.


Architecture[edit]

Modified Application Setup[edit]

To interface an application (possibly embedded) with a graphical user interface to expecco for automation, you have to add the following to the (C/C++) application:

  • link the cBridge library statically or dynamically to your application
  • write callback functions as described below
  • call "cBridge_registerHandler(...) for these handlers, as described above
  • call "cBridge_startAsyncServer(...) to start the bridge, as described above

Code Injection Setup[edit]

Start the deployed cBridge (which is provided as binary by exept) with an inject command line argument. It will then start the application and inject itself into it. Notice that this is not possible on all supported architectures; currently Windows, Linux and OSX are supported. Embedded systems must use the previous modified application setup.

Once started, the cBridge server awaits incoming connections and calls into the registered handler, when a request arrives.

GUI Handlers[edit]

the following functions must be implemented and registered towards the bridge:
Details to be documented...

ui.getTopElements[edit]

Returns the GUIs top-level elements.

ui.getChildElements[edit]

Returns an elements child elements.

ui.getApplicationProperties[edit]

Returns properties of the application.

ui.getElementProperties[edit]

returns properties of an element

ui.getElementProperty[edit]

returns a single property of an element

ui.getScreenShot[edit]

returns a screenshot

Additional (optional) GUI Handlers[edit]

ui.startRecording[edit]

ui.stopRecording[edit]

Events[edit]

When in recording mode, events are delivered by calling:

ui.sendRecordedEvent[edit]

Example Code Fragments[edit]

//
// handler registration
// (called by main, upon startup, before starting the cBridge server)
//
static void 
registerEntries() {
     cBridge_registerHandler("ui.getTopElements", getTopElements);
     cBridge_registerHandler("ui.getChildElements", getChildElements);
     cBridge_registerHandler("ui.getAppProperties", getAppProperties);
     cBridge_registerHandler("ui.getProperties", getProperties);
     cBridge_registerHandler("ui.getProperty", getProperty);
     cBridge_registerHandler("ui.getScreenshot", getScreenshot);
     ...
}
static jsonObject
createElement(char* idString, char* elementType) {
     jsonObject elementInfo = json_newStruct(2);
     json_addStructField(elementInfo, "id", json_newString(idString));
     json_addStructField(elementInfo, "name", json_newString(elementType));
     return elementInfo;
}
static requestStatus 
getTopElements(jsonObject requestIn, jsonObject responseOut) {
     jsonObject body = json_newStruct(1);
     jsonObject arr = json_newArray(6);

     json_addArrayElement(arr, createElement("3335", "label",  ""));
     json_addArrayElement(arr, createElement("2503", "window", "frame1"));
     json_addArrayElement(arr, createElement("2502", "menu",   "mainmenu"));
     json_addArrayElement(arr, createElement("2896", "panel", 	"myTopPanel"));
     json_addArrayElement(arr, createElement("0816", "button", "myButton"));
     json_addArrayElement(arr, createElement("0819", "radioButton", "myRadioButton"));
     json_addStructField(body, "topElements", arr);
     json_addStructField(responseOut, "body", body);
     return REQUEST_OK;
}
static requestStatus 
getChildElements(jsonObject requestIn, jsonObject responseOut) {
     jsonObject arguments = json_getStructField(requestIn, "arguments");
     jsonObject elementId = json_getStructField(arguments, "id");
     char* id = json_stringValue(elementId);
     ...
     same as above, filling an array with child-infos for id
     ...
     return REQUEST_OK;
}
static requestStatus 
getProperties(jsonObject requestIn, jsonObject responseOut) {
     jsonObject arguments = json_getStructField(requestIn, "arguments");
     jsonObject elementId = json_getStructField(arguments, "id");
     char* id = json_stringValue(elementId);
     ...
     fill an array with child-properties for id
     ...
     return REQUEST_OK;
}

Protocol Examples[edit]

Connect[edit]

connect to bridge@localhost:8855...
wait for connectAck of bridge@localhost:8855...
## CBridge wait
<< CBridge: received: >>145 {"type":"event","event":"hello","seq":1,"body":{"bridge":{"name":"cBridge","version":"1.1"},"protocols":["simpleText/json","simpleBinary/json"]}}<<
<< CBridge: got payload: {"type":"event","event":"hello","seq":1,"body":{"bridge":{"name":"cBridge","version":"1.1"},"protocols":["simpleText/json","simpleBinary/json"]}}
Incoming Message: Payload=>{"type":"event","event":"hello","seq":1,"body":{"bridge":{"name":"cBridge","version":"1.1"},"protocols":["simpleText/json","simpleBinary/json"]}}<
check protocols: simpleText/json
check protocols: simpleBinary/json
## CBridge wait

ui.getTopElements[edit]

>> 56 {"seq":1,"type":"request","command":"ui.getTopElements"}
<< CBridge: received: >>179 {"type":"response","requestSeq":1,"command":"ui.getTopElements","body":[{"id":"1","name":"Address Book - common lib","type":"MainWindow","text":"","visible":true}],"success":true}<<
<< CBridge: got payload: {"type":"response","requestSeq":1,"command":"ui.getTopElements","body":[{"id":"1","name":"Address Book - common lib","type":"MainWindow","text":"","visible":true}],"success":true}
Incoming Message: Payload=>{"type":"response","requestSeq":1,"command":"ui.getTopElements","body":[{"id":"1","name":"Address Book - common lib","type":"MainWindow","text":"","visible":true}],"success":true}<
Response: 
## CBridge wait

ui.getChildElements[edit]

>> 81 {"seq":2,"type":"request","command":"ui.getChildElements","arguments":{"id":"1"}}
<< CBridge: received: >>223 {"type":"response","requestSeq":2,"command":"ui.getChildElements","body":[{"id":"2","name":"","type":"AddressWidget","text":"","visible":true},{"id":"3","name":"","type":"QMenuBar","text":"","visible":true}],"success":true}<<
<< CBridge: got payload: {"type":"response","requestSeq":2,"command":"ui.getChildElements","body":[{"id":"2","name":"","type":"AddressWidget","text":"","visible":true},{"id":"3","name":"","type":"QMenuBar","text":"","visible":true}],"success":true}
Incoming Message: Payload=>{"type":"response","requestSeq":2,"command":"ui.getChildElements","body":[{"id":"2","name":"","type":"AddressWidget","text":"","visible":true},{"id":"3","name":"","type":"QMenuBar","text":"","visible":true}],"success":true}<
Response: 
## CBridge wait

ui.getAppProperties[edit]

>> 58 {"seq":3,"type":"request","command":"ui.getAppProperties"}
<< CBridge: received: >>2108 {"type":"response","requestSeq":3,"command":"ui.getAppProperties","body":[{"name":"QApplication","properties":[{"propertyName":"autoSipEnabled","propertyValue":"true","propertyType":"bool"},{"propertyName":"cursorFlashTime","propertyValue":"1200","propertyType":"int"}, ... {"propertyName":"windowIcon","propertyValue":"","propertyType":"QIcon"}]},{"name":"QGuiApplication","properties":[ ... ]},{"name":"QCoreApplication","properties":[ ... ]},{"name":"QObject","properties":[ ... ]}],"success":true}<<
<< CBridge: got payload: {"type":"response","requestSeq":3,"command":"ui.getAppProperties","body":[{"name":"QApplication","properties":[{"propertyName":"autoSipEnabled","propertyValue":"true","propertyType":"bool"},{"propertyName":"cursorFlashTime","propertyValue":"1200","propertyType":"int"}, ... {"propertyName":"windowIcon","propertyValue":"","propertyType":"QIcon"}]},{"name":"QGuiApplication","properties":[ ... ]},{"name":"QCoreApplication","properties":[ ... ]},{"name":"QObject","properties":[ ... ]}],"success":true}
Incoming Message: Payload=>{"type":"response","requestSeq":3,"command":"ui.getAppProperties","body":[{"name":"QApplication","properties":[{"propertyName":"autoSipEnabled","propertyValue":"true","propertyType":"bool"},{"propertyName":"cursorFlashTime","propertyValue":"1200","propertyType":"int"}, ... {"propertyName":"windowIcon","propertyValue":"","propertyType":"QIcon"}]},{"name":"QGuiApplication","properties":[ ... ]},{"name":"QCoreApplication","properties":[ ... ]},{"name":"QObject","properties":[ ... ]}],"success":true}<
Response: 
## CBridge wait

ui.getProperties[edit]

>> 78 {"seq":4,"type":"request","command":"ui.getProperties","arguments":{"id":"1"}}
<< CBridge: received: >>6022 {"type":"response","requestSeq":4,"command":"ui.getProperties","body":[{"name":"DynamicProperties","properties":[{"propertyName":"expecco-id","propertyValue":"1","propertyType":"QString"}, ... ]},{"name":"QMainWindow","properties":[ ... ]}, ... ],"success":true}<<
<< CBridge: got payload: {"type":"response","requestSeq":4,"command":"ui.getProperties","body":[{"name":"DynamicProperties","properties":[{"propertyName":"expecco-id","propertyValue":"1","propertyType":"QString"}, ... ]},{"name":"QMainWindow","properties":[ ... ]}, ... ],"success":true}
Incoming Message: Payload=>{"type":"response","requestSeq":4,"command":"ui.getProperties","body":[{"name":"DynamicProperties","properties":[{"propertyName":"expecco-id","propertyValue":"1","propertyType":"QString"}, ... ]},{"name":"QMainWindow","properties":[ ... ]}, ... ],"success":true}<
Response: 
## CBridge wait

ui.getElementByXPath[edit]

>> 95 {"seq":5,"type":"request","command":"ui.getElementByXPath","arguments":{"xpath":"/MainWindow"}}
<< CBridge: received: >>182 {"type":"response","requestSeq":5,"command":"ui.getElementByXPath","body":[{"id":"1","name":"Address Book - common lib","type":"MainWindow","text":"","visible":true}],"success":true}<<
<< CBridge: got payload: {"type":"response","requestSeq":5,"command":"ui.getElementByXPath","body":[{"id":"1","name":"Address Book - common lib","type":"MainWindow","text":"","visible":true}],"success":true}
Incoming Message: Payload=>{"type":"response","requestSeq":5,"command":"ui.getElementByXPath","body":[{"id":"1","name":"Address Book - common lib","type":"MainWindow","text":"","visible":true}],"success":true}<
Response: 
## CBridge wait

ui.getElementsByXPath[edit]

>> 96 {"seq":6,"type":"request","command":"ui.getElementsByXPath","arguments":{"xpath":"/MainWindow"}}
<< CBridge: received: >>183 {"type":"response","requestSeq":6,"command":"ui.getElementsByXPath","body":[{"id":"1","name":"Address Book - common lib","type":"MainWindow","text":"","visible":true}],"success":true}<<
<< CBridge: got payload: {"type":"response","requestSeq":6,"command":"ui.getElementsByXPath","body":[{"id":"1","name":"Address Book - common lib","type":"MainWindow","text":"","visible":true}],"success":true}
Incoming Message: Payload=>{"type":"response","requestSeq":6,"command":"ui.getElementsByXPath","body":[{"id":"1","name":"Address Book - common lib","type":"MainWindow","text":"","visible":true}],"success":true}<
## CBridge wait

ui.handleRemoteAction[edit]

>> 244 {"seq":4,"type":"request","command":"ui.handleRemoteAction","arguments":{"action":"QT::OpenMenuOrTriggerAction","parameters":"path=/MainWindow[1]/QMenuBar[1]/QAction[@name='&Tools']","path":"/MainWindow[1]/QMenuBar[1]/QAction[@name='&Tools']"}}
<< CBridge: received: >>83 {"type":"response","requestSeq":4,"command":"ui.handleRemoteAction","success":true}<<
<< CBridge: got payload: {"type":"response","requestSeq":4,"command":"ui.handleRemoteAction","success":true}
Incoming Message: Payload=>{"type":"response","requestSeq":4,"command":"ui.handleRemoteAction","success":true}<      

Disconnect[edit]

## CBridge terminate reader
## CBridge close socket
## CBridge reader process got TerminateProcessRequest
## CBridge reader process finished

Sample Application with GUI Automation Support[edit]

--- to be added here ---



Copyright © 2014-2020 eXept Software AG