Skip to main content

client_respond/
client_respond.rs

1//! Client-side challenge->response (`broadcast_auth::respond` /
2//! `broadcast_auth::Authenticator`) — given a server's `WWW-Authenticate`
3//! challenge, compute the `Authorization` header value to answer it, for
4//! Basic, Digest, and Bearer.
5//!
6//! Self-contained and non-blocking: no socket, no server — just the client
7//! half of the handshake a real RTSP/HTTP client (e.g. `rtsp-runtime`,
8//! multimux's HTTP input adapters) would send.
9//!
10//! # Usage
11//!
12//! ```bash
13//! cargo run --example client_respond -p broadcast-auth
14//! ```
15
16use broadcast_auth::{Authenticator, Credentials, RequestContext, respond};
17
18fn main() {
19    // --- Basic (RFC 7617): a one-shot respond(), no session state needed.
20    let value = respond(
21        "Basic realm=\"cameras\"",
22        &RequestContext::new("GET", "/stream/media.m3u8"),
23        Credentials::new("admin", "hunter2"),
24    )
25    .expect("Basic responds to any challenge shape");
26    println!("[basic]  Authorization: {value}");
27    assert!(value.starts_with("Basic "));
28
29    // --- Digest (RFC 7616): parses the server's nonce/realm/qop out of the
30    // challenge, then computes HA1/HA2/response. Demonstrated with an
31    // Authenticator (not the one-shot respond()) since a real session reuses
32    // it across requests so the nonce count (`nc`) advances correctly.
33    let digest_challenge = "Digest realm=\"cameras\", \
34        nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", qop=\"auth\", algorithm=MD5";
35    let mut auth =
36        Authenticator::from_challenge(digest_challenge, Credentials::new("admin", "hunter2"))
37            .expect("challenge parses");
38    let value = auth
39        .authorization(&RequestContext::new(
40            "DESCRIBE",
41            "rtsp://camera.example.com/live",
42        ))
43        .expect("computes a Digest Authorization value");
44    println!("[digest] Authorization: {value}");
45    assert!(value.starts_with("Digest "));
46
47    // A second request on the same Authenticator advances `nc` — the
48    // computed value differs even though nothing else about the request
49    // changed (RFC 7616 §3.3 requires a fresh `nc` per request).
50    let second = auth
51        .authorization(&RequestContext::new(
52            "DESCRIBE",
53            "rtsp://camera.example.com/live",
54        ))
55        .expect("computes a second Digest Authorization value");
56    assert_ne!(value, second, "nc must advance across requests");
57    println!("[digest] Authorization (2nd request, nc advanced): {second}");
58
59    // --- Bearer (RFC 6750): no challenge round-trip needed at all — the
60    // challenge value is ignored, the token is sent verbatim.
61    let value = respond(
62        "ignored — Bearer needs no challenge round-trip",
63        &RequestContext::new("GET", "/stream/media.m3u8"),
64        Credentials::bearer("mytoken123"),
65    )
66    .expect("Bearer always responds");
67    println!("[bearer] Authorization: {value}");
68    assert_eq!(value, "Bearer mytoken123");
69}