use std::io::Read;
use std::sync::OnceLock;
use serde::de::DeserializeOwned;
use sha2::{Digest, Sha256};
use crate::Error;
#[derive(Debug)]
pub struct EmbeddedBundle {
name: &'static str,
gzip: &'static [u8],
sha256: &'static str,
decoded: OnceLock<Result<Vec<u8>, Error>>,
}
impl EmbeddedBundle {
#[must_use]
pub const fn new(name: &'static str, gzip: &'static [u8], sha256: &'static str) -> Self {
Self {
name,
gzip,
sha256,
decoded: OnceLock::new(),
}
}
#[must_use]
pub const fn name(&self) -> &'static str {
self.name
}
pub fn bytes(&self) -> Result<&[u8], Error> {
let cached = self.decoded.get_or_init(|| self.decode());
match cached {
Ok(bytes) => Ok(bytes),
Err(error) => Err(error.clone()),
}
}
fn decode(&self) -> Result<Vec<u8>, Error> {
decode_gzip(self.name, self.gzip, self.sha256)
}
}
#[derive(Debug)]
pub struct JsonBundle<T> {
bundle: EmbeddedBundle,
parsed: OnceLock<Result<T, Error>>,
}
impl<T> JsonBundle<T> {
#[must_use]
pub const fn new(bundle: EmbeddedBundle) -> Self {
Self {
bundle,
parsed: OnceLock::new(),
}
}
}
impl<T> JsonBundle<T>
where
T: DeserializeOwned + Send + Sync,
{
pub fn get(&self) -> Result<&T, Error> {
let cached = self.parsed.get_or_init(|| {
let payload = self.bundle.bytes()?;
serde_json::from_slice(payload).map_err(|error| Error::DataJson {
name: self.bundle.name.to_owned(),
reason: error.to_string(),
})
});
match cached {
Ok(value) => Ok(value),
Err(error) => Err(error.clone()),
}
}
#[must_use]
pub fn name(&self) -> &'static str {
self.bundle.name()
}
}
pub fn load_external_gzip(name: &str, gzip: &[u8], sha256: &str) -> Result<Vec<u8>, Error> {
decode_gzip(name, gzip, sha256)
}
pub fn load_external_json<T>(name: &str, gzip: &[u8], sha256: &str) -> Result<T, Error>
where
T: DeserializeOwned,
{
let payload = decode_gzip(name, gzip, sha256)?;
serde_json::from_slice(&payload).map_err(|error| Error::DataJson {
name: name.to_owned(),
reason: error.to_string(),
})
}
fn decode_gzip(name: &str, gzip: &[u8], sha256: &str) -> Result<Vec<u8>, Error> {
let mut decoder = flate2::read::GzDecoder::new(gzip);
let mut payload = Vec::new();
decoder
.read_to_end(&mut payload)
.map_err(|error| Error::DataCompression {
name: name.to_owned(),
reason: error.to_string(),
})?;
verify_sha256(name, &payload, sha256)?;
Ok(payload)
}
fn verify_sha256(name: &str, payload: &[u8], expected: &str) -> Result<(), Error> {
let expected = decode_digest(name, expected)?;
let actual = Sha256::digest(payload);
if actual.as_slice() != expected.as_slice() {
return Err(Error::DataChecksum {
name: name.to_owned(),
});
}
Ok(())
}
fn decode_digest(name: &str, hex: &str) -> Result<[u8; 32], Error> {
let bytes = hex.as_bytes();
if bytes.len() != 64 {
return Err(Error::DataDigest {
name: name.to_owned(),
});
}
let mut digest = [0u8; 32];
for (slot, pair) in digest.iter_mut().zip(bytes.as_chunks::<2>().0) {
let invalid = || Error::DataDigest {
name: name.to_owned(),
};
let [high, low] = *pair;
let high = hex_nibble(high).ok_or_else(invalid)?;
let low = hex_nibble(low).ok_or_else(invalid)?;
*slot = (high << 4) | low;
}
Ok(digest)
}
fn hex_nibble(byte: u8) -> Option<u8> {
match byte {
b'0'..=b'9' => Some(byte - b'0'),
b'a'..=b'f' => Some(byte - b'a' + 10),
b'A'..=b'F' => Some(byte - b'A' + 10),
_ => None,
}
}