synta 0.1.5

ASN.1 parser, decoder, and encoder library with DER/BER support and C FFI
Documentation
//! Tests for string types

use synta::types::string::{
    BmpString, GeneralString, IA5String, NumericString, OctetString, OctetStringRef,
    PrintableString, TeletexString, UniversalString, Utf8String, VisibleString,
};
use synta::{Decoder, Encoder, Encoding};

#[test]
fn test_octet_string() {
    let data = vec![1, 2, 3, 4, 5];
    let s = OctetString::new(data.clone());
    assert_eq!(s.as_bytes(), &data[..]);

    let recovered = s.into_vec();
    assert_eq!(recovered, data);
}

#[test]
fn test_octet_string_ref() {
    let data = [1, 2, 3, 4, 5];
    let s = OctetStringRef::new(&data);
    assert_eq!(s.as_bytes(), &data[..]);

    let owned = s.to_owned();
    assert_eq!(owned.as_bytes(), &data[..]);
}

#[test]
fn test_utf8string() {
    let s = Utf8String::new("Hello, World!".to_string());
    assert_eq!(s.as_str(), "Hello, World!");

    // Test with Unicode
    let s = Utf8String::new("你好世界".to_string());
    assert_eq!(s.as_str(), "你好世界");
}

#[test]
fn test_utf8string_roundtrip() {
    let test_strings = vec!["Hello", "test@example.com", "你好世界", "🎉🎊", ""];

    for text in test_strings {
        let s = Utf8String::new(text.to_string());

        let mut encoder = Encoder::new(Encoding::Der);
        encoder.encode(&s).unwrap();
        let encoded = encoder.finish().unwrap();

        let mut decoder = Decoder::new(&encoded, Encoding::Der);
        let decoded: Utf8String = decoder.decode().unwrap();

        assert_eq!(decoded.as_str(), text);
    }
}

#[test]
fn test_printablestring_valid() {
    // Valid PrintableString characters
    let valid_strings = vec![
        "Hello", "test123", "ABC-XYZ", "a=b", "(test)", "a b c", "test.com", "a/b", "a:b", "a?b",
        "a'b", "a+b", "a,b",
    ];

    for text in valid_strings {
        let result = PrintableString::new(text.to_string());
        assert!(result.is_ok(), "Failed for: {}", text);
        assert_eq!(result.unwrap().as_str(), text);
    }
}

#[test]
fn test_printablestring_invalid() {
    // Invalid PrintableString characters
    let invalid_strings = vec![
        "test@example.com", // @ is not allowed
        "hello!",           // ! is not allowed
        "a_b",              // _ is not allowed
        "test#123",         // # is not allowed
        "你好",             // Unicode not allowed
    ];

    for text in invalid_strings {
        let result = PrintableString::new(text.to_string());
        assert!(result.is_err(), "Should fail for: {}", text);
    }
}

#[test]
fn test_printablestring_roundtrip() {
    let test_strings = vec!["Hello", "test123", "CN=Test CA", ""];

    for text in test_strings {
        let s = PrintableString::new(text.to_string()).unwrap();

        let mut encoder = Encoder::new(Encoding::Der);
        encoder.encode(&s).unwrap();
        let encoded = encoder.finish().unwrap();

        let mut decoder = Decoder::new(&encoded, Encoding::Der);
        let decoded: PrintableString = decoder.decode().unwrap();

        assert_eq!(decoded.as_str(), text);
    }
}

#[test]
fn test_ia5string_valid() {
    // Valid ASCII strings
    let valid_strings = vec!["Hello", "test@example.com", "123!@#", ""];

    for text in valid_strings {
        let result = IA5String::new(text.to_string());
        assert!(result.is_ok(), "Failed for: {}", text);
        assert_eq!(result.unwrap().as_str(), text);
    }
}

#[test]
fn test_ia5string_invalid() {
    // Invalid (non-ASCII) strings
    let invalid_strings = vec!["你好", "test™", "hello©world"];

    for text in invalid_strings {
        let result = IA5String::new(text.to_string());
        assert!(result.is_err(), "Should fail for: {}", text);
    }
}

#[test]
fn test_ia5string_roundtrip() {
    let test_strings = vec!["Hello", "test@example.com", "user:password", ""];

    for text in test_strings {
        let s = IA5String::new(text.to_string()).unwrap();

        let mut encoder = Encoder::new(Encoding::Der);
        encoder.encode(&s).unwrap();
        let encoded = encoder.finish().unwrap();

        let mut decoder = Decoder::new(&encoded, Encoding::Der);
        let decoded: IA5String = decoder.decode().unwrap();

        assert_eq!(decoded.as_str(), text);
    }
}

