Conceptually Candouble is very similar to Mountebank and we've tried to use the same terminology. It might be worthwhile to read the following pages on the Mountebank site:
The central concept is a so called imposter that is attached to a CAN port. The imposter has a list of stubs that specify how the imposter responds to incoming messages. In addition, the imposter can record incoming messages, and these are available for inspection via Candouble's Web API.
A stub has a list of predicates and a list of responses, e.g.
{
"predicates": [
{ "eq": { "id": "0x0101" }
],
"responses": [
{ "id": "0x01", "data": [ "0x17" ] },
{ "id": "0x01", "data": [ "0x17", "0x20" ] }
]
}
When all predicates match an incoming message a response is sent. If multiple responses are defined, Candouble will cycle through the list of responses.
In the example above, when a message is received that has 0x101 as its id then
Candouble will respond with a message with id 0x01 and 0x17 as data. (Given
that this is CAN, a response can be up to 8 bytes long.) When a second message
matching the predicate is received Candouble will send the second response, a
message with 0x17, 0x20 as data bytes.
Currently, Candouble supports two predicate types only. (Therefore it's currently not too useful to define multiple predicates for one stub.)
{ "eq": { "id": "0x0101" }
{ "msg": { "id": "0x0101", "data": ["*", "0x02"] }
The eq type makes it possible to match on message id. The msg type allows to
match on the id and data bytes. An asterisk can be used to match any value.
Responses are sent as defined. A _behaviors attribute can be added to the
response definition. It is not sent but defines how the stub will send the
response. Multiple behaviors can be combined.
Instructs the stub to wait for a specified amount of time, in milliseconds, before sending the response, e.g.
{ "id": "0x01", "data": [ "0x17" ], "_behaviors": [ { "wait": 50 } ] }
Instructs the stub to send a response a specified number of times before moving on to the next response in the list, e.g.
{
"predicates": [
{ "eq": { "id": "0x01" }
],
"responses": [
{ "id": "0xFF", "data": [ ], "_behaviors": [ { "repeat": 2 } ] },
{ "id": "0x02", "data": [ ] }
]
}
In this case the stub will respond to a sequence of incoming messages that have 0x01 as their id with a sequence of messages that have the following ids, in this order: 0xFF, 0xFF, 0x02, 0xFF, 0xFF, ...
Instructs the stub not to send the response. Note that some essential fields need to be defined even though they will not be used, e.g.
"responses": [
{ "id": "0x01", "data": [], "_behaviors": [ { "drop": true } ] },
{ "id": "0x02", "data": [] }
]
The stub will basically ignore the first matching message and then respond with a message with id 0x02 to the second matching message.
When a response has the concat behavior flag set, the following response will also be sent. This means that receiving a single message can result in the stub sending multiple messages, e.g.
"responses": [
{ "id": "0x01", "data": [], "_behaviors": [ { "concat": true } ] },
{ "id": "0x02", "data": [], "_behaviors": [ ] }
]
Here the imposter will respond to single matching incoming message with a sequence of two response messages.
Note that when all defined responses have the concat flag set, then this would result in an endless stream of responses. Due to the way how Candouble is implemented, no response is sent and the imposter will hang in an endless loop.
The concept of an imposter is borrowed from Mountebank. In a nutshell, an imposter is a collection of stubs that are active for a given CAN port.
At the moment Candouble only supports one CAN port, and its id is 0.
An imposter specifies the port id and a list of stubs, e.g.
{
"id": 0,
"stubs": [
{
"predicates": [
{ "eq": { "id": "0x0101" } },
],
"responses": [
{ "id": "0x0102", "data": ["0xCA", "0xFE"] }
]
}
]
}
The stubs are evaluated in the order they are defined in. The first stub that has a matching predicate will generate the response.
The normal way to interact with Candouble is via its web API. It allows posting and retrieving of stubs. (An alternative is to specify files containing imposter definitions when starting Candouble.)
Imposter definitions can be posted to the /imposters endpoint, e.g.
curl -i -X POST -H 'Content-Type: application/json' http://localhost:8080/imposters ↩
--data '{ "id": 0, "stubs": [ { "predicates": [{ "eq": { "id": "0x01" } }], "responses": [{ "id": "0x02", "data": [ "0x01" ] }] } ] }'
Unless something goes wrong, the API should respond with status code 201 CREATED. If an imposter with the given id exists already, that imposter will be
replaced. In that case the response is 200 OK.
Imposters can be retrieved by their CAN port id, e.g.
curl -i http://localhost:8080/imposters/0
To allow following REST principles strictly, knowledge of this URL format is
not actually necessary. The response to POSTing an imposter includes a
Location header that contains the URL for the imposter. That said, you can
safely use URLs with the format documented here.
When the recordMessages field is set to true, the imposter records all
incoming messages and these are then included in the response, e.g.
{
"id": 0,
"recordMessages": true,
"stubs": [
{
"predicates": [
{ "eq": { "id": "0x1" } }
],
"responses": [
{ "id": "0x201", "data": [ "0x01" ], "_behaviors": null
}
]
}
],
"messages": [
{
"id": 1,
"type": 1,
"length": 2,
"data": [ 202, 254, 0, 0, 0, 0, 0, 0 ]
}
]
}
Note that the data field of recorded messages always contains eight values.
Also note that recording of messages is turned off by default, because this
effectively represents a memory leak for long-running imposters.
A list of all imposters can also be retrieved, e.g.
curl -i http://localhost:8080/imposters
The list of imposters is wrapped in a top-level object, e.g.
{
"imposters": [
{
"id": 0,
"stubs": [
...
]
}
]
}
An imposter can be removed using the DELETE HTTP verb, e.g.
curl -i -X DELETE http://localhost:8080/imposters/0
Note that the API does not return the deleted imposter and therefore responds
with status code 204 NO CONTENT.
If you're on a Mac and have the PCAN adaptor attached, you should run the
application with the pcan feature. For it to find the native library you have
to set the dynamic library loading path:
export LD_LIBRARY_PATH=./lib/PCBUSB
cargo run --no-default-features --features pcan tests/it_imposter.json
If you're not on a Mac then you can run the unit tests, but there are no adaptors yet for CAN hardware.