Skip to main content

running_process/broker/
get_session_token_dispatch.rs

1//! `GetSessionToken` RPC dispatch (zackees/soldr#2361 Phase 2, #2363).
2//!
3//! Mirrors [`super::get_http_endpoint_dispatch`]'s shape exactly: this
4//! slice exposes the typed request/response handler over
5//! [`super::server::session_token::SessionTokenAuthority`] so a later
6//! slice can wire it into the broker's control-channel connection loop.
7//! See `broker_v2_control.proto`'s `GetSessionTokenRequest` doc comment
8//! for why this lives on the v2 control channel rather than the frozen
9//! v1 `Hello`/`Negotiated` envelope.
10
11use prost::Message;
12
13use super::protocol_v2::{GetSessionTokenRequest, GetSessionTokenResponse};
14use super::server::session_token::SessionTokenAuthority;
15
16/// Errors from [`decode_request_and_dispatch`].
17#[derive(Debug, thiserror::Error)]
18pub enum GetSessionTokenDispatchError {
19    /// The incoming frame body did not decode as `GetSessionTokenRequest`.
20    #[error("decode GetSessionTokenRequest: {0}")]
21    Decode(#[from] prost::DecodeError),
22
23    /// Encoding the response failed.
24    #[error("encode GetSessionTokenResponse: {0}")]
25    Encode(#[from] prost::EncodeError),
26}
27
28/// Decode an incoming `GetSessionTokenRequest` frame body and produce a
29/// serialized `GetSessionTokenResponse` body the connection loop can write
30/// back via `protocol::write_frame`.
31///
32/// Read-only lookup against `authority` -- this never mints or registers a
33/// token itself. Minting happens once, at daemon launch
34/// (`HelloRouter::launch_backend` calling `SessionTokenAuthority::register_daemon`);
35/// this RPC only answers "what is it right now" for a caller that already
36/// knows the daemon exists (e.g. from a prior `Hello` negotiation).
37pub fn decode_request_and_dispatch(
38    request_body: &[u8],
39    authority: &SessionTokenAuthority,
40) -> Result<Vec<u8>, GetSessionTokenDispatchError> {
41    let request = GetSessionTokenRequest::decode(request_body)?;
42    let response = match authority.composed_token_for(&request.daemon_id) {
43        Some(session_token) => GetSessionTokenResponse {
44            found: true,
45            session_token,
46        },
47        None => GetSessionTokenResponse {
48            found: false,
49            session_token: Vec::new(),
50        },
51    };
52    let mut body = Vec::with_capacity(response.encoded_len());
53    response.encode(&mut body)?;
54    Ok(body)
55}
56
57#[cfg(test)]
58mod tests {
59    use super::*;
60    use crate::broker::server::session_token::TokenHalf;
61
62    fn authority_with_one_daemon(daemon_id: &str) -> SessionTokenAuthority {
63        let mut authority = SessionTokenAuthority::with_broker_token(TokenHalf::from_bytes(
64            [0xAB; crate::broker::server::session_token::SESSION_TOKEN_HALF_BYTES],
65        ));
66        authority
67            .register_daemon(daemon_id.to_string())
68            .expect("register_daemon");
69        authority
70    }
71
72    fn encode_request(daemon_id: &str) -> Vec<u8> {
73        let req = GetSessionTokenRequest {
74            daemon_id: daemon_id.to_string(),
75        };
76        let mut body = Vec::with_capacity(req.encoded_len());
77        req.encode(&mut body).expect("encode request");
78        body
79    }
80
81    #[test]
82    fn dispatch_returns_composed_token_for_a_registered_daemon() {
83        let authority = authority_with_one_daemon("zccache");
84        let body = encode_request("zccache");
85
86        let resp_body = decode_request_and_dispatch(&body, &authority).expect("dispatch succeeds");
87        let resp = GetSessionTokenResponse::decode(resp_body.as_slice()).expect("decode response");
88
89        assert!(resp.found);
90        assert_eq!(
91            resp.session_token,
92            authority.composed_token_for("zccache").unwrap()
93        );
94    }
95
96    #[test]
97    fn dispatch_reports_not_found_for_an_unregistered_daemon() {
98        let authority = authority_with_one_daemon("zccache");
99        let body = encode_request("some-other-daemon");
100
101        let resp_body = decode_request_and_dispatch(&body, &authority).expect("dispatch succeeds");
102        let resp = GetSessionTokenResponse::decode(resp_body.as_slice()).expect("decode response");
103
104        assert!(!resp.found);
105        assert!(resp.session_token.is_empty());
106    }
107
108    #[test]
109    fn dispatch_rejects_malformed_request_body() {
110        let authority = authority_with_one_daemon("zccache");
111        let err = decode_request_and_dispatch(&[0xFF; 4], &authority)
112            .expect_err("malformed request body should be rejected");
113        match err {
114            GetSessionTokenDispatchError::Decode(_) => {}
115            other => panic!("expected Decode error, got: {other:?}"),
116        }
117    }
118}