Skip to main content

macula_rust/record/
station_endpoint.rs

1//! Station endpoints: where a station is dialled, signed by the station and
2//! stored under its node_id's station endpoint key.
3
4use crate::cbor::Value;
5
6use super::{entry, malformed, unsigned, Record, RecordError, RecordType};
7
8/// A station endpoint's optional fields: the hosts it is dialled at, left
9/// out when none; its ALPN, left out when empty; `ttl_ms`, 0 for the default
10/// and maximum, 5 minutes.
11#[derive(Debug, Clone, Default, PartialEq, Eq)]
12pub struct StationEndpointOptions {
13    pub host_advertised: Vec<String>,
14    pub alpn: String,
15    pub ttl_ms: u64,
16}
17
18/// An unsigned record of a station's dialable endpoint. A QUIC port of 0 is
19/// refused.
20pub fn new_station_endpoint(
21    quic_port: u16,
22    opts: &StationEndpointOptions,
23) -> Result<Record, RecordError> {
24    if quic_port == 0 {
25        return Err(RecordError::InvalidPort);
26    }
27    let mut entries = vec![entry("quic_port", Value::Int(i128::from(quic_port)))];
28    if !opts.host_advertised.is_empty() {
29        entries.push(entry(
30            "host_advertised",
31            Value::List(
32                opts.host_advertised
33                    .iter()
34                    .map(|h| Value::Bytes(h.as_bytes().to_vec()))
35                    .collect(),
36            ),
37        ));
38    }
39    if !opts.alpn.is_empty() {
40        entries.push(entry("alpn", Value::text(opts.alpn.clone())));
41    }
42    Ok(unsigned(
43        RecordType::STATION_ENDPOINT,
44        Value::Map(entries),
45        opts.ttl_ms,
46    ))
47}
48
49/// A station endpoint's payload: its QUIC port, 0 when it carries none from
50/// 1 to 65535, and the hosts it is dialled at.
51#[derive(Debug, Clone, Default, PartialEq, Eq)]
52pub struct StationEndpoint {
53    pub quic_port: u16,
54    pub host_advertised: Vec<String>,
55}
56
57/// Reads a station endpoint's payload.
58pub fn read_station_endpoint(r: &Record) -> Result<StationEndpoint, RecordError> {
59    if r.record_type != RecordType::STATION_ENDPOINT {
60        return Err(malformed("not a station endpoint"));
61    }
62    let quic_port = match r.payload.get("quic_port") {
63        Some(Value::Int(n)) if (1..=65535).contains(n) => *n as u16,
64        _ => 0,
65    };
66    Ok(StationEndpoint {
67        quic_port,
68        host_advertised: host_list(r.payload.get("host_advertised")),
69    })
70}
71
72/// host_advertised as macula_record's host_list/1 reads it: a list of hosts
73/// or a single host, each as bytes or text.
74fn host_list(v: Option<&Value>) -> Vec<String> {
75    let items: Vec<&Value> = match v {
76        None => return Vec::new(),
77        Some(Value::List(items)) => items.iter().collect(),
78        Some(single) => vec![single],
79    };
80    items
81        .into_iter()
82        .filter_map(|item| match item {
83            Value::Bytes(b) => Some(String::from_utf8_lossy(b).into_owned()),
84            Value::Text(t) => Some(t.clone()),
85            _ => None,
86        })
87        .collect()
88}