use codec_multicodec::{
lookup_codec_prefix, CodecSpec, CodecTag as PrimitiveCodecTag, KeyLength,
KeyMaterialKind as PrimitiveKeyMaterialKind, MULTICODEC_TABLE,
};
use std::sync::OnceLock;
const MAX_UNSIGNED_VARINT_BYTES: usize = 9;
static REGISTRY_VALIDITY: OnceLock<Result<(), MulticodecOperationError>> = OnceLock::new();
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum MulticodecOperationError {
#[error("unknown multicodec name")]
UnknownName,
#[error("invalid multicodec prefix")]
InvalidPrefix,
#[error("multicodec registry invariant violation")]
RegistryInvariant,
#[error("multicodec result allocation failed")]
AllocationFailure,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum CodecTag {
Encryption,
Hash,
Key,
Multihash,
Multikey,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum KeyMaterialKind {
NotKey,
PublicKey,
PrivateKey,
SymmetricKey,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum MulticodecLength {
Fixed(usize),
Variable,
NotApplicable,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MulticodecSpec<'a> {
name: &'a str,
tag: CodecTag,
key_material: KeyMaterialKind,
algorithm_name: &'a str,
code: &'a [u8],
prefix: &'a [u8],
length: MulticodecLength,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MulticodecLookup<'a> {
name: &'a str,
prefix_length: usize,
metadata: MulticodecSpec<'a>,
}
#[derive(Debug, PartialEq, Eq)]
pub struct MulticodecTable<'a> {
entries: Vec<MulticodecSpec<'a>>,
}
impl<'a> MulticodecSpec<'a> {
pub const fn name(&self) -> &'a str {
self.name
}
pub const fn tag(&self) -> CodecTag {
self.tag
}
pub const fn key_material(&self) -> KeyMaterialKind {
self.key_material
}
pub const fn algorithm_name(&self) -> &'a str {
self.algorithm_name
}
pub const fn code(&self) -> &'a [u8] {
self.code
}
pub const fn prefix(&self) -> &'a [u8] {
self.prefix
}
pub const fn length(&self) -> MulticodecLength {
self.length
}
}
impl<'a> MulticodecLookup<'a> {
pub const fn name(&self) -> &'a str {
self.name
}
pub const fn prefix_length(&self) -> usize {
self.prefix_length
}
pub const fn metadata(&self) -> &MulticodecSpec<'a> {
&self.metadata
}
}
impl<'a> MulticodecTable<'a> {
pub fn entries(&self) -> &[MulticodecSpec<'a>] {
self.entries.as_slice()
}
}
pub fn prefix_for_name(name: &str) -> Result<MulticodecSpec<'static>, MulticodecOperationError> {
validate_registry()?;
let Some((canonical_name, spec)) = find_codec_spec(name) else {
return Err(MulticodecOperationError::UnknownName);
};
multicodec_spec(
canonical_name,
spec.tag,
spec.key_material,
spec.alg,
spec.codec,
spec.key_length,
)
}
pub fn lookup_prefix(value: &[u8]) -> Result<MulticodecLookup<'static>, MulticodecOperationError> {
validate_registry()?;
let Some(found) = lookup_codec_prefix(value) else {
return Err(MulticodecOperationError::InvalidPrefix);
};
Ok(MulticodecLookup {
name: found.name,
prefix_length: found.codec.len(),
metadata: multicodec_spec(
found.name,
found.tag,
found.key_material,
found.alg,
found.codec,
found.key_length,
)?,
})
}
pub fn strip_prefix(value: &[u8]) -> Result<&[u8], MulticodecOperationError> {
validate_registry()?;
codec_multicodec::strip_codec_prefix(value).map_err(|_| MulticodecOperationError::InvalidPrefix)
}
pub fn supported_table() -> Result<MulticodecTable<'static>, MulticodecOperationError> {
validate_registry()?;
let mut entries = table_entries_with_capacity(MULTICODEC_TABLE.len())?;
for (name, spec) in MULTICODEC_TABLE {
entries.push(multicodec_spec(
name,
spec.tag,
spec.key_material,
spec.alg,
spec.codec,
spec.key_length,
)?);
}
Ok(MulticodecTable { entries })
}
fn table_entries_with_capacity(
capacity: usize,
) -> Result<Vec<MulticodecSpec<'static>>, MulticodecOperationError> {
let mut entries = Vec::new();
entries
.try_reserve(capacity)
.map_err(|_| MulticodecOperationError::AllocationFailure)?;
Ok(entries)
}
fn find_codec_spec(codec_name: &str) -> Option<(&'static str, &'static CodecSpec)> {
MULTICODEC_TABLE
.iter()
.find(|(name, _)| *name == codec_name)
.map(|(name, spec)| (*name, spec))
}
fn validate_registry() -> Result<(), MulticodecOperationError> {
*REGISTRY_VALIDITY.get_or_init(validate_registry_uncached)
}
fn validate_registry_uncached() -> Result<(), MulticodecOperationError> {
validate_registry_entries(MULTICODEC_TABLE)
}
fn validate_registry_entries(
entries: &[(&str, CodecSpec)],
) -> Result<(), MulticodecOperationError> {
if entries.is_empty() {
return Err(MulticodecOperationError::RegistryInvariant);
}
for (index, (name, spec)) in entries.iter().enumerate() {
if name.is_empty() || spec.alg.is_empty() || !is_canonical_u64_varint(spec.codec) {
return Err(MulticodecOperationError::RegistryInvariant);
}
let tag = semantic_codec_tag(spec.tag)?;
let key_material = semantic_key_material_kind(spec.key_material)?;
let is_key_tag = tag == CodecTag::Key;
let carries_key_material = key_material != KeyMaterialKind::NotKey;
if is_key_tag != carries_key_material {
return Err(MulticodecOperationError::RegistryInvariant);
}
if (is_key_tag && matches!(spec.key_length, KeyLength::NotApplicable))
|| (!is_key_tag && matches!(spec.key_length, KeyLength::Variable))
|| matches!(spec.key_length, KeyLength::Fixed(0))
{
return Err(MulticodecOperationError::RegistryInvariant);
}
let next_index = index
.checked_add(1)
.ok_or(MulticodecOperationError::RegistryInvariant)?;
let remaining = entries
.get(next_index..)
.ok_or(MulticodecOperationError::RegistryInvariant)?;
for (other_name, other_spec) in remaining {
let ambiguous_prefix = spec.codec.starts_with(other_spec.codec)
|| other_spec.codec.starts_with(spec.codec);
if name == other_name || ambiguous_prefix {
return Err(MulticodecOperationError::RegistryInvariant);
}
}
}
Ok(())
}
fn is_canonical_u64_varint(value: &[u8]) -> bool {
if value.len() > MAX_UNSIGNED_VARINT_BYTES {
return false;
}
let Some((&last, leading)) = value.split_last() else {
return false;
};
if last & 0x80 != 0 || leading.iter().any(|byte| byte & 0x80 == 0) {
return false;
}
if !leading.is_empty() && last & 0x7f == 0 {
return false;
}
true
}
fn multicodec_spec(
name: &'static str,
tag: PrimitiveCodecTag,
key_material: PrimitiveKeyMaterialKind,
algorithm_name: &'static str,
prefix: &'static [u8],
key_length: KeyLength,
) -> Result<MulticodecSpec<'static>, MulticodecOperationError> {
let tag = semantic_codec_tag(tag)?;
let key_material = semantic_key_material_kind(key_material)?;
let length = match key_length {
KeyLength::Fixed(length) => MulticodecLength::Fixed(length),
KeyLength::Variable => MulticodecLength::Variable,
KeyLength::NotApplicable => MulticodecLength::NotApplicable,
};
Ok(MulticodecSpec {
name,
tag,
key_material,
algorithm_name,
code: prefix,
prefix,
length,
})
}
fn semantic_codec_tag(tag: PrimitiveCodecTag) -> Result<CodecTag, MulticodecOperationError> {
match tag {
PrimitiveCodecTag::Encryption => Ok(CodecTag::Encryption),
PrimitiveCodecTag::Hash => Ok(CodecTag::Hash),
PrimitiveCodecTag::Key => Ok(CodecTag::Key),
PrimitiveCodecTag::Multihash => Ok(CodecTag::Multihash),
PrimitiveCodecTag::Multikey => Ok(CodecTag::Multikey),
_ => Err(MulticodecOperationError::RegistryInvariant),
}
}
fn semantic_key_material_kind(
kind: PrimitiveKeyMaterialKind,
) -> Result<KeyMaterialKind, MulticodecOperationError> {
match kind {
PrimitiveKeyMaterialKind::NotKey => Ok(KeyMaterialKind::NotKey),
PrimitiveKeyMaterialKind::PublicKey => Ok(KeyMaterialKind::PublicKey),
PrimitiveKeyMaterialKind::PrivateKey => Ok(KeyMaterialKind::PrivateKey),
PrimitiveKeyMaterialKind::SymmetricKey => Ok(KeyMaterialKind::SymmetricKey),
_ => Err(MulticodecOperationError::RegistryInvariant),
}
}
#[cfg(test)]
mod tests;