The Rust SDK implements the NATS contract of Stellar Control for drivers, transports, gateways and output connectors. A component implements a trait that says what it does; the SDK handles registration, key pair and credentials, heartbeats, control verbs, bindings, JetStream consumers, deadlines, telecommand events and the data plane. It also carries the protocols flight links need: CCSDS framing, COP-1, CFDP, CSP, KISS and CAN frames.
Crates#
| Crate | Path | Content |
|---|---|---|
stellar-sdk | sdk/rust/sdk | Traits, runners, session, protocol helpers. Re-exports the contract as stellar_sdk::contract. |
stellar-contract | sdk/rust/contract | The messages of the contract (serde and JSON Schema): Registration, Bindings, SemanticTc, TcEvent, RawSample, Sample, RunEvent, AlarmEvent, Pass, Transfer… and the header constants. See the NATS contract reference. |
stellar-common | sdk/rust/common | Shared types: TcId, RunId, ConfigHash (id), Timestamp and Duration (time), units and quantities (unit), subject builders and Token (subject), message headers (meta), calibration curves, file checksums, the global configuration (config). |
The crates are not published on crates.io yet: depend on them by path or Git. Every example
under examples/rust depends on stellar-sdk only (plus stellar-common for time), which the
layering check of the repository enforces.
[dependencies]
stellar-sdk = { path = "../stellar-mcs/sdk/rust/sdk" }
stellar-common = { path = "../stellar-mcs/sdk/rust/common" }
anyhow = "1"
serde_json = "1"
tokio = { version = "1", features = ["rt-multi-thread", "macros", "signal"] }
tokio-util = "0.7"Traits and runners#
| Component | Trait | Run from main | Run in your runtime |
|---|---|---|---|
| Driver | Driver | driver_main(driver, default_instance) | run_driver(Arc<D>, &Options, CancellationToken) |
| Transport | Transport | transport_main(transport, default_instance) | run_transport(Arc<T>, &Options, CancellationToken) |
| Gateway | Gateway | — | run_gateway(Arc<G>, &Options, Option<Arc<dyn Rpc>>, CancellationToken) |
| Output connector | Connector | — | run_connector(Arc<C>, &Options, Option<Arc<dyn Rpc>>, CancellationToken) |
The *_main functions read the settings from the environment, start a Tokio runtime and stop
on Ctrl-C. The run_* functions register the component and serve it until the token is
cancelled; they fail when the instance cannot register or its metrics endpoint cannot start.
Driver#
| Method | Required | Role |
|---|---|---|
software() -> (String, String) | yes | Software name and version |
capabilities() -> DriverCapabilities | yes | codec, catalogue (name@requirement), telecommands, measures, optional output and params_schema |
encode(&SemanticTc) -> Result<Vec<u8>, String> | yes | Frame or unit of a telecommand; an error gives ENCODE_FAILED |
decode(&[u8]) -> Result<Vec<RawSample>, String> | yes | Raw values of a frame or unit |
encode_with(&SemanticTc, &LinkContext) | no, calls encode | Encoding with the link parameters |
decode_with(&[u8], &LinkContext) | no, calls decode | Decoding with the link parameters |
echo(&SemanticTc, &[u8]) -> Option<bool> | no, None | On-board echo of a telecommand |
chunks(&[u8]) -> Vec<FileChunk> | no, none | File chunks of a frame |
decode_file(file_type, content) -> Option<Result<Vec<RawSample>, String>> | no, None | Samples of a verified file |
cfdp(target) -> Option<CfdpConfig> | no, None | CFDP entity of a target |
cfdp_frame(target, pdu) | no, the PDU | Frame of a CFDP PDU |
cfdp_pdus(frame) -> Vec<Vec<u8>> | no, none | CFDP PDUs of a telemetry frame |
cfdp_file_name(target, file_id, file_type) -> String | no, the file id | On-board name of a file |
See Writing a Driver.
Transport#
| Method | Required | Role |
|---|---|---|
software() | yes | Software name and version |
capabilities() -> TransportCapabilities | yes | input, output, optional params_schema |
wrap(unit, &LinkContext) -> Result<Vec<Vec<u8>>, String> | yes | Frames of a unit, in order |
unwrap(frame, &LinkContext) -> Result<Vec<Vec<u8>>, String> | yes | Units a frame completes |
cop1(&LinkContext) -> Option<Cop1Config> | no, None | COP-1 settings of a link |
clcw(frame, &LinkContext) -> Option<Clcw> | no, None | CLCW of a frame received |
See Writing a Transport.
Gateway#
| Method | Required | Role |
|---|---|---|
software() | yes | Software name and version |
capabilities() -> GatewayCapabilities | yes | tags, optional uplink and downlink (LinkCapability {link_type, rate_bps}) |
send(target, frame) -> impl Future<Output = Result<(), String>> | yes | Send a frame: Ok gives SENT, Err gives SEND_FAILED |
send_with(target, frame, &LinkContext) | no, calls send | Send a frame knowing its context: environment, mode of the target and its parameters |
configure(&LinkContext) -> impl Future<Output = ()> | no, nothing | Called when a link is bound and each time its binding changes, such as a new mode |
link_available() -> bool | no, true | Link state reported in heartbeats |
receive(Downlink) -> impl Future<Output = anyhow::Result<()>> | no, never returns | Receive until the end; an error marks the gateway degraded |
Downlink::publish(target, frame, ground_time, deferred) publishes a raw frame,
Downlink::publish_segment(target, stream, seq, segment, ground_time) a stream segment; both
count the throughput. Downlink::client() gives the NATS client, Downlink::throughput() the
counters. See Writing a Gateway and its section
Modes and parameters.
Connector#
| Method | Required | Role |
|---|---|---|
software() | yes | Software name and version |
data() -> Vec<String> | no, every kind | Kinds of data it takes |
deliver(Delivered) -> impl Future<Output = Result<(), String>> | yes | Write or relay a message; an error has it delivered again after RETRY (5 s) |
Delivered carries kind, subject, id (stable), stream_sequence, published, headers
and payload. See Writing an Output Connector.
Rpc#
Control verbs of an instance besides the built-in ones (status, credentials, bindings,
reregister):
use stellar_sdk::Rpc;
struct Verbs;
impl Rpc for Verbs {
fn call(&self, verb: &str, request: &[u8]) -> Result<serde_json::Value, String> {
match verb {
"antenna" => Ok(serde_json::json!({"elevation": 42.0})),
_ => Err(format!("unknown verb `{verb}`")), // replied as {"error": …}
}
}
}Pass it to run_gateway or run_connector; it answers on
stellar.ctl.rpc.<kind>.<instance>.<verb>.
Configuration#
Options::from_env(default_instance) reads:
| Variable | Default | Field |
|---|---|---|
STELLAR_NATS_URL | nats://localhost:4222 | nats_url |
STELLAR_INSTANCE | default_instance | instance |
STELLAR_NATS_CREDENTIALS | none | credentials: bootstrap credentials file (nsc format), limited to registration and heartbeats |
STELLAR_NATS_CA | none | tls.ca: CA the server is checked against; TLS required |
STELLAR_NATS_CERT, STELLAR_NATS_KEY | none | tls: client certificate and key, for mTLS |
STELLAR_METRICS_ADDR | none | metrics_addr: Prometheus endpoint |
The other fields have fixed defaults you may change in code: heartbeat_period (1 s),
register_timeout (2 s) and metrics_period (5 s, throughput reports of a gateway).
Session#
Session::start(options, registration, link_probe, rpc, shutdown) is what the runners use: it
connects (with the bootstrap credentials, then with the JWT the reconciler issues, signing the
server nonce with a key pair generated at start), registers and retries until the reconciler
answers, starts the heartbeats and the control verbs. A session gives client(),
bindings() (a watch::Receiver<Bindings>), registration(), jwt(),
set_health(Health, reason) and shutdown(). Errors are SessionError: InvalidName,
Connect, Rejected (final) and Stopped.
Protocol helpers#
| Module | Content |
|---|---|
ccsds | SpacePacket (encode, decode), packets, idle_packet; TC Transfer Frames: TcChannel {scid, vcid}, tc_frame, read_tc_frame, TcFrame, crc16, MAX_TC_FRAME; TM frames: TmFrame, TmLayout; the CLCW: Clcw (encode, decode) |
cop1 | Fop (FOP-1: request, directive, clcw, tick, snapshot), Cop1Config, FopState, FopSnapshot, Directive, Output; Farm, a FARM-1 for boards and simulators |
cfdp | Entity (class 2: put, ask, receive, tick, take_pdus, take_events), CfdpConfig, TxId, Event, Outcome, ProxyPut, MemFilestore |
csp | Version (V1, V2), Header, Packet, crc32c, FLAG_CRC32, FLAG_RDP |
kiss | encode, Decoder (frames of a stream received in pieces) |
can | CanFrame (11-bit or 29-bit identifier, 0 to 8 octets): new, extended, encode, decode, bits |
cfp | CSP over CAN, CFP 1 and CFP 2 of libcsp: fragment, Sending, Reassembler, MAX_CFP1_DATA |
events | publish(client, target, event): a telecommand event with its de-duplication id |
The CFDP entity is independent of NATS: it runs on the fork of cfdp-rs 0.3.0 in vendor/cfdp-rs,
which fixes defects of the acknowledged mode (listed in vendor/cfdp-rs/FORK.md).
Metrics#
With STELLAR_METRICS_ADDR, drivers, transports and gateways serve /metrics (Prometheus) and
/healthz, every metric labelled by component and instance: the gauges
stellar_component_info and stellar_component_start_time_seconds, and by kind:
| Kind | Metrics |
|---|---|
| Driver | stellar_driver_frames_total (decoded, failed), stellar_driver_telecommands_total (encoded, failed, expired), stellar_driver_cfdp_pdus_total (sent, received) |
| Transport | stellar_transport_units_total (framed, failed, expired), stellar_transport_frames_total (unwrapped, failed), stellar_transport_cop1_frames_total (first, retransmitted), stellar_transport_cop1_alerts_total |
| Gateway | stellar_gateway_throughput_bps, stellar_gateway_bytes_total (by direction and target), stellar_gateway_uplink_frames_total (by outcome) |
Testing#
- Without NATS. Call the trait methods directly:
encode,decode,echo,wrap,unwrapare plain functions. The example drivers replay test vectors this way (examples/rust/drivers/platform-v3/tests/vectors.rsdrives the driver, theccsds-tctransport and aFopfrom the V(S) of each vector).cop1::Farmplays the board side of COP-1;cfdp::Entitywith aMemFilestoreplays either side of CFDP. - Gateways. Put the medium behind a trait:
bench-canhas aBustrait with a SocketCAN implementation and aMemoryBusfor tests. - With NATS. Tests that need a server run when
STELLAR_TEST_NATS_URLis set, against a throw-awaynats-server -js. - Dry runs. A simulated target plays a platform as a driver and a gateway, for procedures: see Simulated Targets.