use crate::b64;
use mkit_core::object::{Identity, IdentityKind};
pub const BRIDGE_EMAIL: &str = "bridge@mkit.invalid";
fn name_byte_ok(b: u8) -> bool {
!(b == b'<' || b == b'>' || b < 0x20 || b == 0x7F)
}
#[must_use]
pub fn display_name(identity: &Identity) -> String {
match identity.kind {
IdentityKind::Ed25519 => {
format!("mkit:ed25519:{}", crate::gitobj::bytes_hex(&identity.bytes))
}
IdentityKind::DidKey => {
if identity.bytes.iter().all(|&b| name_byte_ok(b))
&& let Ok(s) = std::str::from_utf8(&identity.bytes)
{
format!("did:key:{s}")
} else {
opaque_name(&identity.bytes)
}
}
IdentityKind::Opaque => {
if identity.bytes.iter().all(|&b| name_byte_ok(b))
&& let Ok(s) = std::str::from_utf8(&identity.bytes)
{
s.to_owned()
} else {
opaque_name(&identity.bytes)
}
}
}
}
fn opaque_name(payload: &[u8]) -> String {
format!("mkit:opaque:{}", b64::encode(payload))
}
#[must_use]
pub fn line(identity: &Identity, timestamp: u64) -> Vec<u8> {
let mut out = Vec::new();
out.extend_from_slice(display_name(identity).as_bytes());
out.extend_from_slice(b" <");
out.extend_from_slice(BRIDGE_EMAIL.as_bytes());
out.extend_from_slice(b"> ");
out.extend_from_slice(timestamp.to_string().as_bytes());
out.extend_from_slice(b" +0000");
out
}
#[must_use]
pub fn parse_timestamp(line: &[u8]) -> Option<u64> {
let s = std::str::from_utf8(line).ok()?;
let rest = s.strip_suffix(" +0000")?;
let (_, ts) = rest.rsplit_once(' ')?;
ts.parse::<u64>().ok()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ed25519_name_is_hex() {
let id = Identity::ed25519([0xAA; 32]);
let n = display_name(&id);
assert!(n.starts_with("mkit:ed25519:aaaa"));
assert_eq!(n.len(), "mkit:ed25519:".len() + 64);
}
#[test]
fn printable_opaque_is_verbatim() {
let id = Identity::opaque(b"Alice Example".to_vec());
assert_eq!(display_name(&id), "Alice Example");
}
#[test]
fn angle_bracket_opaque_falls_back_to_base64() {
let id = Identity::opaque(b"Alice <alice@example.com>".to_vec());
let n = display_name(&id);
assert!(n.starts_with("mkit:opaque:"), "got {n}");
assert!(!n.contains('<'));
}
#[test]
fn non_utf8_opaque_falls_back_to_base64() {
let id = Identity::opaque(vec![0xFF, 0xFE, 0x00]);
assert!(display_name(&id).starts_with("mkit:opaque:"));
}
#[test]
fn line_round_trips_timestamp() {
let id = Identity::opaque(b"x".to_vec());
let l = line(&id, 1_700_000_000);
assert!(l.ends_with(b" +0000"));
assert_eq!(parse_timestamp(&l), Some(1_700_000_000));
}
}