liminal-server 0.25.4

Standalone server for the liminal messaging bus
Documentation
use crate::config::types::{LysConfig, ServerConfig};
use std::collections::BTreeSet;

pub(super) fn validate_outbound_links(config: &ServerConfig, errors: &mut Vec<String>) {
    let Some(lys) = config.auth.as_ref().and_then(|auth| auth.lys.as_ref()) else {
        return;
    };
    let Some(machine) = &lys.machine else {
        return;
    };
    let mut seen = BTreeSet::new();
    for (index, link) in machine.links.iter().enumerate() {
        let field = format!("auth.lys.machine.links[{index}] link '{}'", link.link_id);
        if link.link_id.is_empty()
            || link
                .link_id
                .chars()
                .any(|c| c.is_whitespace() || c.is_control())
        {
            errors.push(format!("{field}.link_id: not an identifier"));
        }
        if !seen.insert(&link.link_id) {
            errors.push(format!("{field}: duplicate link_id '{}'", link.link_id));
        }
        match machine.machines.get(&link.peer_holder) {
            None => errors.push(format!(
                "{field}: peer_holder '{}' has no recorded machine key",
                link.peer_holder
            )),
            Some(key) => {
                let identity = hex::decode(key)
                    .ok()
                    .and_then(|bytes| <[u8; 32]>::try_from(bytes).ok())
                    .map(crate::link_handshake::MachineIdentity::new);
                if identity.is_none_or(|identity| identity.x25519_public().is_err()) {
                    errors.push(format!(
                        "{field}: peer_holder '{}' has an invalid recorded machine key",
                        link.peer_holder
                    ));
                }
            }
        }
        if let Some(address) = &link.peer_address {
            let valid = address.rsplit_once(':').is_some_and(|(host, port)| {
                !host.is_empty()
                    && !host.chars().any(|c| c.is_whitespace() || c.is_control())
                    && port.parse::<u16>().is_ok_and(|port| port != 0)
            });
            if !valid {
                errors.push(format!(
                    "{field}.peer_address: expected host:port with a nonzero port"
                ));
            }
        }
        for prefix in &link.exports {
            if prefix == "liminal" || prefix.starts_with("liminal.") {
                errors.push(format!(
                    "{field}: exports '{prefix}' names the reserved 'liminal.' namespace"
                ));
                continue;
            }
            if crate::auth_lys::SubjectBinding::new(
                lys.network_id.clone(),
                prefix.clone(),
                prefix.clone(),
            )
            .is_err()
            {
                errors.push(format!(
                    "{field}: exports '{prefix}' is not a placed subject name"
                ));
                continue;
            }
            let mut placed = false;
            let mut durable = false;
            for channel in &config.channels {
                if reaches(prefix, &channel.name) {
                    placed = true;
                    if channel.durable {
                        durable = true;
                    } else {
                        errors.push(format!(
                            "{field}: exports '{prefix}' names non-durable channel '{}'",
                            channel.name
                        ));
                    }
                }
            }
            if durable {
                continue;
            }
            let subjects = service_subjects_reached(lys, prefix);
            if subjects.is_empty() && !placed {
                errors.push(format!(
                    "{field}: exports '{prefix}' names no placed subject"
                ));
            }
            for subject in subjects {
                errors.push(format!(
                    "{field}: exports '{prefix}' names service subject '{subject}', which is not a stream"
                ));
            }
        }
    }
}

fn service_subjects_reached(lys: &LysConfig, prefix: &str) -> BTreeSet<String> {
    let mut subjects = BTreeSet::new();
    for root in lys.templates.iter().flat_map(|template| &template.subjects) {
        subjects.extend(reached_family(prefix, root));
    }
    if lys.services.is_some() {
        for kind in crate::services::ServiceKind::ALL {
            subjects.extend(reached_shape(prefix, kind.subject_shape()));
        }
    }
    if lys.broker.is_some() {
        for subject in [
            "net.broker.register",
            "net.broker.lookup",
            "net.broker.punch",
        ] {
            if reaches(prefix, subject) {
                subjects.insert(subject.to_owned());
            }
        }
    }
    if lys.relay.as_ref().is_some_and(|relay| relay.enabled) {
        subjects.extend(reached_family(prefix, "net.relay"));
    }
    if !lys.templates.is_empty() {
        for verb in ["list", "create", "deploy", "hold", "wake", "retire"] {
            let subject = format!("services.{verb}");
            if reaches(prefix, &subject) {
                subjects.insert(subject);
            }
        }
    }
    subjects
}

fn reached_family(prefix: &str, root: &str) -> Option<String> {
    if reaches(prefix, root) {
        return Some(root.to_owned());
    }
    // Templates serve exactly one instance segment below each configured root.
    prefix
        .strip_prefix(root)?
        .strip_prefix('.')
        .filter(|name| super::super::templates::is_segment(name))
        .map(|_| prefix.to_owned())
}

fn reached_shape(prefix: &str, shape: &str) -> Option<String> {
    let mut supplied = prefix.split('.');
    let mut resolved = Vec::new();
    for part in shape.split('.') {
        match supplied.next() {
            Some(segment)
                if segment == part
                    || (part.starts_with('<') && super::super::templates::is_segment(segment)) =>
            {
                resolved.push(segment);
            }
            Some(_) => return None,
            None => resolved.push(part),
        }
    }
    if supplied.next().is_some() {
        return None;
    }
    Some(resolved.join("."))
}

fn reaches(prefix: &str, subject: &str) -> bool {
    subject == prefix
        || subject
            .strip_prefix(prefix)
            .is_some_and(|rest| rest.starts_with('.'))
}