ALPINE Rust SDK
alpine-protocol-sdk is a high-level wrapper around the published alpine-protocol-rs
protocol artifacts. It keeps discovery, handshake, and streaming lifecycles explicit so
application code can reason about each step without diving into the lower-level
protocol helpers.
When to use the SDK vs the protocol layer
- Protocol layer (
alpine-protocol-rs) gives you access to every message, frame, and handshake primitive and is useful if you already have a transport layer or want to implement a custom state machine. - SDK (
alpine-protocol-sdk) builds on top of the protocol layer and manages sockets, keep-alive, and profile lifetimes so you can focus on discovery → connect → streaming flows.
Quick lifecycle
- Fetch and cache the root-signed attesters bundle (e.g., from Ops) and build a trust view.
- Use the discovery runner (recommended) or
DiscoveryClient(low-level) to find devices. The runner performs unicast/broadcast/cached fallbacks and optional subnet scans. - Call
AlpineClient::connectwith the discovered identity, capability set, and a credential pair; the SDK spins up the transport plus the keep-alive task. - Call
AlpineClient::start_stream, pass aStreamProfile, and track the returnedconfig_id. - Use
send_frameto push encodedFrameEnvelopes orsend_controlfor control envelopes. - Call
AlpineClient::ping,status,health,identity, ormetadatato send the corresponding control command and receive typed replies when the device returns structured CBOR payloads.
Example
use alpine_protocol_sdk::{
AlpineClient,
DiscoveryRunOptions,
run_discovery_with_options,
TrustConfig,
load_or_fetch_trust_view,
};
use alpine_protocol_rs::{crypto::identity::NodeCredentials, messages::DeviceIdentity};
use std::net::{SocketAddr, IpAddr, Ipv4Addr};
#[tokio::main]
async fn main() -> Result<(), alpine_protocol_sdk::AlpineSdkError> {
let trust = TrustConfig::new("https://panel.y-link.no/attesters/latest".into())
.with_root_pubkey(alpine_protocol_sdk::parse_root_pubkey_base64(
"BASE64_ED25519_ROOT_PUBKEY",
)?);
let trust_view = load_or_fetch_trust_view(&trust).await?;
let mut opts = DiscoveryRunOptions::default();
opts.attester_registry = Some(trust_view.registry.clone());
let outcome = run_discovery_with_options(
SocketAddr::new(IpAddr::V4(Ipv4Addr::BROADCAST), 19455),
opts,
)
.await?;
let remote = SocketAddr::new(outcome.peer.ip(), outcome.peer.port());
let identity = DeviceIdentity {
device_id: outcome.reply.device_id.clone(),
manufacturer_id: outcome.reply.manufacturer_id.clone(),
model_id: outcome.reply.model_id.clone(),
hardware_rev: outcome.reply.hardware_rev.clone(),
firmware_rev: outcome.reply.firmware_rev.clone(),
};
let credentials = NodeCredentials::load("path/to/credentials")?;
let capabilities = outcome.reply.capabilities.clone();
let mut client = AlpineClient::connect(
SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0),
remote,
identity,
capabilities,
credentials,
)
.await?;
let config_id = client.start_stream(alpine_protocol_rs::profile::StreamProfile::auto())?;
println!("Streaming with config id {}", config_id);
Ok(())
}
Discovery runner behavior
The runner is designed to "just work" on most networks:
- Unicast (if a concrete target is provided).
- Broadcast on all IPv4 interfaces (default).
- Cached unicast fallbacks (if you provide cached targets).
- Optional multicast (
prefer_multicast = true). - Optional subnet scan (opt-in via
scan_subnets = true).
These behaviors are explicit in DiscoveryRunOptions; aggressive scanning is opt-in.
Discovery diagnostics
If you need per-attempt visibility (e.g., broadcast blocked vs unicast denied), use the report helpers:
run_discovery_with_reportrun_discovery_with_options_report
These return a DiscoveryRunReport with a result plus a list of DiscoveryAttempt
entries that include target, mode, local bind, and any error details observed.
Common discovery error hints:
discovery channel permission denied→ UDP send/recv blocked by OS policy or firewall.broadcast discovery blocked→ network policy prevents broadcast.multicast discovery unavailable→ multicast not permitted on this network.discovery timed out→ no replies observed before the timeout.
Discovery trust state:
- Use
DiscoveryOutcome::trust_state()to get a strictDeviceTrustStateenum instead of parsing the optional attestation error string. - Use
DiscoveryOutcome::require_trusted()to obtain aTrustedDiscoveryOutcomeor a clearAlpineSdkError::UntrustedDevice.
Discovery retries:
- Use
run_discovery_with_retryorrun_discovery_with_options_retrywith aDiscoveryRetryPolicyto add exponential backoff across discovery runs.
Trust bundle setup
To verify device identity attestations, you must supply a root public key and bundle URL. The SDK does not auto-load environment variables; your application should configure them.
Recommended variables for your app or CLI:
ALPINE_ATTESTERS_URLALPINE_ROOT_PUBKEY_B64
Control helper note
The ping, status, health, identity, and metadata helpers send a vendor
control command payload ({ "command": "status" }) via ControlOp::Vendor.
Ensure your device firmware responds to those vendor commands (not only get_status).
Standardized status probe
Use AlpineClient::probe_status() for a single canonical liveness + health check.
It runs ping, then status, and optionally health if status is unavailable.
The ProbeResult includes a normalized ProbeState (Online, Degraded, Offline)
along with per-step errors and round-trip timings.
Use ProbeResult::to_device_state(trust_state) to normalize probe + trust into a
DeviceState snapshot suitable for UI status badges.
Control options
Use AlpineClient::control_with_options with ControlOptions to add per-call
timeouts and retries. The retry policy uses exponential backoff capped by
backoff_max_ms.
Connect policy
Use connect_with_policy to enforce discovery → trust → handshake → first probe.
The default ConnectPolicy requires a trusted identity, probes after handshake,
and rejects Offline (and optionally Degraded) devices.
If you have a DiscoveryRunReport, use connect_with_policy_from_report to
include discovery attempt diagnostics in any discovery/probe failure.
LAN defaults and cache helpers
Use DiscoveryRunOptions::defaults_for_lan() for a safe LAN baseline.
Use discover_with_cache or discover_with_cache_report to try cached targets
before falling back to broadcast.
Every exported module in this crate has /// documentation so the generated
docs on docs.rs describe the same lifecycle described here.
Discovery dry run
Use discovery_dry_run to inspect the computed interfaces and broadcast targets
without sending any packets. This is useful when debugging permissions or NIC
selection issues.
Standard vs vendor status
If your device implements the standard control op, call
AlpineClient::status_standard() (uses ControlOp::GetStatus).
If your device only implements vendor commands, call status_vendor() or
status() (vendor command payload).
Safe client wrapper
SafeClient wraps an AlpineClient and enforces a probe before control or
stream calls. Use SafeClientOptions to require online or accept degraded.
Session guard
SessionGuard tracks idle time and lets you expire or close sessions after
an inactivity timeout.
Trust policy helper
Use enforce_trust_policy with TrustPolicy to apply strict or warn-only
trust behavior based on DeviceTrustState.
Logging helpers
Use init_pretty_logging() or init_json_logging() to set up a default
tracing subscriber with env-based filtering.
Troubleshooting
See sdk/docs/troubleshooting.md for common failure modes and debugging tips.
Common failure modes and remediation
- Discovery timed out: confirm the device is powered on, on the same subnet, and UDP is permitted.
- Broadcast or multicast blocked: switch to unicast or cached targets; ask network admin to allow UDP broadcast.
- Device untrusted: load a trusted attesters bundle and validate
ALPINE_ROOT_PUBKEY_B64. - Standard status rejected: use
status_vendor()orstatus()for vendor-only devices. - Incompatible protocol: upgrade/downgrade SDK or firmware to match
ALPINE_VERSION. - Unsupported environment: avoid WSL/Docker without host networking for UDP discovery/control.