Stellar ControlMission control · by Stellar Systems v0.1.0

SDKs and Integration

Rust SDK

The stellar-sdk crate: traits and runners of drivers, transports, gateways and connectors, and protocol helpers.

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#

CratePathContent
stellar-sdksdk/rust/sdkTraits, runners, session, protocol helpers. Re-exports the contract as stellar_sdk::contract.
stellar-contractsdk/rust/contractThe 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-commonsdk/rust/commonShared 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.

TOML
[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#

ComponentTraitRun from mainRun in your runtime
DriverDriverdriver_main(driver, default_instance)run_driver(Arc<D>, &Options, CancellationToken)
TransportTransporttransport_main(transport, default_instance)run_transport(Arc<T>, &Options, CancellationToken)
GatewayGateway—run_gateway(Arc<G>, &Options, Option<Arc<dyn Rpc>>, CancellationToken)
Output connectorConnector—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#

MethodRequiredRole
software() -> (String, String)yesSoftware name and version
capabilities() -> DriverCapabilitiesyescodec, catalogue (name@requirement), telecommands, measures, optional output and params_schema
encode(&SemanticTc) -> Result<Vec<u8>, String>yesFrame or unit of a telecommand; an error gives ENCODE_FAILED
decode(&[u8]) -> Result<Vec<RawSample>, String>yesRaw values of a frame or unit
encode_with(&SemanticTc, &LinkContext)no, calls encodeEncoding with the link parameters
decode_with(&[u8], &LinkContext)no, calls decodeDecoding with the link parameters
echo(&SemanticTc, &[u8]) -> Option<bool>no, NoneOn-board echo of a telecommand
chunks(&[u8]) -> Vec<FileChunk>no, noneFile chunks of a frame
decode_file(file_type, content) -> Option<Result<Vec<RawSample>, String>>no, NoneSamples of a verified file
cfdp(target) -> Option<CfdpConfig>no, NoneCFDP entity of a target
cfdp_frame(target, pdu)no, the PDUFrame of a CFDP PDU
cfdp_pdus(frame) -> Vec<Vec<u8>>no, noneCFDP PDUs of a telemetry frame
cfdp_file_name(target, file_id, file_type) -> Stringno, the file idOn-board name of a file

See Writing a Driver.

Transport#

MethodRequiredRole
software()yesSoftware name and version
capabilities() -> TransportCapabilitiesyesinput, output, optional params_schema
wrap(unit, &LinkContext) -> Result<Vec<Vec<u8>>, String>yesFrames of a unit, in order
unwrap(frame, &LinkContext) -> Result<Vec<Vec<u8>>, String>yesUnits a frame completes
cop1(&LinkContext) -> Option<Cop1Config>no, NoneCOP-1 settings of a link
clcw(frame, &LinkContext) -> Option<Clcw>no, NoneCLCW of a frame received

See Writing a Transport.

Gateway#

MethodRequiredRole
software()yesSoftware name and version
capabilities() -> GatewayCapabilitiesyestags, optional uplink and downlink (LinkCapability {link_type, rate_bps})
send(target, frame) -> impl Future<Output = Result<(), String>>yesSend a frame: Ok gives SENT, Err gives SEND_FAILED
send_with(target, frame, &LinkContext)no, calls sendSend a frame knowing its context: environment, mode of the target and its parameters
configure(&LinkContext) -> impl Future<Output = ()>no, nothingCalled when a link is bound and each time its binding changes, such as a new mode
link_available() -> boolno, trueLink state reported in heartbeats
receive(Downlink) -> impl Future<Output = anyhow::Result<()>>no, never returnsReceive 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#

MethodRequiredRole
software()yesSoftware name and version
data() -> Vec<String>no, every kindKinds of data it takes
deliver(Delivered) -> impl Future<Output = Result<(), String>>yesWrite 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):

Rust
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:

VariableDefaultField
STELLAR_NATS_URLnats://localhost:4222nats_url
STELLAR_INSTANCEdefault_instanceinstance
STELLAR_NATS_CREDENTIALSnonecredentials: bootstrap credentials file (nsc format), limited to registration and heartbeats
STELLAR_NATS_CAnonetls.ca: CA the server is checked against; TLS required
STELLAR_NATS_CERT, STELLAR_NATS_KEYnonetls: client certificate and key, for mTLS
STELLAR_METRICS_ADDRnonemetrics_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#

ModuleContent
ccsdsSpacePacket (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)
cop1Fop (FOP-1: request, directive, clcw, tick, snapshot), Cop1Config, FopState, FopSnapshot, Directive, Output; Farm, a FARM-1 for boards and simulators
cfdpEntity (class 2: put, ask, receive, tick, take_pdus, take_events), CfdpConfig, TxId, Event, Outcome, ProxyPut, MemFilestore
cspVersion (V1, V2), Header, Packet, crc32c, FLAG_CRC32, FLAG_RDP
kissencode, Decoder (frames of a stream received in pieces)
canCanFrame (11-bit or 29-bit identifier, 0 to 8 octets): new, extended, encode, decode, bits
cfpCSP over CAN, CFP 1 and CFP 2 of libcsp: fragment, Sending, Reassembler, MAX_CFP1_DATA
eventspublish(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:

KindMetrics
Driverstellar_driver_frames_total (decoded, failed), stellar_driver_telecommands_total (encoded, failed, expired), stellar_driver_cfdp_pdus_total (sent, received)
Transportstellar_transport_units_total (framed, failed, expired), stellar_transport_frames_total (unwrapped, failed), stellar_transport_cop1_frames_total (first, retransmitted), stellar_transport_cop1_alerts_total
Gatewaystellar_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, unwrap are plain functions. The example drivers replay test vectors this way (examples/rust/drivers/platform-v3/tests/vectors.rs drives the driver, the ccsds-tc transport and a Fop from the V(S) of each vector). cop1::Farm plays the board side of COP-1; cfdp::Entity with a MemFilestore plays either side of CFDP.
  • Gateways. Put the medium behind a trait: bench-can has a Bus trait with a SocketCAN implementation and a MemoryBus for tests.
  • With NATS. Tests that need a server run when STELLAR_TEST_NATS_URL is set, against a throw-away nats-server -js.
  • Dry runs. A simulated target plays a platform as a driver and a gateway, for procedures: see Simulated Targets.

Stellar Control · v0.1.0

↑↓ to moveEnter to open