ohttp-gateway 1.0.1

A OHTTP Gateway server, meant to run between a OHTTP Relay and a target web service.
Documentation
use bhttp::Message;
use ohttp_gateway::key_manager::{CipherSuiteConfig, KeyManager, KeyManagerConfig};
use std::io::Cursor;
use std::time::Duration;
use tokio;

#[tokio::test]
async fn test_key_generation() {
    let config = KeyManagerConfig::default();
    let manager = KeyManager::new(config).await.unwrap();

    let stats = manager.get_stats().await;
    assert_eq!(stats.total_keys, 1);
    assert_eq!(stats.active_keys, 1);
    assert!(stats.active_key_id > 0); // Should have generated a key with ID > 0
}

#[tokio::test]
async fn test_key_generation_with_seed() {
    let config = KeyManagerConfig::default();
    let seed = vec![0u8; 32]; // 32 bytes of zeros for deterministic testing

    let manager = KeyManager::new_with_seed(config, seed).await.unwrap();
    let stats = manager.get_stats().await;

    assert_eq!(stats.total_keys, 1);
    assert_eq!(stats.active_keys, 1);
}

#[tokio::test]
async fn test_key_generation_with_insufficient_seed() {
    let config = KeyManagerConfig::default();
    let short_seed = vec![0u8; 16]; // Only 16 bytes - should fail

    let result = KeyManager::new_with_seed(config, short_seed).await;
    assert!(result.is_err());

    if let Err(e) = result {
        assert!(e.to_string().contains("Seed must be at least 32 bytes"));
    }
}

#[tokio::test]
async fn test_key_rotation() {
    let config = KeyManagerConfig {
        rotation_interval: Duration::from_secs(60),
        key_retention_period: Duration::from_secs(30),
        auto_rotation_enabled: true,
        ..Default::default()
    };

    let manager = KeyManager::new(config).await.unwrap();
    let initial_stats = manager.get_stats().await;

    // Rotate keys
    manager.rotate_keys().await.unwrap();

    let new_stats = manager.get_stats().await;
    assert_eq!(new_stats.total_keys, 2); // Old key + new key
    assert_ne!(new_stats.active_key_id, initial_stats.active_key_id);
}

#[tokio::test]
async fn test_get_current_server() {
    let config = KeyManagerConfig::default();
    let manager = KeyManager::new(config).await.unwrap();

    let server = manager.get_current_server().await;
    assert!(server.is_ok());
}

#[tokio::test]
async fn test_get_server_by_id() {
    let config = KeyManagerConfig::default();
    let manager = KeyManager::new(config).await.unwrap();

    let stats = manager.get_stats().await;
    let active_id = stats.active_key_id;

    // Should find the active key
    let server = manager.get_server_by_id(active_id).await;
    assert!(server.is_some());

    // Should not find non-existent key
    let non_existent = manager.get_server_by_id(active_id.wrapping_add(100)).await;
    assert!(non_existent.is_none());
}

#[tokio::test]
async fn test_should_rotate() {
    let config = KeyManagerConfig {
        rotation_interval: Duration::from_millis(100), // Very short for testing
        ..Default::default()
    };

    let manager = KeyManager::new(config).await.unwrap();

    // Should not need rotation immediately
    assert!(!manager.should_rotate().await);

    // Wait for the rotation interval to pass
    tokio::time::sleep(Duration::from_millis(200)).await;

    // Now should need rotation
    assert!(manager.should_rotate().await);
}

#[tokio::test]
async fn test_get_encoded_config() {
    let config = KeyManagerConfig::default();
    let manager = KeyManager::new(config).await.unwrap();

    let encoded_config = manager.get_encoded_config().await.unwrap();

    // Should have config data with 2-byte length prefix per RFC 9458
    assert!(!encoded_config.is_empty());
    assert!(encoded_config.len() > 2);

    // Verify the length prefix is correct
    let length_prefix = u16::from_be_bytes([encoded_config[0], encoded_config[1]]);
    assert_eq!(length_prefix as usize, encoded_config.len() - 2);

    // The encoded config should be the length-prefixed config bytes
    // We can't easily verify the exact content without duplicating the encoding logic,
    // but we can at least verify it's reasonable in size
    assert!(encoded_config.len() < 1000); // Reasonable upper bound
}

#[tokio::test]
async fn test_multiple_cipher_suites() {
    let config = KeyManagerConfig {
        cipher_suites: vec![
            CipherSuiteConfig {
                kem: "X25519_SHA256".to_string(),
                kdf: "HKDF_SHA256".to_string(),
                aead: "AES_128_GCM".to_string(),
            },
            CipherSuiteConfig {
                kem: "X25519_SHA256".to_string(),
                kdf: "HKDF_SHA256".to_string(),
                aead: "CHACHA20_POLY1305".to_string(),
            },
        ],
        ..Default::default()
    };

    let manager = KeyManager::new(config).await.unwrap();
    let stats = manager.get_stats().await;
    assert_eq!(stats.total_keys, 1);
}

#[tokio::test]
async fn test_cleanup_expired_keys() {
    let config = KeyManagerConfig {
        rotation_interval: Duration::from_millis(50),
        key_retention_period: Duration::from_millis(100),
        auto_rotation_enabled: false, // Manual control for testing
        ..Default::default()
    };

    let manager = KeyManager::new(config).await.unwrap();

    // Rotate to create an old key
    manager.rotate_keys().await.unwrap();

    let stats_after_rotation = manager.get_stats().await;
    assert_eq!(stats_after_rotation.total_keys, 2);

    // Wait for keys to expire and manually trigger cleanup
    tokio::time::sleep(Duration::from_millis(200)).await;

    // Trigger another rotation which should clean up expired keys
    manager.rotate_keys().await.unwrap();

    let final_stats = manager.get_stats().await;
    // Should have cleaned up the expired key
    assert!(final_stats.total_keys <= 2);
}

#[tokio::test]
async fn test_bhttp_parsing() {
    // let data = &[
    //     2, 3, 71, 69, 84, 5, 104, 116, 116, 112, 115, 9, 108, 111, 99, 97, 108, 104, 111, 115, 116,
    //     4, 47, 103, 101, 116, 10, 117, 115, 101, 114, 45, 97, 103, 101, 110, 116, 21, 79, 72, 84,
    //     84, 80, 45, 84, 101, 115, 116, 45, 67, 108, 105, 101, 110, 116, 47, 49, 46, 48, 6, 97, 99,
    //     99, 101, 112, 116, 16, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 47, 106, 115,
    //     111, 110, 0, 0,
    // ];
    // let mut cursor = std::io::Cursor::new(data);
    //
    // let m = Message::read_bhttp(&mut cursor).unwrap();
    //
    // println!("TEST {:?}", m);

    const REQUEST: &[u8] = &[
        2, 3, 71, 69, 84, 5, 104, 116, 116, 112, 115, 9, 108, 111, 99, 97, 108, 104, 111, 115, 116,
        4, 47, 103, 101, 116, 10, 117, 115, 101, 114, 45, 97, 103, 101, 110, 116, 21, 79, 72, 84,
        84, 80, 45, 84, 101, 115, 116, 45, 67, 108, 105, 101, 110, 116, 47, 49, 46, 48, 6, 97, 99,
        99, 101, 112, 116, 16, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 47, 106, 115,
        111, 110, 0, 0, 0,
    ];
    let m = Message::read_bhttp(&mut Cursor::new(REQUEST)).unwrap();
    assert!(m.header().get(b"accept").is_some());
}