196 lines
5.6 KiB
Rust
196 lines
5.6 KiB
Rust
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<T: Serialize>(value: &T, entity: &'static str) -> StorageResult<Vec<u8>> {
|
|
serde_cbor::to_vec(value).map_err(|e| StorageError::Codec {
|
|
entity,
|
|
detail: e.to_string(),
|
|
})
|
|
}
|
|
|
|
pub(super) fn decode_cbor<T: DeserializeOwned>(
|
|
bytes: &[u8],
|
|
entity: &'static str,
|
|
) -> StorageResult<T> {
|
|
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<u8>],
|
|
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<u8>,
|
|
) -> 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<time::OffsetDateTime> {
|
|
value.parse().map_err(|detail| StorageError::InvalidData {
|
|
entity: field,
|
|
detail,
|
|
})
|
|
}
|