use crate::error::{Error, Result};
use byteorder::{LittleEndian, ReadBytesExt};
use serde::{Deserialize, Serialize};
use serde_with::{hex::Hex, serde_as, DisplayFromStr};
use std::{
collections::HashMap,
fmt::{self, Display},
path::PathBuf,
str::FromStr,
};
#[cfg(test)]
pub(crate) static TEST_ASAR: &[u8] = include_bytes!("../data/test.asar");
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Header {
File(File),
Directory { files: HashMap<String, Self> },
Link { link: PathBuf },
}
impl Header {
pub(crate) fn new() -> Self {
Self::Directory {
files: HashMap::new(),
}
}
pub fn read<Read: ReadBytesExt>(data: &mut Read) -> Result<(Self, usize)> {
data.read_u32::<LittleEndian>()?; let header_size = data.read_u32::<LittleEndian>()? as usize;
data.read_u32::<LittleEndian>()?;
let json_size = data.read_u32::<LittleEndian>()? as usize;
let mut bytes = vec![0_u8; json_size];
data.read_exact(&mut bytes)?;
Ok((serde_json::from_slice(&bytes)?, header_size + 8))
}
}
#[serde_as]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(untagged)]
pub enum FileLocation {
Offset {
#[serde_as(as = "DisplayFromStr")]
offset: usize,
},
Unpacked {
#[serde(skip_serializing_if = "is_false")]
unpacked: bool,
},
}
impl FileLocation {
#[inline]
pub const fn offset(offset: usize) -> Self {
FileLocation::Offset { offset }
}
#[inline]
pub const fn unpacked() -> Self {
FileLocation::Unpacked { unpacked: true }
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct File {
#[serde(flatten)]
location: FileLocation,
size: usize,
#[serde(skip_serializing_if = "is_false", default = "default_false")]
executable: bool,
#[serde(skip_serializing_if = "Option::is_none")]
integrity: Option<FileIntegrity>,
}
impl File {
pub(crate) const fn new(
location: FileLocation,
size: usize,
executable: bool,
integrity: Option<FileIntegrity>,
) -> Self {
Self {
location,
size,
executable,
integrity,
}
}
#[inline]
pub const fn location(&self) -> FileLocation {
self.location
}
#[inline]
pub const fn offset(&self) -> Option<usize> {
match self.location {
FileLocation::Offset { offset } => Some(offset),
_ => None,
}
}
#[inline]
pub const fn unpacked(&self) -> bool {
matches!(self.location, FileLocation::Unpacked { .. })
}
#[inline]
pub const fn size(&self) -> usize {
self.size
}
#[inline]
pub const fn executable(&self) -> bool {
self.executable
}
#[inline]
pub const fn integrity(&self) -> Option<&FileIntegrity> {
self.integrity.as_ref()
}
}
#[serde_as]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FileIntegrity {
algorithm: HashAlgorithm,
#[serde_as(as = "Hex")]
hash: Vec<u8>,
block_size: usize,
#[serde_as(as = "Vec<Hex>")]
blocks: Vec<Vec<u8>>,
}
impl FileIntegrity {
pub(crate) fn new(
algorithm: HashAlgorithm,
hash: Vec<u8>,
block_size: usize,
blocks: Vec<Vec<u8>>,
) -> Self {
Self {
algorithm,
hash,
block_size,
blocks,
}
}
#[inline]
pub const fn algorithm(&self) -> HashAlgorithm {
self.algorithm
}
#[inline]
pub fn hash(&self) -> &[u8] {
&self.hash
}
#[inline]
pub const fn block_size(&self) -> usize {
self.block_size
}
#[inline]
pub fn blocks(&self) -> &[Vec<u8>] {
&self.blocks
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[non_exhaustive]
pub enum HashAlgorithm {
#[serde(rename = "SHA256")]
Sha256,
}
impl Display for HashAlgorithm {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Sha256 => write!(f, "SHA256"),
}
}
}
impl FromStr for HashAlgorithm {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
match s.trim().to_lowercase().as_str() {
"sha256" | "sha-256" => Ok(Self::Sha256),
_ => Err(Error::InvalidHashAlgorithm(s.to_string())),
}
}
}
const fn is_false(b: &bool) -> bool {
!*b
}
const fn default_false() -> bool {
false
}
#[cfg(test)]
mod test {
use super::{Header, TEST_ASAR};
static TEST_ASAR_JSON: &str = include_str!("../data/test.asar.json");
#[test]
pub fn test_read() {
let mut asar = TEST_ASAR;
let (header, _) = Header::read(&mut asar).expect("failed to read header");
let expected =
serde_json::from_str::<Header>(TEST_ASAR_JSON).expect("failed to decode expected");
assert_eq!(header, expected);
}
}