use std::path::Path; #[derive(Clone, Debug, PartialEq, Eq)] pub struct SapPublicationPoint { pub header: SapPointHeader, pub manifest: Option, pub objects: Vec, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct SapPointHeader { pub version: u8, pub manifest_uri: String, pub rpki_notify: Option, 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, pub crl_uri: String, pub crl_der: Vec, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct SapStoredObject { pub uri: String, pub hash_sha256: Option<[u8; 32]>, pub content_der: Vec, } #[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 { 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 { 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 { Ok(self.read_exact(1)?[0]) } fn read_u32_be(&mut self) -> Result { 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 { 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 { 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 { 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 { 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, SapDecodeError> { if self.remaining() == 0 { return Ok(None); } Ok(Some(self.read_string_u32()?)) } fn read_option_string_u32(&mut self) -> Result, 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, 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) } }