use std::cmp::Ordering;
use prikk_error::{PrikkError, Result};
use crate::{CanonicalEncode, CanonicalWriter, ObjectId, ObjectType, Signature};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct SignatureEnvelopeIssues {
pub malformed_shape: bool,
pub duplicate: bool,
pub noncanonical_order: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ObjectEnvelope {
pub object_type: ObjectType,
pub schema_version: u32,
pub canonical_payload: Vec<u8>,
pub signatures: Vec<Signature>,
}
impl ObjectEnvelope {
#[must_use]
pub fn unsigned(
object_type: ObjectType,
schema_version: u32,
canonical_payload: Vec<u8>,
) -> Self {
Self {
object_type,
schema_version,
canonical_payload,
signatures: Vec::new(),
}
}
#[must_use]
pub fn object_id(&self) -> ObjectId {
ObjectId::from_canonical_payload(
self.object_type,
self.schema_version,
&self.canonical_payload,
)
}
pub fn validate(&self) -> Result<()> {
if self.schema_version == 0 {
return Err(PrikkError::UnsupportedFormatVersion(0));
}
for signature in &self.signatures {
signature.validate()?;
}
Ok(())
}
pub fn signature_issues(&self) -> Result<SignatureEnvelopeIssues> {
self.validate()?;
let mut issues = SignatureEnvelopeIssues {
malformed_shape: self
.signatures
.iter()
.any(|signature| signature.validate_shape().is_err()),
..SignatureEnvelopeIssues::default()
};
let mut signatures_by_tuple = self.signatures.iter().collect::<Vec<_>>();
signatures_by_tuple.sort_unstable_by(|left, right| left.canonical_cmp(right));
issues.duplicate = signatures_by_tuple
.windows(2)
.any(|pair| matches!(pair, [left, right] if left.canonical_cmp(right).is_eq()));
for pair in self.signatures.windows(2) {
let [left, right] = pair else {
continue;
};
match left.canonical_cmp(right) {
Ordering::Equal => {}
Ordering::Greater => issues.noncanonical_order = true,
Ordering::Less => {}
}
}
Ok(issues)
}
pub fn validate_strict(&self) -> Result<()> {
let issues = self.signature_issues()?;
if issues.malformed_shape {
return Err(PrikkError::InvalidSignature(
"envelope contains a signature with malformed algorithm shape".to_string(),
));
}
if issues.duplicate {
return Err(PrikkError::InvalidSignature(
"envelope contains a duplicate signature tuple".to_string(),
));
}
if issues.noncanonical_order {
return Err(PrikkError::InvalidSignature(
"envelope signatures are not in canonical order".to_string(),
));
}
Ok(())
}
pub fn add_signature(&mut self, signature: Signature) -> Result<()> {
self.validate_strict()?;
signature.validate()?;
signature.validate_shape()?;
match self
.signatures
.binary_search_by(|existing| existing.canonical_cmp(&signature))
{
Ok(_) => Err(PrikkError::InvalidSignature(
"envelope contains a duplicate signature tuple".to_string(),
)),
Err(index) => {
self.signatures.insert(index, signature);
Ok(())
}
}
}
}
impl CanonicalEncode for ObjectEnvelope {
fn encode_canonical(&self, writer: &mut CanonicalWriter) -> Result<()> {
self.validate_strict()?;
writer.field_u32(1, self.object_type.code() as u32)?;
writer.field_u32(2, self.schema_version)?;
writer.field_bytes(3, &self.canonical_payload)?;
writer.repeated_record(4, &self.signatures)?;
Ok(())
}
}
#[cfg(test)]
mod tests;