// --- NumericString ---

#[test]
fn test_numericstring_valid() {
    let valid = vec!["", "0", "12345", "1 2 3", "   "];
    for text in valid {
        let result = NumericString::new(text.to_string());
        assert!(result.is_ok(), "Should accept: {:?}", text);
        assert_eq!(result.unwrap().as_str(), text);
    }
}

#[test]
fn test_numericstring_invalid() {
    let invalid = vec!["abc", "12a", "1.2", "-3", "1+2", "hello world"];
    for text in invalid {
        let result = NumericString::new(text.to_string());
        assert!(result.is_err(), "Should reject: {:?}", text);
    }
}

#[test]
fn test_numericstring_roundtrip() {
    for text in &["", "0", "12345", "1 2 3"] {
        let s = NumericString::new(text.to_string()).unwrap();

        let mut encoder = Encoder::new(Encoding::Der);
        encoder.encode(&s).unwrap();
        let encoded = encoder.finish().unwrap();

        // Tag byte must be 0x12 (tag 18, primitive, universal)
        assert_eq!(encoded[0], 0x12, "wrong tag for NumericString");

        let mut decoder = Decoder::new(&encoded, Encoding::Der);
        let decoded: NumericString = decoder.decode().unwrap();
        assert_eq!(decoded.as_str(), *text);
    }
}

// --- TeletexString ---

#[test]
fn test_teletexstring_bytes() {
    let data = vec![0x48, 0x65, 0x6c, 0x6c, 0x6f]; // "Hello" in ASCII / Latin-1
    let s = TeletexString::new(data.clone());
    assert_eq!(s.as_bytes(), &data[..]);
    assert_eq!(s.as_latin1_string(), "Hello");
}

#[test]
fn test_teletexstring_roundtrip() {
    let data: Vec<u8> = b"test\x80\xff".to_vec();
    let s = TeletexString::new(data.clone());

    let mut encoder = Encoder::new(Encoding::Der);
    encoder.encode(&s).unwrap();
    let encoded = encoder.finish().unwrap();

    // Tag byte must be 0x14 (tag 20, primitive, universal)
    assert_eq!(encoded[0], 0x14, "wrong tag for TeletexString");

    let mut decoder = Decoder::new(&encoded, Encoding::Der);
    let decoded: TeletexString = decoder.decode().unwrap();
    assert_eq!(decoded.as_bytes(), &data[..]);
}

#[test]
fn test_teletexstring_empty() {
    let s = TeletexString::new(vec![]);
    let mut encoder = Encoder::new(Encoding::Der);
    encoder.encode(&s).unwrap();
    let encoded = encoder.finish().unwrap();
    assert_eq!(&encoded, &[0x14, 0x00]);

    let mut decoder = Decoder::new(&encoded, Encoding::Der);
    let decoded: TeletexString = decoder.decode().unwrap();
    assert!(decoded.as_bytes().is_empty());
}

// --- VisibleString ---

#[test]
fn test_visiblestring_valid() {
    let valid = vec!["", "Hello", "test123", "!@#$%", " ", "a-z A-Z 0-9"];
    for text in valid {
        let result = VisibleString::new(text.to_string());
        assert!(result.is_ok(), "Should accept: {:?}", text);
        assert_eq!(result.unwrap().as_str(), text);
    }
}

#[test]
fn test_visiblestring_invalid() {
    // Control characters (below 0x20) and DEL (0x7f) are not allowed
    for text in &["\t", "\n", "\x00", "\x1f", "\x7f"] {
        let result = VisibleString::new(text.to_string());
        assert!(result.is_err(), "Should reject: {:?}", text);
    }
    // Non-ASCII is also rejected
    assert!(VisibleString::new("café".to_string()).is_err());
}

#[test]
fn test_visiblestring_roundtrip() {
    for text in &["", "Hello, World!", "user@example.com", "CN=Test"] {
        let s = VisibleString::new(text.to_string()).unwrap();

        let mut encoder = Encoder::new(Encoding::Der);
        encoder.encode(&s).unwrap();
        let encoded = encoder.finish().unwrap();

        // Tag byte must be 0x1a (tag 26, primitive, universal)
        assert_eq!(encoded[0], 0x1a, "wrong tag for VisibleString");

        let mut decoder = Decoder::new(&encoded, Encoding::Der);
        let decoded: VisibleString = decoder.decode().unwrap();
        assert_eq!(decoded.as_str(), *text);
    }
}

