alpine-protocol-sdk

Crates.ioalpine-protocol-sdk
lib.rsalpine-protocol-sdk
version0.2.4
created_at2025-11-30 12:55:35.261083+00
updated_at2026-01-15 17:09:22.463737+00
descriptionHigh-level SDK on top of the ALPINE protocol layer.
homepage
repository
max_upload_size
id1958196
size272,278
Herman Kristoffer Nerskogen Helle (kristofferous)

documentation

README

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

  1. Fetch and cache the root-signed attesters bundle (e.g., from Ops) and build a trust view.
  2. Use the discovery runner (recommended) or DiscoveryClient (low-level) to find devices. The runner performs unicast/broadcast/cached fallbacks and optional subnet scans.
  3. Call AlpineClient::connect with the discovered identity, capability set, and a credential pair; the SDK spins up the transport plus the keep-alive task.
  4. Call AlpineClient::start_stream, pass a StreamProfile, and track the returned config_id.
  5. Use send_frame to push encoded FrameEnvelopes or send_control for control envelopes.
  6. Call AlpineClient::ping, status, health, identity, or metadata to 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_report
  • run_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 strict DeviceTrustState enum instead of parsing the optional attestation error string.
  • Use DiscoveryOutcome::require_trusted() to obtain a TrustedDiscoveryOutcome or a clear AlpineSdkError::UntrustedDevice.

Discovery retries:

  • Use run_discovery_with_retry or run_discovery_with_options_retry with a DiscoveryRetryPolicy to 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_URL
  • ALPINE_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() or status() 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.
Commit count: 0

cargo fmt