#[cfg(not(feature = "std"))]
extern crate alloc;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use crate::builder::error::{BuildError, MetadataError};
use crate::cms::enveloped_data::EncryptedContentInfo;
use crate::matrix::MatrixDyn;
use crate::{Asn1Matrix, CompressedData, DigestInfo, MessagePriority, Metadata, Version};
pub struct MetadataBuilder {
version: Version,
id: Option<Vec<u8>>,
order: Option<u64>,
integrity: Option<DigestInfo>,
compactness: Option<CompressedData>,
confidentiality: Option<EncryptedContentInfo>,
priority: Option<MessagePriority>,
lifetime: Option<u64>,
previous_frame: Option<DigestInfo>,
matrix: Option<MatrixDyn>,
}
impl From<Version> for MetadataBuilder {
fn from(version: Version) -> Self {
Self {
version,
id: None,
order: None,
integrity: None,
compactness: None,
confidentiality: None,
priority: None,
lifetime: None,
previous_frame: None,
matrix: None,
}
}
}
impl MetadataBuilder {
pub fn with_id(mut self, id: impl AsRef<[u8]>) -> Self {
self.id = Some(id.as_ref().to_vec());
self
}
pub fn with_order(mut self, seconds: u64) -> Self {
self.order = Some(seconds);
self
}
pub fn with_integrity_info(mut self, hash: DigestInfo) -> Self {
self.integrity = Some(hash);
self
}
pub fn with_compactness_info(mut self, compression: CompressedData) -> Self {
self.compactness = Some(compression);
self
}
pub fn with_confidentiality_info(mut self, encryption: EncryptedContentInfo) -> Self {
self.confidentiality = Some(encryption);
self
}
pub fn with_priority(mut self, priority: MessagePriority) -> Self {
self.priority = Some(priority);
self
}
pub fn with_lifetime(mut self, seconds: u64) -> Self {
self.lifetime = Some(seconds);
self
}
pub fn previous_frame(mut self, previous: DigestInfo) -> Self {
self.previous_frame = Some(previous);
self
}
pub fn with_matrix(mut self, matrix: MatrixDyn) -> Self {
self.matrix = Some(matrix);
self
}
pub fn build(self) -> Result<Metadata, BuildError> {
let Self {
version,
id,
order,
integrity,
compactness,
confidentiality,
priority,
lifetime,
previous_frame,
matrix,
} = self;
let id = id.ok_or(BuildError::InvalidMetadata(MetadataError::MissingId))?;
let order = order.ok_or(BuildError::InvalidMetadata(MetadataError::MissingTimestamp))?;
macro_rules! reject_unsupported {
($value:ident, $allows:ident) => {
if $value.is_some() && !version.$allows() {
return Err(BuildError::InvalidMetadata(MetadataError::UnsupportedField {
field: stringify!($value),
version,
}));
}
};
}
reject_unsupported!(integrity, allows_integrity);
reject_unsupported!(confidentiality, allows_confidentiality);
reject_unsupported!(priority, allows_priority);
reject_unsupported!(lifetime, allows_lifetime);
reject_unsupported!(previous_frame, allows_previous_frame);
reject_unsupported!(matrix, allows_matrix);
let matrix = if let Some(m) = matrix {
Some(Asn1Matrix::try_from(m)?)
} else {
None
};
Ok(Metadata {
id,
order,
compactness,
integrity,
confidentiality,
priority,
lifetime,
previous_frame,
matrix,
})
}
pub fn has_id(&self) -> bool {
self.id.is_some()
}
pub fn has_priority(&self) -> bool {
self.priority.is_some()
}
pub fn has_order(&self) -> bool {
self.order.is_some()
}
pub fn has_lifetime(&self) -> bool {
self.lifetime.is_some()
}
pub fn has_previous(&self) -> bool {
self.previous_frame.is_some()
}
pub fn has_matrix(&self) -> bool {
self.matrix.is_some()
}
pub fn has_compression(&self) -> bool {
self.compactness.is_some()
}
pub fn has_integrity(&self) -> bool {
self.integrity.is_some()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::testing::create_test_hash_info;
macro_rules! test_metadata_builder {
($test_name:ident, $version:expr, $builder:expr) => {
#[test]
fn $test_name() {
let metadata = $builder
.build()
.expect(concat!("Failed to build ", stringify!($version), " metadata"));
match $version {
Version::V0 => {
assert!(metadata.priority.is_none());
assert!(metadata.lifetime.is_none());
assert!(metadata.previous_frame.is_none());
assert!(metadata.matrix.is_none());
}
Version::V1 => {
assert!(metadata.priority.is_none());
assert!(metadata.lifetime.is_none());
assert!(metadata.previous_frame.is_none());
assert!(metadata.matrix.is_none());
}
Version::V2 => {
assert!(metadata.priority.is_some());
assert!(metadata.matrix.is_none());
}
Version::V3 => {
assert!(metadata.priority.is_some());
assert!(metadata.matrix.is_some());
}
}
}
};
}
test_metadata_builder!(
test_metadata_builder_v0,
Version::V0,
MetadataBuilder::from(Version::V0)
.with_id("test-id-v0")
.with_order(1696521600u64)
);
test_metadata_builder!(
test_metadata_builder_v1,
Version::V1,
MetadataBuilder::from(Version::V1)
.with_id("test-id-v1")
.with_order(1696521600u64)
.with_integrity_info(create_test_hash_info())
);
test_metadata_builder!(
test_metadata_builder_v2,
Version::V2,
MetadataBuilder::from(Version::V2)
.with_id("test-id-v2")
.with_order(1696521600u64)
.with_integrity_info(create_test_hash_info())
.with_priority(MessagePriority::LowLatency)
.with_lifetime(3600)
);
test_metadata_builder!(
test_metadata_builder_v3,
Version::V3,
MetadataBuilder::from(Version::V3)
.with_id("test-id-v3")
.with_order(1696521600u64)
.with_integrity_info(create_test_hash_info())
.with_priority(MessagePriority::LowLatency)
.with_lifetime(3600)
.with_matrix(MatrixDyn::try_from(2u8).unwrap())
);
#[test]
fn test_metadata_builder_missing_required_fields() {
let result = MetadataBuilder::from(Version::V0).with_id("test-id").build();
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
BuildError::InvalidMetadata(MetadataError::MissingTimestamp)
));
}
mod errors {
use super::*;
struct ErrorTestCase {
name: &'static str,
builder: fn() -> MetadataBuilder,
expected_error: MetadataError,
}
#[test]
fn test_metadata_validation_errors() {
let test_cases = [
ErrorTestCase {
name: "V0 missing id",
builder: || MetadataBuilder::from(Version::V0).with_order(1696521600),
expected_error: MetadataError::MissingId,
},
ErrorTestCase {
name: "V0 missing order",
builder: || MetadataBuilder::from(Version::V0).with_id("test-id"),
expected_error: MetadataError::MissingTimestamp,
},
ErrorTestCase {
name: "V1 missing id",
builder: || MetadataBuilder::from(Version::V1).with_order(1696521600),
expected_error: MetadataError::MissingId,
},
ErrorTestCase {
name: "V1 missing order",
builder: || MetadataBuilder::from(Version::V1).with_id("test-id"),
expected_error: MetadataError::MissingTimestamp,
},
ErrorTestCase {
name: "V2 missing id",
builder: || MetadataBuilder::from(Version::V2).with_order(1696521600),
expected_error: MetadataError::MissingId,
},
ErrorTestCase {
name: "V2 missing order",
builder: || MetadataBuilder::from(Version::V2).with_id("test-id"),
expected_error: MetadataError::MissingTimestamp,
},
ErrorTestCase {
name: "V0 rejects integrity",
builder: || {
MetadataBuilder::from(Version::V0)
.with_id("test-id")
.with_order(1696521600)
.with_integrity_info(create_test_hash_info())
},
expected_error: MetadataError::UnsupportedField { field: "integrity", version: Version::V0 },
},
ErrorTestCase {
name: "V0 rejects priority",
builder: || {
MetadataBuilder::from(Version::V0)
.with_id("test-id")
.with_order(1696521600)
.with_priority(MessagePriority::LowLatency)
},
expected_error: MetadataError::UnsupportedField { field: "priority", version: Version::V0 },
},
ErrorTestCase {
name: "V1 rejects lifetime",
builder: || {
MetadataBuilder::from(Version::V1)
.with_id("test-id")
.with_order(1696521600)
.with_lifetime(3600)
},
expected_error: MetadataError::UnsupportedField { field: "lifetime", version: Version::V1 },
},
ErrorTestCase {
name: "V0 rejects previous_frame",
builder: || {
MetadataBuilder::from(Version::V0)
.with_id("test-id")
.with_order(1696521600)
.previous_frame(create_test_hash_info())
},
expected_error: MetadataError::UnsupportedField { field: "previous_frame", version: Version::V0 },
},
ErrorTestCase {
name: "V2 rejects matrix",
builder: || {
MetadataBuilder::from(Version::V2)
.with_id("test-id")
.with_order(1696521600)
.with_matrix(MatrixDyn::default())
},
expected_error: MetadataError::UnsupportedField { field: "matrix", version: Version::V2 },
},
];
for case in test_cases {
let result = (case.builder)().build();
assert!(
matches!(result, Err(BuildError::InvalidMetadata(ref err)) if *err == case.expected_error),
"{}",
case.name
);
}
}
}
}