1use crate::bytes::BytesReader;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12#[repr(u8)]
13pub enum EloRecordKind {
14 DeltaSpan = 0x00,
15 Snapshot = 0x01,
16}
17
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct EloDeltaHeader {
20 pub peer_id: Vec<u8>,
21 pub start: u64,
22 pub end: u64,
23 pub key_id: String,
24 pub iv: [u8; 12],
25}
26
27#[derive(Debug, Clone, PartialEq, Eq)]
28pub struct EloSnapshotHeader {
29 pub vv: Vec<(Vec<u8>, u64)>,
30 pub key_id: String,
31 pub iv: [u8; 12],
32}
33
34#[derive(Debug, Clone, PartialEq, Eq)]
35pub enum EloHeader {
36 Delta(EloDeltaHeader),
37 Snapshot(EloSnapshotHeader),
38}
39
40#[derive(Debug, Clone, PartialEq, Eq)]
41pub struct ParsedEloRecord<'a> {
42 pub kind: EloRecordKind,
43 pub header: EloHeader,
44 pub aad: &'a [u8],
46 pub ct: &'a [u8],
48}
49
50pub fn decode_elo_container<'a>(data: &'a [u8]) -> Result<Vec<&'a [u8]>, String> {
53 let mut r = BytesReader::new(data);
54 let n = usize::try_from(r.read_uleb128()?).map_err(|_| "length too large".to_string())?;
55 let mut out: Vec<&[u8]> = Vec::with_capacity(n);
56 for _ in 0..n {
57 out.push(r.read_var_bytes()?);
58 }
59 if r.remaining() != 0 { return Err("ELO container has trailing bytes".into()); }
60 Ok(out)
61}
62
63pub fn parse_elo_record_header<'a>(record: &'a [u8]) -> Result<ParsedEloRecord<'a>, String> {
66 let mut r = BytesReader::new(record);
67 let kind_byte = r.read_byte()?;
68 match kind_byte {
69 0x00 => parse_delta(&mut r, record),
70 0x01 => parse_snapshot(&mut r, record),
71 _ => Err(format!("Unknown ELO record kind: 0x{:02x}", kind_byte)),
72 }
73}
74
75fn parse_delta<'a>(r: &mut BytesReader<'a>, record: &'a [u8]) -> Result<ParsedEloRecord<'a>, String> {
76 let peer_id = r.read_var_bytes()?.to_vec();
77 let start = r.read_uleb128()?;
78 let end = r.read_uleb128()?;
79 let key_id = r.read_var_string()?;
80 let iv_bytes = r.read_var_bytes()?;
81 let aad_len = r.position();
82 let ct = r.read_var_bytes()?;
83 if r.remaining() != 0 { return Err("ELO record trailing bytes".into()); }
84
85 if end <= start { return Err("Invalid ELO delta span: end must be > start".into()); }
87 if iv_bytes.len() != 12 { return Err("Invalid ELO delta span: IV must be 12 bytes".into()); }
88 if peer_id.len() > 64 { return Err("Invalid ELO delta span: peerId too long".into()); }
89 if key_id.as_bytes().len() > 64 { return Err("Invalid ELO delta span: keyId too long".into()); }
90
91 let mut iv = [0u8; 12];
92 iv.copy_from_slice(iv_bytes);
93
94 Ok(ParsedEloRecord {
95 kind: EloRecordKind::DeltaSpan,
96 header: EloHeader::Delta(EloDeltaHeader { peer_id, start, end, key_id, iv }),
97 aad: &record[..aad_len],
98 ct,
99 })
100}
101
102fn parse_snapshot<'a>(r: &mut BytesReader<'a>, record: &'a [u8]) -> Result<ParsedEloRecord<'a>, String> {
103 let count = usize::try_from(r.read_uleb128()?).map_err(|_| "vv length too large".to_string())?;
104 let mut vv: Vec<(Vec<u8>, u64)> = Vec::with_capacity(count);
105 for _ in 0..count {
106 let pid = r.read_var_bytes()?.to_vec();
107 let ctr = r.read_uleb128()?;
108 vv.push((pid, ctr));
109 }
110 let key_id = r.read_var_string()?;
111 let iv_bytes = r.read_var_bytes()?;
112 let aad_len = r.position();
113 let ct = r.read_var_bytes()?;
114 if r.remaining() != 0 { return Err("ELO record trailing bytes".into()); }
115
116 if iv_bytes.len() != 12 { return Err("Invalid ELO snapshot: IV must be 12 bytes".into()); }
117 if key_id.as_bytes().len() > 64 { return Err("Invalid ELO snapshot: keyId too long".into()); }
118 if !is_sorted_by_bytes(&vv) { return Err("Invalid ELO snapshot: vv not sorted by peer id bytes".into()); }
120
121 let mut iv = [0u8; 12];
122 iv.copy_from_slice(iv_bytes);
123
124 Ok(ParsedEloRecord {
125 kind: EloRecordKind::Snapshot,
126 header: EloHeader::Snapshot(EloSnapshotHeader { vv, key_id, iv }),
127 aad: &record[..aad_len],
128 ct,
129 })
130}
131
132fn is_sorted_by_bytes(vv: &[(Vec<u8>, u64)]) -> bool {
133 vv.windows(2).all(|w| {
134 let a = &w[0].0;
135 let b = &w[1].0;
136 compare_bytes(a, b) <= 0
137 })
138}
139
140fn compare_bytes(a: &[u8], b: &[u8]) -> i32 {
141 let n = a.len().min(b.len());
142 for i in 0..n {
143 let da = a[i];
144 let db = b[i];
145 if da != db { return (da as i32) - (db as i32); }
146 }
147 (a.len() as i32) - (b.len() as i32)
148}