liminal-protocol 0.13.1

Shared participant-lifecycle protocol types for liminal
Documentation
//! A credential's `Debug` names its type and length, never its bytes: a
//! secret or attempt token reaches logs and error text through `{:?}`.

use alloc::format;
use alloc::string::ToString;

use super::{
    AttachAttemptToken, AttachSecret, CredentialDigest, DetachAttemptToken, EnrollmentToken,
    LeaveAttemptToken, RecordAdmissionAttemptToken,
};

/// Bytes 200, 201, … — each three digits, so no byte's rendering can hide in
/// a type name or a length.
fn distinct<const N: usize>() -> [u8; N] {
    core::array::from_fn(|index| u8::try_from(200 + index).unwrap_or(u8::MAX))
}

/// `rendered` shows none of `bytes`, in decimal or in hex.
fn shows_none_of(rendered: &str, bytes: &[u8]) {
    for byte in bytes {
        assert!(
            !rendered.contains(&byte.to_string()) && !rendered.contains(&format!("{byte:02x}")),
            "{rendered} shows byte {byte}"
        );
    }
}

#[test]
fn an_attach_secret_debug_shows_no_byte() {
    let bytes = distinct::<32>();
    let rendered = format!("{:?}", AttachSecret::new(bytes));
    shows_none_of(&rendered, &bytes);
    assert_eq!(rendered, "AttachSecret(32 bytes)");
}

#[test]
fn an_attach_attempt_token_debug_shows_no_byte() {
    let bytes = distinct::<16>();
    let rendered = format!("{:?}", AttachAttemptToken::new(bytes));
    shows_none_of(&rendered, &bytes);
    assert_eq!(rendered, "AttachAttemptToken(16 bytes)");
}

#[test]
fn an_enrollment_token_debug_shows_no_byte() {
    let bytes = distinct::<16>();
    let rendered = format!("{:?}", EnrollmentToken::new(bytes));
    shows_none_of(&rendered, &bytes);
    assert_eq!(rendered, "EnrollmentToken(16 bytes)");
}

#[test]
fn a_detach_attempt_token_debug_shows_no_byte() {
    let bytes = distinct::<16>();
    let rendered = format!("{:?}", DetachAttemptToken::new(bytes));
    shows_none_of(&rendered, &bytes);
    assert_eq!(rendered, "DetachAttemptToken(16 bytes)");
}

#[test]
fn a_leave_attempt_token_debug_shows_no_byte() {
    let bytes = distinct::<16>();
    let rendered = format!("{:?}", LeaveAttemptToken::new(bytes));
    shows_none_of(&rendered, &bytes);
    assert_eq!(rendered, "LeaveAttemptToken(16 bytes)");
}

#[test]
fn a_record_admission_attempt_token_debug_shows_no_byte() {
    let bytes = distinct::<16>();
    let rendered = format!("{:?}", RecordAdmissionAttemptToken::new(bytes));
    shows_none_of(&rendered, &bytes);
    assert_eq!(rendered, "RecordAdmissionAttemptToken(16 bytes)");
}

/// A request carrying a credential shows none of its bytes either: a
/// containing type's derived `Debug` delegates to the credential's.
#[test]
fn a_debug_of_a_containing_value_shows_no_credential_byte() {
    let secret = distinct::<32>();
    let rendered = format!("{:?}", Some((AttachSecret::new(secret), 7_u8)));
    shows_none_of(&rendered, &secret);
}

#[test]
fn a_credential_digest_debug_shows_no_byte() {
    // bytes 100 to 131: none is a hex pair inside the type name (222 is "de", as in "CredentialDigest")
    let bytes: [u8; 32] =
        core::array::from_fn(|index| u8::try_from(100 + index).unwrap_or(u8::MAX));
    let digest = CredentialDigest::new(bytes);
    for rendered in [format!("{digest:?}"), format!("{digest:#?}")] {
        shows_none_of(&rendered, &bytes);
        assert_eq!(rendered, "CredentialDigest(32 bytes)");
    }
}