use super::gf::{Gf, rs_decode, rs_encode};
use super::tables::GAPS_BITS_ORDER_LUT;
use crate::error::{Error, Result};
struct FormatParams {
gaps_data: usize,
gaps_parity: usize,
arcs_data: usize,
arcs_parity: usize,
}
fn format_params(version: usize) -> FormatParams {
if version == 0 {
FormatParams {
gaps_data: 9,
gaps_parity: 4,
arcs_data: 5,
arcs_parity: 2,
}
} else {
FormatParams {
gaps_data: 11,
gaps_parity: 2,
arcs_data: 5,
arcs_parity: 2,
}
}
}
const TEMPLATE_BITS: [bool; 8] = [false, true, false, true, false, true, false, false];
pub fn encode_payload(payload: &[u8]) -> Result<Vec<bool>> {
let trimmed: &[u8] = {
let start = payload
.iter()
.position(|&b| b != 0)
.unwrap_or(payload.len());
&payload[start..]
};
if trimmed.len() > 16 {
return Err(Error::capacity("App Clip payload exceeds 16 bytes"));
}
let version = usize::from(trimmed.len() > 14);
let fp = format_params(version);
let total = fp.gaps_data + fp.arcs_data;
let mut padded = vec![0u8; total];
padded[total - trimmed.len()..].copy_from_slice(trimmed);
let scrambled: Vec<u8> = (0..total).map(|i| padded[total - 1 - i] ^ 0xA5).collect();
let gf256 = Gf::f256();
let gaps_syms: Vec<usize> = scrambled[..fp.gaps_data]
.iter()
.map(|&b| b as usize)
.collect();
let mut gaps_bits = symbols_to_bits(&rs_encode(&gf256, &gaps_syms, fp.gaps_parity), 8);
let gap_zeros = gaps_bits.iter().filter(|&&b| !b).count();
let inverted = gap_zeros <= 51;
if inverted {
for b in &mut gaps_bits {
*b = !*b;
}
}
let gf16 = Gf::f16();
let meta = [version >> 3, usize::from(inverted) | ((version & 7) << 1)];
let meta_bits = symbols_to_bits(&rs_encode(&gf16, &meta, 2), 4);
let arcs_syms: Vec<usize> = scrambled[total - fp.arcs_data..]
.iter()
.map(|&b| b as usize)
.collect();
let arcs_bits = symbols_to_bits(&rs_encode(&gf256, &arcs_syms, fp.arcs_parity), 8);
let mut pre = [false; 128];
pre[..16].copy_from_slice(&meta_bits);
pre[16..120].copy_from_slice(&gaps_bits);
pre[120..].copy_from_slice(&TEMPLATE_BITS);
let zero_count = pre.iter().filter(|&&b| !b).count();
let mut out = vec![false; 129 + zero_count];
for (i, &b) in pre.iter().enumerate() {
out[GAPS_BITS_ORDER_LUT[i]] = b;
}
out[129..129 + 56].copy_from_slice(&arcs_bits);
let extra = zero_count.saturating_sub(56);
if extra > 0 && extra <= gaps_bits.len() {
out[185..185 + extra].copy_from_slice(&gaps_bits[..extra]);
}
Ok(out)
}
pub fn decode_payload(bits: &[bool]) -> Result<[u8; 16]> {
if bits.len() < 128 {
return Err(Error::undecodable("App Clip bit vector shorter than 128"));
}
let mut pre = [false; 128];
for (i, p) in pre.iter_mut().enumerate() {
*p = bits[GAPS_BITS_ORDER_LUT[i]];
}
let gf16 = Gf::f16();
let meta_cw: Vec<usize> = (0..4)
.map(|i| bits_to_symbol(&pre[i * 4..i * 4 + 4]))
.collect();
let meta = rs_decode(&gf16, &meta_cw, 2)
.ok_or_else(|| Error::undecodable("App Clip metadata RS failed"))?;
let version = (meta[0] << 3) | (meta[1] >> 1);
let inverted = meta[1] & 1 == 1;
if version > 1 {
return Err(Error::undecodable("invalid App Clip codec version"));
}
let fp = format_params(version);
let mut gap_bits: Vec<bool> = pre[16..120].to_vec();
if inverted {
for b in &mut gap_bits {
*b = !*b;
}
}
let gap_cw: Vec<usize> = (0..13)
.map(|i| bits_to_symbol(&gap_bits[i * 8..i * 8 + 8]))
.collect();
let gf256 = Gf::f256();
let gap_syms = rs_decode(&gf256, &gap_cw, fp.gaps_parity)
.ok_or_else(|| Error::undecodable("App Clip gaps RS failed"))?;
let total = fp.gaps_data + fp.arcs_data;
let mut scrambled = vec![0u8; total];
for i in 0..fp.gaps_data {
scrambled[i] = gap_syms[i] as u8;
}
if bits.len() >= 128 + 57 {
if bits[128] {
return Err(Error::undecodable("invalid App Clip separator bit"));
}
let arc_cw: Vec<usize> = (0..7)
.map(|i| bits_to_symbol(&bits[129 + i * 8..129 + i * 8 + 8]))
.collect();
let arc_syms = rs_decode(&gf256, &arc_cw, fp.arcs_parity)
.ok_or_else(|| Error::undecodable("App Clip arcs RS failed"))?;
for i in 0..fp.arcs_data {
scrambled[fp.gaps_data + i] = arc_syms[i] as u8;
}
}
let mut payload = [0u8; 16];
for i in 0..total {
payload[16 - total + (total - 1 - i)] = scrambled[i] ^ 0xA5;
}
Ok(payload)
}
fn symbols_to_bits(symbols: &[usize], bits_per: usize) -> Vec<bool> {
let mut out = Vec::with_capacity(symbols.len() * bits_per);
for &s in symbols {
for j in (0..bits_per).rev() {
out.push((s >> j) & 1 == 1);
}
}
out
}
fn bits_to_symbol(bits: &[bool]) -> usize {
bits.iter()
.fold(0usize, |acc, &b| (acc << 1) | usize::from(b))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn payload_roundtrip_both_versions() {
let mut short = [0u8; 16];
short[14] = 0x42;
short[15] = 0xAA;
let mut long = [0u8; 16];
for (i, b) in long.iter_mut().enumerate() {
*b = (i as u8).wrapping_mul(17).wrapping_add(1);
}
for payload in [short, long] {
let bits = encode_payload(&payload).unwrap();
assert!(bits.len() >= 129, "vector too short: {}", bits.len());
let back = decode_payload(&bits).unwrap();
assert_eq!(back, payload);
}
}
#[test]
fn gap_ring_majority_zero() {
for seed in 0u8..16 {
let payload = [seed.wrapping_mul(37); 16];
let bits = encode_payload(&payload).unwrap();
let mut pre = [false; 128];
for (i, p) in pre.iter_mut().enumerate() {
*p = bits[GAPS_BITS_ORDER_LUT[i]];
}
let zeros = pre[16..120].iter().filter(|&&b| !b).count();
assert!(zeros >= 52, "gap zeros {zeros} < 52 for seed {seed}");
}
}
}