quicburn-shared 0.1.0

A blazing fast QUIC implementation in Rust
//! # quicburn-shared
//!
//! Shared types, protocols, and utilities for the quicburn QUIC implementation.
pub use frame::MAX_FRAME_SIZE;

pub mod frame;
mod macros;
pub mod routes;

pub mod prelude {
    pub use super::frame::{Frame, MAX_FRAME_SIZE, Status, read_frame, write_frame};
    pub use super::routes::{
        Priority, RouteCategory, RouteInfo, RouteRegistry, register_core_routes, route_id,
    };
    pub use super::{ALPN_PROTOCOL, PROTOCOL_VERSION, protocol_info};
    pub use super::{define_routes, protected_route_def, public_route_def, single_route};
}

// ============================================================
// ALPN Protocol
// ============================================================

/// Application-level protocol identifier for ALPN negotiation
pub const ALPN_PROTOCOL: &[u8] = b"quicburn/1";

/// ALPN protocol as a string (convenience for logging)
pub const ALPN_PROTOCOL_STR: &str = "quicburn/1";

/// Current version of the protocol
pub const PROTOCOL_VERSION: &str = env!("CARGO_PKG_VERSION");

/// Get protocol information as a string
pub fn protocol_info() -> String {
    format!("quicburn/{} ({})", PROTOCOL_VERSION, ALPN_PROTOCOL_STR)
}

// ============================================================
// Protocol Negotiation Helpers
// ============================================================

/// Check if a given ALPN protocol is supported
pub fn supports_alpn(protocol: &[u8]) -> bool {
    protocol == ALPN_PROTOCOL
}

/// Get the list of supported ALPN protocols (for server configuration)
pub fn supported_alpn_protocols() -> &'static [&'static [u8]] {
    &[ALPN_PROTOCOL]
}

/// Parse a protocol version from a string
pub fn parse_protocol_version(version_str: &str) -> Option<(u32, u32, u32)> {
    let parts: Vec<&str> = version_str.split('.').collect();
    if parts.len() != 3 {
        return None;
    }

    let major = parts[0].parse::<u32>().ok()?;
    let minor = parts[1].parse::<u32>().ok()?;
    let patch = parts[2].parse::<u32>().ok()?;

    Some((major, minor, patch))
}

/// Check if a protocol version is compatible with the current version
pub fn is_compatible_version(version_str: &str) -> bool {
    if let Some((major, _minor, _patch)) = parse_protocol_version(version_str) {
        // Same major version = compatible
        // Different minor versions are okay within same major
        if let Some((our_major, _, _)) = parse_protocol_version(PROTOCOL_VERSION) {
            return major == our_major;
        }
    }
    false
}

// ============================================================
// Protocol Constants
// ============================================================

/// Maximum packet size for QUIC (typical)
pub const MAX_UDP_PAYLOAD_SIZE: usize = 1200;

/// Maximum number of streams per connection
pub const MAX_STREAMS: u64 = 100;

/// Default idle timeout in milliseconds
pub const DEFAULT_IDLE_TIMEOUT_MS: u64 = 30000; // 30 seconds

/// Keep-alive interval in milliseconds
pub const KEEP_ALIVE_INTERVAL_MS: u64 = 5000; // 5 seconds

// ============================================================
// Convenience Result type for shared crate
// ============================================================

/// A convenience Result type for the shared crate
pub type Result<T> = anyhow::Result<T>;

// ============================================================
// Tests
// ============================================================

#[cfg(test)]
mod tests {
    pub use super::frame::{Frame, MAX_FRAME_SIZE, Status, read_frame, write_frame};
    use super::*;

    pub use super::routes::{
        Priority, RouteCategory, RouteInfo, RouteRegistry, register_core_routes, route_id,
    };

    #[test]
    fn test_protocol_info() {
        let info = protocol_info();
        assert!(info.contains("quicburn"));
        assert!(info.contains(ALPN_PROTOCOL_STR));
    }

