1use prikk_error::{PrikkError, Result};
4
5use crate::{CanonicalEncode, CanonicalWriter, ObjectId, ObjectType, Signature};
6
7#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct ObjectEnvelope {
10 pub object_type: ObjectType,
12 pub schema_version: u32,
14 pub canonical_payload: Vec<u8>,
16 pub signatures: Vec<Signature>,
18}
19
20impl ObjectEnvelope {
21 #[must_use]
23 pub fn unsigned(
24 object_type: ObjectType,
25 schema_version: u32,
26 canonical_payload: Vec<u8>,
27 ) -> Self {
28 Self {
29 object_type,
30 schema_version,
31 canonical_payload,
32 signatures: Vec::new(),
33 }
34 }
35
36 #[must_use]
38 pub fn object_id(&self) -> ObjectId {
39 ObjectId::from_canonical_payload(
40 self.object_type,
41 self.schema_version,
42 &self.canonical_payload,
43 )
44 }
45
46 pub fn validate(&self) -> Result<()> {
48 if self.schema_version == 0 {
49 return Err(PrikkError::UnsupportedFormatVersion(0));
50 }
51 for signature in &self.signatures {
52 signature.validate()?;
53 }
54 Ok(())
55 }
56
57 pub fn add_signature(&mut self, signature: Signature) -> Result<()> {
59 signature.validate()?;
60 self.signatures.push(signature);
61 self.signatures.sort_by(|a, b| {
62 (&a.key_id, a.signer_role, a.created_at).cmp(&(&b.key_id, b.signer_role, b.created_at))
63 });
64 Ok(())
65 }
66}
67
68impl CanonicalEncode for ObjectEnvelope {
69 fn encode_canonical(&self, writer: &mut CanonicalWriter) -> Result<()> {
70 writer.field_u32(1, self.object_type.code() as u32)?;
71 writer.field_u32(2, self.schema_version)?;
72 writer.field_bytes(3, &self.canonical_payload)?;
73 writer.repeated_record(4, &self.signatures)?;
74 Ok(())
75 }
76}
77
78#[cfg(test)]
79mod tests {
80 use super::ObjectEnvelope;
81 use crate::{ObjectType, Signature, SignatureAlgorithm, SignerRole};
82
83 #[test]
84 fn signature_does_not_change_object_id() {
85 let mut envelope = ObjectEnvelope::unsigned(ObjectType::Patch, 1, b"payload".to_vec());
86 let before = envelope.object_id();
87 let signature = Signature {
88 algorithm: SignatureAlgorithm::Ed25519,
89 key_id: "k1".to_string(),
90 signature_bytes: vec![1, 2, 3],
91 created_at: 1,
92 signer_role: SignerRole::Author,
93 };
94 assert!(envelope.add_signature(signature).is_ok());
95 assert_eq!(before, envelope.object_id());
96 }
97}