use prikk_error::{PrikkError, Result};
use crate::{CanonicalEncode, CanonicalWriter, ObjectId};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(u16)]
pub enum AttestationStatus {
Pass = 1,
Warn = 2,
Fail = 3,
Quarantine = 4,
}
impl AttestationStatus {
#[must_use]
pub const fn code(self) -> u16 {
self as u16
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PluginResultEntry {
pub plugin_id: String,
pub plugin_version: String,
pub status: AttestationStatus,
pub report_hash: Vec<u8>,
pub finding_count: u32,
}
impl CanonicalEncode for PluginResultEntry {
fn encode_canonical(&self, writer: &mut CanonicalWriter) -> Result<()> {
writer.field_string(1, &self.plugin_id)?;
writer.field_string(2, &self.plugin_version)?;
writer.field_enum_u16(3, self.status.code())?;
writer.field_bytes(4, &self.report_hash)?;
writer.field_u32(5, self.finding_count)?;
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AttestationPayload {
pub target_block_id: ObjectId,
pub policy_version: String,
pub plugin_set_hash: Vec<u8>,
pub results: Vec<PluginResultEntry>,
pub status: AttestationStatus,
pub created_at: u64,
pub is_reproducible_offline: bool,
}
fn results_sorted_by_plugin_id(results: &[PluginResultEntry]) -> bool {
results.windows(2).all(|pair| match pair {
[a, b] => a.plugin_id.as_bytes() < b.plugin_id.as_bytes(),
_ => true,
})
}
impl CanonicalEncode for AttestationPayload {
fn encode_canonical(&self, writer: &mut CanonicalWriter) -> Result<()> {
if !results_sorted_by_plugin_id(&self.results) {
return Err(PrikkError::CanonicalEncoding(
"plugin results must be strictly ordered and unique by plugin_id".to_string(),
));
}
writer.field_object_id(1, &self.target_block_id)?;
writer.field_string(2, &self.policy_version)?;
writer.field_bytes(3, &self.plugin_set_hash)?;
writer.repeated_record_list(4, &self.results)?;
writer.field_enum_u16(5, self.status.code())?;
writer.field_u64(6, self.created_at)?;
writer.field_bool(7, self.is_reproducible_offline)?;
Ok(())
}
}