use serde::{Serialize, de::DeserializeOwned}; use crate::data_model::common::der_take_tlv; use super::config::*; use super::pack::PackTime; use super::{StorageError, StorageResult}; pub(super) fn repository_view_key(rsync_uri: &str) -> String { format!("{REPOSITORY_VIEW_KEY_PREFIX}{rsync_uri}") } pub(super) fn repository_view_prefix(rsync_uri_prefix: &str) -> String { format!("{REPOSITORY_VIEW_KEY_PREFIX}{rsync_uri_prefix}") } pub(super) fn raw_by_hash_key(sha256_hex: &str) -> String { format!("{RAW_BY_HASH_KEY_PREFIX}{sha256_hex}") } pub(super) fn raw_blob_key(sha256_hex: &str) -> String { format!("{RAW_BLOB_KEY_PREFIX}{sha256_hex}") } pub(super) fn vcir_key(manifest_rsync_uri: &str) -> String { format!("{VCIR_KEY_PREFIX}{manifest_rsync_uri}") } pub(super) fn manifest_replay_meta_key(manifest_rsync_uri: &str) -> String { format!("{MANIFEST_REPLAY_META_KEY_PREFIX}{manifest_rsync_uri}") } pub(super) fn rrdp_source_key(notify_uri: &str) -> String { format!("{RRDP_SOURCE_KEY_PREFIX}{notify_uri}") } pub(super) fn rrdp_source_member_key(notify_uri: &str, rsync_uri: &str) -> String { format!("{RRDP_SOURCE_MEMBER_KEY_PREFIX}{notify_uri}:{rsync_uri}") } pub(super) fn rrdp_source_member_prefix(notify_uri: &str) -> String { format!("{RRDP_SOURCE_MEMBER_KEY_PREFIX}{notify_uri}:") } pub(super) fn rrdp_uri_owner_key(rsync_uri: &str) -> String { format!("{RRDP_URI_OWNER_KEY_PREFIX}{rsync_uri}") } pub(super) fn encode_cbor(value: &T, entity: &'static str) -> StorageResult> { serde_cbor::to_vec(value).map_err(|e| StorageError::Codec { entity, detail: e.to_string(), }) } pub(super) fn decode_cbor( bytes: &[u8], entity: &'static str, ) -> StorageResult { serde_cbor::from_slice(bytes).map_err(|e| StorageError::Codec { entity, detail: e.to_string(), }) } pub(super) fn validate_non_empty(field: &'static str, value: &str) -> StorageResult<()> { if value.is_empty() { return Err(StorageError::InvalidData { entity: field, detail: "must not be empty".to_string(), }); } Ok(()) } pub(super) fn validate_sha256_hex(field: &'static str, value: &str) -> StorageResult<()> { if value.len() != 64 || !value.as_bytes().iter().all(u8::is_ascii_hexdigit) { return Err(StorageError::InvalidData { entity: field, detail: "must be a 64-character lowercase or uppercase SHA-256 hex string".to_string(), }); } Ok(()) } pub(super) fn decode_sha256_hex_32(field: &'static str, value: &str) -> StorageResult<[u8; 32]> { validate_sha256_hex(field, value)?; let mut out = [0u8; 32]; hex::decode_to_slice(value, &mut out).map_err(|e| StorageError::InvalidData { entity: field, detail: format!("hex decode failed: {e}"), })?; Ok(out) } pub(super) fn validate_manifest_number_be(field: &'static str, value: &[u8]) -> StorageResult<()> { if value.is_empty() { return Err(StorageError::InvalidData { entity: field, detail: "must not be empty".to_string(), }); } if value.len() > 20 { return Err(StorageError::InvalidData { entity: field, detail: "must be at most 20 octets".to_string(), }); } if value.len() > 1 && value[0] == 0 { return Err(StorageError::InvalidData { entity: field, detail: "must be minimal big-endian without leading zeros".to_string(), }); } Ok(()) } pub(super) fn validate_sha256_digest_bytes(field: &'static str, value: &[u8]) -> StorageResult<()> { if value.len() != 32 { return Err(StorageError::InvalidData { entity: field, detail: format!("must be 32 bytes, got {}", value.len()), }); } Ok(()) } pub(super) fn validate_fixed_len_bytes( field: &'static str, value: &[u8], expected_len: usize, ) -> StorageResult<()> { if value.len() != expected_len { return Err(StorageError::InvalidData { entity: field, detail: format!("must be {expected_len} bytes, got {}", value.len()), }); } Ok(()) } pub(super) fn validate_sorted_unique_fixed_len_bytes( field: &'static str, values: &[Vec], expected_len: usize, ) -> StorageResult<()> { for value in values { validate_fixed_len_bytes(field, value, expected_len)?; } for window in values.windows(2) { if window[0] >= window[1] { return Err(StorageError::InvalidData { entity: field, detail: "must be strictly sorted and unique".to_string(), }); } } Ok(()) } pub(super) fn validate_full_der_with_tag( field: &'static str, der: &[u8], expected_tag: Option, ) -> StorageResult<()> { let (tag, _value, rem) = der_take_tlv(der).map_err(|detail| StorageError::InvalidData { entity: field, detail, })?; if !rem.is_empty() { return Err(StorageError::InvalidData { entity: field, detail: "trailing bytes after DER object".to_string(), }); } if let Some(expected_tag) = expected_tag { if tag != expected_tag { return Err(StorageError::InvalidData { entity: field, detail: format!("unexpected tag 0x{tag:02X}, expected 0x{expected_tag:02X}"), }); } } Ok(()) } pub(super) fn parse_time( field: &'static str, value: &PackTime, ) -> StorageResult { value.parse().map_err(|detail| StorageError::InvalidData { entity: field, detail, }) }