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.