254 lines
7.9 KiB
Rust
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)
|
|
}
|
|
}
|