// --- GeneralString ---

#[test]
fn test_generalstring_bytes() {
    let data = vec![0x48, 0x69, 0x80, 0xff];
    let s = GeneralString::new(data.clone());
    assert_eq!(s.as_bytes(), &data[..]);
}

#[test]
fn test_generalstring_roundtrip() {
    let data: Vec<u8> = b"krbtgt\x00".to_vec();
    let s = GeneralString::new(data.clone());

    let mut encoder = Encoder::new(Encoding::Der);
    encoder.encode(&s).unwrap();
    let encoded = encoder.finish().unwrap();

    // Tag byte must be 0x1b (tag 27, primitive, universal)
    assert_eq!(encoded[0], 0x1b, "wrong tag for GeneralString");

    let mut decoder = Decoder::new(&encoded, Encoding::Der);
    let decoded: GeneralString = decoder.decode().unwrap();
    assert_eq!(decoded.as_bytes(), &data[..]);
}

// --- UniversalString ---

#[test]
fn test_universalstring_ascii() {
    let s = UniversalString::new("Hi".to_string());
    assert_eq!(s.as_str(), "Hi");
}

#[test]
fn test_universalstring_unicode() {
    let text = "你好";
    let s = UniversalString::new(text.to_string());
    assert_eq!(s.as_str(), text);
}

#[test]
fn test_universalstring_roundtrip() {
    for text in &["", "Hello", "你好", "\u{1F600}"] {
        let s = UniversalString::new(text.to_string());

        let mut encoder = Encoder::new(Encoding::Der);
        encoder.encode(&s).unwrap();
        let encoded = encoder.finish().unwrap();

        // Tag byte must be 0x1c (tag 28, primitive, universal)
        assert_eq!(encoded[0], 0x1c, "wrong tag for UniversalString");

        let mut decoder = Decoder::new(&encoded, Encoding::Der);
        let decoded: UniversalString = decoder.decode().unwrap();
        assert_eq!(decoded.as_str(), *text);
    }
}

#[test]
fn test_universalstring_from_ucs4_be() {
    // "A" in UCS-4 BE is 0x00 0x00 0x00 0x41
    let s = UniversalString::from_ucs4_be(&[0x00, 0x00, 0x00, 0x41]).unwrap();
    assert_eq!(s.as_str(), "A");
}

#[test]
fn test_universalstring_from_ucs4_be_invalid_length() {
    // Length not a multiple of 4
    assert!(UniversalString::from_ucs4_be(&[0x00, 0x41]).is_err());
}

// --- BmpString ---

#[test]
fn test_bmpstring_ascii() {
    let s = BmpString::new("Hello".to_string()).unwrap();
    assert_eq!(s.as_str(), "Hello");
}

#[test]
fn test_bmpstring_bmp_unicode() {
    let text = "café";
    let s = BmpString::new(text.to_string()).unwrap();
    assert_eq!(s.as_str(), text);
}

#[test]
fn test_bmpstring_rejects_supplementary() {
    // U+1F600 GRINNING FACE is outside the BMP
    assert!(BmpString::new("\u{1F600}".to_string()).is_err());
}

#[test]
fn test_bmpstring_roundtrip() {
    for text in &["", "Hello", "café", "ñ"] {
        let s = BmpString::new(text.to_string()).unwrap();

        let mut encoder = Encoder::new(Encoding::Der);
        encoder.encode(&s).unwrap();
        let encoded = encoder.finish().unwrap();

        // Tag byte must be 0x1e (tag 30, primitive, universal)
        assert_eq!(encoded[0], 0x1e, "wrong tag for BmpString");

        let mut decoder = Decoder::new(&encoded, Encoding::Der);
        let decoded: BmpString = decoder.decode().unwrap();
        assert_eq!(decoded.as_str(), *text);
    }
}

#[test]
fn test_bmpstring_from_ucs2_be() {
    // "A" in UCS-2 BE is 0x00 0x41
    let s = BmpString::from_ucs2_be(&[0x00, 0x41]).unwrap();
    assert_eq!(s.as_str(), "A");
}

#[test]
fn test_bmpstring_from_ucs2_be_invalid_length() {
    assert!(BmpString::from_ucs2_be(&[0x00]).is_err());
}