super_cereal 0.1.0

Proxy a serial port over the network using RustDDS (UART over LAN)
use serde::{Deserialize, Serialize};

pub const CHUNK_SIZE: usize = 4096;
pub const TOPIC_PREFIX: &str = "super_cereal";
pub const TYPE_NAME: &str = "SerialChunk";
pub const DEFAULT_HISTORY_DEPTH: i32 = 256;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SerialChunk {
    pub seq: u64,
    pub data: Vec<u8>,
}

impl SerialChunk {
    pub fn new(seq: u64, data: Vec<u8>) -> Self {
        Self { seq, data }
    }
}

pub fn up_topic_name(channel: &str) -> String {
    format!("{TOPIC_PREFIX}/{channel}/up")
}

pub fn down_topic_name(channel: &str) -> String {
    format!("{TOPIC_PREFIX}/{channel}/down")
}

pub fn parse_channel_from_topic(topic_name: &str) -> Option<(String, String)> {
    let rest = topic_name.strip_prefix(&format!("{TOPIC_PREFIX}/"))?;
    let (channel, direction) = rest.rsplit_once('/')?;
    if channel.is_empty() {
        return None;
    }
    match direction {
        "up" | "down" => Some((channel.to_string(), direction.to_string())),
        _ => None,
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn parses_super_cereal_topics() {
        assert_eq!(
            parse_channel_from_topic("super_cereal/dev1/up"),
            Some(("dev1".to_string(), "up".to_string()))
        );
        assert_eq!(
            parse_channel_from_topic("super_cereal/my-channel/down"),
            Some(("my-channel".to_string(), "down".to_string()))
        );
        assert!(parse_channel_from_topic("other/topic").is_none());
    }

    #[test]
    fn topic_names_are_stable() {
        assert_eq!(up_topic_name("dev1"), "super_cereal/dev1/up");
        assert_eq!(down_topic_name("dev1"), "super_cereal/dev1/down");
    }
}