prikk_object/payload/
attestation.rs1use prikk_error::{PrikkError, Result};
4
5use crate::{CanonicalEncode, CanonicalWriter, ObjectId};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
9#[repr(u16)]
10pub enum AttestationStatus {
11 Pass = 1,
13 Warn = 2,
15 Fail = 3,
17 Quarantine = 4,
19}
20
21impl AttestationStatus {
22 #[must_use]
24 pub const fn code(self) -> u16 {
25 self as u16
26 }
27}
28
29#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct PluginResultEntry {
34 pub plugin_id: String,
36 pub plugin_version: String,
38 pub status: AttestationStatus,
40 pub report_hash: Vec<u8>,
42 pub finding_count: u32,
44}
45
46impl CanonicalEncode for PluginResultEntry {
47 fn encode_canonical(&self, writer: &mut CanonicalWriter) -> Result<()> {
48 writer.field_string(1, &self.plugin_id)?;
49 writer.field_string(2, &self.plugin_version)?;
50 writer.field_enum_u16(3, self.status.code())?;
51 writer.field_bytes(4, &self.report_hash)?;
52 writer.field_u32(5, self.finding_count)?;
53 Ok(())
54 }
55}
56
57#[derive(Debug, Clone, PartialEq, Eq)]
59pub struct AttestationPayload {
60 pub target_block_id: ObjectId,
62 pub policy_version: String,
64 pub plugin_set_hash: Vec<u8>,
66 pub results: Vec<PluginResultEntry>,
68 pub status: AttestationStatus,
70 pub created_at: u64,
72 pub is_reproducible_offline: bool,
74}
75
76fn results_sorted_by_plugin_id(results: &[PluginResultEntry]) -> bool {
80 results.windows(2).all(|pair| match pair {
81 [a, b] => a.plugin_id.as_bytes() < b.plugin_id.as_bytes(),
82 _ => true,
83 })
84}
85
86impl CanonicalEncode for AttestationPayload {
87 fn encode_canonical(&self, writer: &mut CanonicalWriter) -> Result<()> {
88 if !results_sorted_by_plugin_id(&self.results) {
89 return Err(PrikkError::CanonicalEncoding(
90 "plugin results must be strictly ordered and unique by plugin_id".to_string(),
91 ));
92 }
93 writer.field_object_id(1, &self.target_block_id)?;
94 writer.field_string(2, &self.policy_version)?;
95 writer.field_bytes(3, &self.plugin_set_hash)?;
96 writer.repeated_record_list(4, &self.results)?;
97 writer.field_enum_u16(5, self.status.code())?;
98 writer.field_u64(6, self.created_at)?;
99 writer.field_bool(7, self.is_reproducible_offline)?;
100 Ok(())
101 }
102}