use crate::control::json::Writer;
pub const EXIT_NODE_ROUTES: [&str; 2] = ["0.0.0.0/0", "::/0"];
#[derive(Clone, Copy)]
pub struct Hostinfo<'a> {
pub hostname: &'a str,
pub version: &'a str,
pub os: &'a str,
pub routable_ips: &'a [&'a str],
}
impl Hostinfo<'_> {
pub const DEFAULT_HOSTNAME: &'static str = "esp-gateway";
}
impl Default for Hostinfo<'_> {
fn default() -> Self {
Self {
hostname: Self::DEFAULT_HOSTNAME,
version: concat!("esp-gateway-", env!("CARGO_PKG_VERSION")),
os: "esp32",
routable_ips: &[],
}
}
}
impl Hostinfo<'_> {
pub fn write(&self, writer: &mut Writer<'_>) {
writer
.begin_object()
.field_str("IPNVersion", self.version)
.field_str("OS", self.os)
.field_str("Hostname", self.hostname);
if !self.routable_ips.is_empty() {
writer.key("RoutableIPs").begin_array();
for route in self.routable_ips {
writer.str(route);
}
writer.end_array();
}
writer.key("Services").begin_array().end_array();
writer.end_object();
}
}
#[cfg(test)]
mod tests {
use super::*;
fn rendered<'a>(buffer: &'a mut [u8], hostinfo: &Hostinfo<'_>) -> &'a str {
let mut writer = Writer::new(buffer);
hostinfo.write(&mut writer);
core::str::from_utf8(writer.finish().unwrap()).unwrap()
}
#[test]
fn a_default_hostinfo_advertises_no_routes() {
let mut buffer = [0u8; 256];
let text = rendered(&mut buffer, &Hostinfo::default());
assert!(text.contains(r#""OS":"esp32""#));
assert!(text.contains(r#""Hostname":"esp-gateway""#));
assert!(text.contains(r#""IPNVersion":"esp-gateway-"#));
assert!(text.contains(r#""Services":[]"#));
assert!(
!text.contains("RoutableIPs"),
"a node that is not an exit node must not claim routes"
);
}
#[test]
fn an_exit_node_advertises_both_default_routes() {
let mut buffer = [0u8; 256];
let hostinfo = Hostinfo {
routable_ips: &EXIT_NODE_ROUTES,
..Default::default()
};
let text = rendered(&mut buffer, &hostinfo);
assert!(
text.contains(r#""RoutableIPs":["0.0.0.0/0","::/0"]"#),
"{text}"
);
}
#[test]
fn a_hostname_with_a_quote_in_it_cannot_break_the_document() {
let mut buffer = [0u8; 256];
let hostinfo = Hostinfo {
hostname: r#"evil","OS":"linux"#,
..Default::default()
};
let text = rendered(&mut buffer, &hostinfo);
assert!(text.contains(r#""OS":"esp32""#));
assert!(!text.contains(r#""OS":"linux""#), "injection: {text}");
}
}