2026-03-04 11:12:53 +08:00

254 lines
7.9 KiB
Rust

use std::path::Path;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SapPublicationPoint {
pub header: SapPointHeader,
pub manifest: Option<SapStoredManifest>,
pub objects: Vec<SapStoredObject>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SapPointHeader {
pub version: u8,
pub manifest_uri: String,
pub rpki_notify: Option<String>,
pub update_status: SapUpdateStatus,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum SapUpdateStatus {
Success { time_utc: time::OffsetDateTime },
LastAttempt { time_utc: time::OffsetDateTime },
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SapStoredManifest {
pub not_after_utc: time::OffsetDateTime,
pub manifest_number: [u8; 20],
pub this_update_utc: time::OffsetDateTime,
pub ca_repository: String,
pub manifest_der: Vec<u8>,
pub crl_uri: String,
pub crl_der: Vec<u8>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SapStoredObject {
pub uri: String,
pub hash_sha256: Option<[u8; 32]>,
pub content_der: Vec<u8>,
}
#[derive(Debug, thiserror::Error)]
pub enum SapDecodeError {
#[error("I/O error reading {path}: {err}")]
Io { path: String, err: String },
#[error("SAP decode error: {0}")]
Decode(String),
}
impl SapPublicationPoint {
pub fn decode_path(path: &Path) -> Result<Self, SapDecodeError> {
let bytes = std::fs::read(path).map_err(|e| SapDecodeError::Io {
path: path.display().to_string(),
err: e.to_string(),
})?;
Self::decode_bytes(&bytes)
}
pub fn decode_bytes(bytes: &[u8]) -> Result<Self, SapDecodeError> {
let mut cur = Cursor::new(bytes);
let version = cur.read_u8()?;
let manifest_uri = cur.read_string_u32()?;
let rpki_notify = cur.read_option_string_u32()?;
let update_kind = cur.read_u8()?;
let update_time_utc = cur.read_time_utc_i64_be()?;
let update_status = match update_kind {
0 => SapUpdateStatus::Success {
time_utc: update_time_utc,
},
1 => SapUpdateStatus::LastAttempt {
time_utc: update_time_utc,
},
_ => {
return Err(SapDecodeError::Decode(format!(
"invalid update_status kind: {update_kind}"
)));
}
};
let header = SapPointHeader {
version,
manifest_uri,
rpki_notify,
update_status: update_status.clone(),
};
match update_status {
SapUpdateStatus::LastAttempt { .. } => Ok(SapPublicationPoint {
header,
manifest: None,
objects: Vec::new(),
}),
SapUpdateStatus::Success { .. } => {
let not_after_utc = cur.read_time_utc_i64_be()?;
let manifest_number = cur.read_array_20()?;
let this_update_utc = cur.read_time_utc_i64_be()?;
let ca_repository = cur.read_string_u32()?;
let manifest_der = cur.read_bytes_u64_vec()?;
let crl_uri = cur.read_string_u32()?;
let crl_der = cur.read_bytes_u64_vec()?;
let manifest = SapStoredManifest {
not_after_utc,
manifest_number,
this_update_utc,
ca_repository,
manifest_der,
crl_uri,
crl_der,
};
let mut objects = Vec::new();
while cur.remaining() > 0 {
let uri = match cur.try_read_string_u32() {
Ok(Some(v)) => v,
Ok(None) => break,
Err(e) => return Err(e),
};
let hash_type = cur.read_u8()?;
let hash_sha256 = match hash_type {
0 => None,
1 => Some(cur.read_array_32()?),
other => {
return Err(SapDecodeError::Decode(format!(
"unsupported hash_type {other} for stored object {uri}"
)));
}
};
let content_der = cur.read_bytes_u64_vec()?;
objects.push(SapStoredObject {
uri,
hash_sha256,
content_der,
});
}
Ok(SapPublicationPoint {
header,
manifest: Some(manifest),
objects,
})
}
}
}
}
struct Cursor<'a> {
bytes: &'a [u8],
pos: usize,
}
impl<'a> Cursor<'a> {
fn new(bytes: &'a [u8]) -> Self {
Self { bytes, pos: 0 }
}
fn remaining(&self) -> usize {
self.bytes.len().saturating_sub(self.pos)
}
fn read_exact(&mut self, len: usize) -> Result<&'a [u8], SapDecodeError> {
if len > self.remaining() {
return Err(SapDecodeError::Decode(format!(
"truncated input: need {len} bytes, have {}",
self.remaining()
)));
}
let start = self.pos;
self.pos += len;
Ok(&self.bytes[start..start + len])
}
fn read_u8(&mut self) -> Result<u8, SapDecodeError> {
Ok(self.read_exact(1)?[0])
}
fn read_u32_be(&mut self) -> Result<u32, SapDecodeError> {
let b = self.read_exact(4)?;
Ok(u32::from_be_bytes([b[0], b[1], b[2], b[3]]))
}
fn read_u64_be(&mut self) -> Result<u64, SapDecodeError> {
let b = self.read_exact(8)?;
Ok(u64::from_be_bytes([
b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
]))
}
fn read_i64_be(&mut self) -> Result<i64, SapDecodeError> {
let b = self.read_exact(8)?;
Ok(i64::from_be_bytes([
b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
]))
}
fn read_time_utc_i64_be(&mut self) -> Result<time::OffsetDateTime, SapDecodeError> {
let ts = self.read_i64_be()?;
time::OffsetDateTime::from_unix_timestamp(ts)
.map_err(|e| SapDecodeError::Decode(format!("invalid unix timestamp {ts}: {e}")))
}
fn read_string_u32(&mut self) -> Result<String, SapDecodeError> {
let len = self.read_u32_be()? as usize;
let b = self.read_exact(len)?;
std::str::from_utf8(b)
.map(|s| s.to_string())
.map_err(|e| SapDecodeError::Decode(format!("invalid UTF-8 string: {e}")))
}
fn try_read_string_u32(&mut self) -> Result<Option<String>, SapDecodeError> {
if self.remaining() == 0 {
return Ok(None);
}
Ok(Some(self.read_string_u32()?))
}
fn read_option_string_u32(&mut self) -> Result<Option<String>, SapDecodeError> {
let len = self.read_u32_be()? as usize;
if len == 0 {
return Ok(None);
}
let b = self.read_exact(len)?;
let s = std::str::from_utf8(b)
.map_err(|e| SapDecodeError::Decode(format!("invalid UTF-8 string: {e}")))?;
Ok(Some(s.to_string()))
}
fn read_bytes_u64_vec(&mut self) -> Result<Vec<u8>, SapDecodeError> {
let len_u64 = self.read_u64_be()?;
let len = usize::try_from(len_u64).map_err(|_| {
SapDecodeError::Decode(format!("data block too large for this system: {len_u64}"))
})?;
Ok(self.read_exact(len)?.to_vec())
}
fn read_array_20(&mut self) -> Result<[u8; 20], SapDecodeError> {
let b = self.read_exact(20)?;
let mut out = [0u8; 20];
out.copy_from_slice(b);
Ok(out)
}
fn read_array_32(&mut self) -> Result<[u8; 32], SapDecodeError> {
let b = self.read_exact(32)?;
let mut out = [0u8; 32];
out.copy_from_slice(b);
Ok(out)
}
}