use alloc::vec::Vec;
const ENVELOPE_MAGIC: &[u8; 8] = b"SPGENV01";
const ENVELOPE_VERSION_V1: u8 = 1;
const ENVELOPE_VERSION_V2: u8 = 2;
const ENVELOPE_VERSION_V3: u8 = 3;
const ENVELOPE_VERSION_V4: u8 = 4;
const ENVELOPE_VERSION_V5: u8 = 5;
pub(crate) fn build_envelope(
catalog: &[u8],
users: &[u8],
pubs: &[u8],
subs: &[u8],
stats: &[u8],
) -> Vec<u8> {
let mut out = Vec::with_capacity(
8 + 1
+ 4
+ catalog.len()
+ 4
+ users.len()
+ 4
+ pubs.len()
+ 4
+ subs.len()
+ 4
+ stats.len()
+ 4,
);
out.extend_from_slice(ENVELOPE_MAGIC);
out.push(ENVELOPE_VERSION_V5);
out.extend_from_slice(
&u32::try_from(catalog.len())
.expect("≤ 4G catalog")
.to_le_bytes(),
);
out.extend_from_slice(catalog);
out.extend_from_slice(
&u32::try_from(users.len())
.expect("≤ 4G users")
.to_le_bytes(),
);
out.extend_from_slice(users);
out.extend_from_slice(
&u32::try_from(pubs.len())
.expect("≤ 4G publications")
.to_le_bytes(),
);
out.extend_from_slice(pubs);
out.extend_from_slice(
&u32::try_from(subs.len())
.expect("≤ 4G subscriptions")
.to_le_bytes(),
);
out.extend_from_slice(subs);
out.extend_from_slice(
&u32::try_from(stats.len())
.expect("≤ 4G statistics")
.to_le_bytes(),
);
out.extend_from_slice(stats);
let crc = spg_crypto::crc32::crc32(&out);
out.extend_from_slice(&crc.to_le_bytes());
out
}
pub(crate) enum EnvelopeParse<'a> {
Bare,
Pair {
catalog: &'a [u8],
users: &'a [u8],
publications: Option<&'a [u8]>,
subscriptions: Option<&'a [u8]>,
statistics: Option<&'a [u8]>,
},
CrcMismatch {
expected: u32,
computed: u32,
},
}
pub(crate) fn split_envelope(buf: &[u8]) -> EnvelopeParse<'_> {
if buf.len() < 8 + 1 + 4 || &buf[..8] != ENVELOPE_MAGIC {
return EnvelopeParse::Bare;
}
let version = buf[8];
if !matches!(
version,
ENVELOPE_VERSION_V1
| ENVELOPE_VERSION_V2
| ENVELOPE_VERSION_V3
| ENVELOPE_VERSION_V4
| ENVELOPE_VERSION_V5
) {
return EnvelopeParse::Bare;
}
let mut p = 9usize;
let Some(cat_len_bytes) = buf.get(p..p + 4) else {
return EnvelopeParse::Bare;
};
let Ok(cat_len_arr) = cat_len_bytes.try_into() else {
return EnvelopeParse::Bare;
};
let cat_len = u32::from_le_bytes(cat_len_arr) as usize;
p += 4;
if p + cat_len + 4 > buf.len() {
return EnvelopeParse::Bare;
}
let catalog = &buf[p..p + cat_len];
p += cat_len;
let Some(user_len_bytes) = buf.get(p..p + 4) else {
return EnvelopeParse::Bare;
};
let Ok(user_len_arr) = user_len_bytes.try_into() else {
return EnvelopeParse::Bare;
};
let user_len = u32::from_le_bytes(user_len_arr) as usize;
p += 4;
if p + user_len > buf.len() {
return EnvelopeParse::Bare;
}
let users = &buf[p..p + user_len];
p += user_len;
let publications = if matches!(
version,
ENVELOPE_VERSION_V3 | ENVELOPE_VERSION_V4 | ENVELOPE_VERSION_V5
) {
let Some(pubs_len_bytes) = buf.get(p..p + 4) else {
return EnvelopeParse::Bare;
};
let Ok(pubs_len_arr) = pubs_len_bytes.try_into() else {
return EnvelopeParse::Bare;
};
let pubs_len = u32::from_le_bytes(pubs_len_arr) as usize;
p += 4;
if p + pubs_len > buf.len() {
return EnvelopeParse::Bare;
}
let pubs_slice = &buf[p..p + pubs_len];
p += pubs_len;
Some(pubs_slice)
} else {
None
};
let subscriptions = if matches!(version, ENVELOPE_VERSION_V4 | ENVELOPE_VERSION_V5) {
let Some(subs_len_bytes) = buf.get(p..p + 4) else {
return EnvelopeParse::Bare;
};
let Ok(subs_len_arr) = subs_len_bytes.try_into() else {
return EnvelopeParse::Bare;
};
let subs_len = u32::from_le_bytes(subs_len_arr) as usize;
p += 4;
if p + subs_len > buf.len() {
return EnvelopeParse::Bare;
}
let subs_slice = &buf[p..p + subs_len];
p += subs_len;
Some(subs_slice)
} else {
None
};
let statistics = if version == ENVELOPE_VERSION_V5 {
let Some(stats_len_bytes) = buf.get(p..p + 4) else {
return EnvelopeParse::Bare;
};
let Ok(stats_len_arr) = stats_len_bytes.try_into() else {
return EnvelopeParse::Bare;
};
let stats_len = u32::from_le_bytes(stats_len_arr) as usize;
p += 4;
if p + stats_len > buf.len() {
return EnvelopeParse::Bare;
}
let stats_slice = &buf[p..p + stats_len];
p += stats_len;
Some(stats_slice)
} else {
None
};
if matches!(
version,
ENVELOPE_VERSION_V2 | ENVELOPE_VERSION_V3 | ENVELOPE_VERSION_V4 | ENVELOPE_VERSION_V5
) {
if p + 4 != buf.len() {
return EnvelopeParse::Bare;
}
let Ok(crc_arr) = buf[p..p + 4].try_into() else {
return EnvelopeParse::Bare;
};
let expected = u32::from_le_bytes(crc_arr);
let computed = spg_crypto::crc32::crc32(&buf[..p]);
if expected != computed {
return EnvelopeParse::CrcMismatch { expected, computed };
}
} else if p != buf.len() {
return EnvelopeParse::Bare;
}
EnvelopeParse::Pair {
catalog,
users,
publications,
subscriptions,
statistics,
}
}