const ZERO: char = '\u{200B}';
const ONE: char = '\u{200C}';
pub const MAGIC: [u8; 4] = *b"C2ZW";
pub fn is_bit(c: char) -> bool {
c == ZERO || c == ONE
}
pub fn encode(payload: &[u8]) -> String {
let mut bytes = MAGIC.to_vec();
bytes.extend_from_slice(payload);
let mut out = String::with_capacity(bytes.len() * 8 * 3);
for byte in bytes {
for bit in (0..8).rev() {
out.push(if (byte >> bit) & 1 == 1 { ONE } else { ZERO });
}
}
out
}
pub fn embed(text: &str, payload: &[u8]) -> String {
let mut s = String::with_capacity(text.len() + (MAGIC.len() + payload.len()) * 24);
s.push_str(text);
s.push_str(&encode(payload));
s
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Decoded {
None,
Corrupt,
Payload(Vec<u8>),
}
pub fn extract(text: &str) -> Decoded {
let bits: Vec<char> = text.chars().filter(|c| is_bit(*c)).collect();
if bits.is_empty() {
return Decoded::None;
}
if !bits.len().is_multiple_of(8) {
return Decoded::Corrupt;
}
let bytes: Vec<u8> = bits
.chunks(8)
.map(|chunk| {
chunk
.iter()
.fold(0u8, |acc, &c| (acc << 1) | (c == ONE) as u8)
})
.collect();
if bytes.len() >= MAGIC.len() && bytes[..MAGIC.len()] == MAGIC {
Decoded::Payload(bytes[MAGIC.len()..].to_vec())
} else {
Decoded::Corrupt
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trip_recovers_payload() {
let payload = b"c2pa-manifest-01";
let text = embed("A document body.", payload);
assert_eq!(extract(&text), Decoded::Payload(payload.to_vec()));
}
#[test]
fn eight_code_points_per_byte() {
assert_eq!(encode(b"z").chars().count(), (MAGIC.len() + 1) * 8);
}
#[test]
fn stripping_bits_yields_none() {
let text = embed("hi", b"x");
let stripped: String = text.chars().filter(|c| !is_bit(*c)).collect();
assert_eq!(extract(&stripped), Decoded::None);
}
#[test]
fn losing_one_bit_fails_safe() {
let mut chars: Vec<char> = embed("hi", b"payload").chars().collect();
chars.pop();
let mangled: String = chars.into_iter().collect();
assert_eq!(extract(&mangled), Decoded::Corrupt);
}
}