    #[test]
    fn test_alpn_protocol() {
        assert_eq!(ALPN_PROTOCOL, b"quicburn/1");
        assert!(supports_alpn(b"quicburn/1"));
        assert!(!supports_alpn(b"http/1.1"));
        assert!(!supports_alpn(b"h3"));
    }

    #[test]
    fn test_supported_alpn_protocols() {
        let protocols = supported_alpn_protocols();
        assert_eq!(protocols.len(), 1);
        assert_eq!(protocols[0], ALPN_PROTOCOL);
    }

    #[test]
    fn test_parse_protocol_version() {
        assert_eq!(parse_protocol_version("1.0.0"), Some((1, 0, 0)));
        assert_eq!(parse_protocol_version("1.2.3"), Some((1, 2, 3)));
        assert_eq!(parse_protocol_version("0.1.0"), Some((0, 1, 0)));
        assert_eq!(parse_protocol_version("invalid"), None);
        assert_eq!(parse_protocol_version("1.0"), None);
        assert_eq!(parse_protocol_version("1.0.0.0"), None);
    }

    #[test]
    fn test_is_compatible_version() {
        // Current version should be compatible with itself
        assert!(is_compatible_version(PROTOCOL_VERSION));

        // Same major version should be compatible
        let current = PROTOCOL_VERSION;
        if let Some((major, _, _)) = parse_protocol_version(current) {
            let compat = format!("{}.{}.{}", major, 99, 99);
            assert!(is_compatible_version(&compat));

            let incompat = format!("{}.{}.{}", major + 1, 0, 0);
            assert!(!is_compatible_version(&incompat));
        }
    }

    #[test]
    fn test_route_reexports() {
        let mut registry = RouteRegistry::new();
        register_core_routes(&mut registry);
        assert!(!registry.is_empty());
    }

    #[test]
    fn test_route_id_is_deterministic() {
        assert_eq!(route_id("echo"), route_id("echo"));
        assert_eq!(route_id("kv_get"), route_id("kv_get"));
    }

    #[test]
    fn test_route_id_short_name() {
        // just exercises the short-name path, doesn't pin the exact hash algorithm
        assert_eq!(route_id("hi"), route_id("hi"));
        assert_ne!(route_id("hi"), route_id("ih"));
    }

    #[test]
    fn test_route_registry_lookup_by_name_matches_by_id() {
        let mut registry = RouteRegistry::new();
        register_core_routes(&mut registry);

        let by_name = registry
            .get_by_name("health")
            .expect("health route registered");
        let by_id = registry
            .get(route_id("health"))
            .expect("health route registered");
        assert_eq!(by_name.name, "health");
        assert_eq!(by_name.id, by_id.id);
    }

    #[test]
    fn test_route_registry_by_category() {
        let mut registry = RouteRegistry::new();
        register_core_routes(&mut registry);

        let file_routes = registry.by_category(RouteCategory::File);
        assert!(file_routes.iter().any(|r| r.name == "file_upload"));
    }

    #[test]
    #[should_panic(expected = "route id collision")]
    fn test_route_registry_rejects_duplicate_ids() {
        let mut registry = RouteRegistry::new();
        registry.register(
            "aa",
            RouteCategory::Other("test"),
            Priority::Normal,
            false,
            None,
        );
        registry.register(
            "aa",
            RouteCategory::Other("test"),
            Priority::Normal,
            false,
            None,
        );
    }

    #[test]
    fn test_frame_reexports() {
        let frame = Frame::ok(0x01);
        assert_eq!(frame.route, 0x01);
        assert!(frame.is_ok());
    }

    #[test]
    fn test_status_reexports() {
        assert_eq!(Status::Ok.as_byte(), 0);
        assert_eq!(Status::Error.as_byte(), 1);
    }

    #[test]
    fn test_protocol_constants() {
        assert!(MAX_UDP_PAYLOAD_SIZE > 0);
        assert!(MAX_STREAMS > 0);
        assert!(DEFAULT_IDLE_TIMEOUT_MS > 0);
        assert!(KEEP_ALIVE_INTERVAL_MS > 0);
        assert!(KEEP_ALIVE_INTERVAL_MS < DEFAULT_IDLE_TIMEOUT_MS);
    }
}