alpine-protocol-sdk 0.1.14

High-level SDK on top of the ALPINE protocol layer.
Documentation
# 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. Use `DiscoveryClient` to broadcast a request and inspect the returned
   `DiscoveryOutcome` for identity, capability, and server nonce information.
2. Call `AlpineClient::connect` with the discovered identity, capability set,
   and a credential pair; the SDK spins up the transport plus the keep-alive task.
3. Call `AlpineClient::start_stream`, pass a `StreamProfile`, and track the
   returned `config_id`.
4. Use `send_frame` to push encoded `FrameEnvelope`s or `send_control` for
   control envelopes.
5. 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

```ignore
use alpine_protocol_sdk::{AlpineClient, DiscoveryClient, DiscoveryClientOptions};
use alpine_protocol_rs::{
    crypto::identity::NodeCredentials,
    messages::{CapabilitySet, DeviceIdentity},
    profile::StreamProfile,
};
use std::net::{IpAddr, SocketAddr};

#[tokio::main]
async fn main() -> Result<(), alpine_protocol_sdk::AlpineSdkError> {
    let discovery = DiscoveryClient::new(DiscoveryClientOptions::new(
        SocketAddr::new(IpAddr::V4([0, 0, 0, 0].into()), 0),
        SocketAddr::new(IpAddr::V4([192, 168, 1, 42].into()), 5555),
        std::time::Duration::from_secs(3),
    ))?;
    let outcome = discovery.discover(&["alpine-control".to_string()])?;
    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([0, 0, 0, 0].into()), 0),
        remote,
        identity,
        capabilities,
        credentials,
    )
    .await?;
    let config_id = client.start_stream(StreamProfile::auto())?;
    println!("Streaming with config id {}", config_id);
    Ok(())
}
```

Every exported module in this crate has `///` documentation so the generated
docs on docs.rs describe the same lifecycle described here.