Crate interchange

source ·
Expand description

Implement a somewhat convenient and somewhat efficient way to perform RPC in an embedded context.

The approach is inspired by Go’s channels, with the restriction that there is a clear separation into a requester and a responder.

Requests may be canceled, which the responder should honour on a best-effort basis.

Example use cases

  • USB device interrupt handler performs low-level protocol details, hands off commands from the host to higher-level logic running in the idle thread. This higher-level logic need only understand clearly typed commands, as moduled by variants of a given Request enum.
  • trussed crypto service, responding to crypto request from apps across TrustZone for Cortex-M secure/non-secure boundaries.
  • Request to blink a few lights and reply on button press

Approach

It is assumed that all requests fit in a single Request enum, and that all responses fit in single Response enum. The Channel and Interchange structs allocate a single buffer in which either Request or Response fit and handle synchronization Both structures have const constructors, allowing them to be statically allocated.

An alternative approach would be to use two heapless Queues of length one each for response and requests. The advantage of our construction is to have only one static memory region in use.

#[derive(Clone, Debug, PartialEq)]
pub enum Request {
    This(u8, u32),
    That(i64),
}

#[derive(Clone, Debug, PartialEq)]
pub enum Response {
    Here(u8, u8, u8),
    There(i16),
}

static INTERCHANGE: Interchange<Request, Response, 1> = Interchange::new();

let (mut rq, mut rp) = INTERCHANGE.claim().unwrap();

assert!(rq.state() == State::Idle);

// happy path: no cancelation
let request = Request::This(1, 2);
assert!(rq.request(request).is_ok());

let request = rp.take_request().unwrap();
println!("rp got request: {:?}", request);

let response = Response::There(-1);
assert!(!rp.is_canceled());
assert!(rp.respond(response).is_ok());

let response = rq.take_response().unwrap();
println!("rq got response: {:?}", response);

// early cancelation path
assert!(rq.request(request).is_ok());

let request =  rq.cancel().unwrap().unwrap();
println!("responder could cancel: {:?}", request);

assert!(rp.take_request().is_none());
assert!(State::Idle == rq.state());

// late cancelation
assert!(rq.request(request).is_ok());
let request = rp.take_request().unwrap();

println!("responder could cancel: {:?}", &rq.cancel().unwrap().is_none());
assert!(rp.is_canceled());
assert!(rp.respond(response).is_err());
assert!(rp.acknowledge_cancel().is_ok());
assert!(State::Idle == rq.state());

// building into request buffer
impl Default for Request {
  fn default() -> Self {
    Request::That(0)
  }
}

rq.with_request_mut(|r| *r = Request::This(1,2)).unwrap() ;
assert!(rq.send_request().is_ok());
let request = rp.take_request().unwrap();
assert_eq!(request, Request::This(1, 2));
println!("rp got request: {:?}", request);

// building into response buffer
impl Default for Response {
  fn default() -> Self {
    Response::There(1)
  }
}

rp.with_response_mut(|r| *r = Response::Here(3,2,1)).unwrap();
assert!(rp.send_response().is_ok());
let response = rq.take_response().unwrap();
assert_eq!(response, Response::Here(3,2,1));

Structs

Enums

  • State of the RPC interchange