1use codec_multicodec::{
13 lookup_codec_prefix, CodecSpec, CodecTag as PrimitiveCodecTag,
14 KeyMaterialKind as PrimitiveKeyMaterialKind, MULTICODEC_TABLE, VARIABLE_KEY_LENGTH,
15};
16use std::sync::OnceLock;
17
18const MAX_U64_VARINT_BYTES: usize = 10;
19
20static REGISTRY_VALIDITY: OnceLock<Result<(), MulticodecOperationError>> = OnceLock::new();
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
27#[non_exhaustive]
28pub enum MulticodecOperationError {
29 #[error("unknown multicodec name")]
31 UnknownName,
32 #[error("invalid multicodec prefix")]
34 InvalidPrefix,
35 #[error("multicodec registry invariant violation")]
37 RegistryInvariant,
38 #[error("multicodec result allocation failed")]
40 AllocationFailure,
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45#[non_exhaustive]
46pub enum CodecTag {
47 Encryption,
49 Hash,
51 Key,
53 Multihash,
55 Multikey,
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61#[non_exhaustive]
62pub enum KeyMaterialKind {
63 NotKey,
65 PublicKey,
67 PrivateKey,
69 SymmetricKey,
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80#[non_exhaustive]
81pub enum MulticodecLength {
82 Fixed(usize),
84 Variable,
86 NotApplicable,
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92pub struct MulticodecSpec<'a> {
93 name: &'a str,
94 tag: CodecTag,
95 key_material: KeyMaterialKind,
96 algorithm_name: &'a str,
97 code: &'a [u8],
98 prefix: &'a [u8],
99 length: MulticodecLength,
100}
101
102#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104pub struct MulticodecLookup<'a> {
105 name: &'a str,
106 prefix_length: usize,
107 metadata: MulticodecSpec<'a>,
108}
109
110#[derive(Debug, PartialEq, Eq)]
115pub struct MulticodecTable<'a> {
116 entries: Vec<MulticodecSpec<'a>>,
117}
118
119impl<'a> MulticodecSpec<'a> {
120 pub const fn name(&self) -> &'a str {
122 self.name
123 }
124
125 pub const fn tag(&self) -> CodecTag {
127 self.tag
128 }
129
130 pub const fn key_material(&self) -> KeyMaterialKind {
132 self.key_material
133 }
134
135 pub const fn algorithm_name(&self) -> &'a str {
137 self.algorithm_name
138 }
139
140 pub const fn code(&self) -> &'a [u8] {
142 self.code
143 }
144
145 pub const fn prefix(&self) -> &'a [u8] {
147 self.prefix
148 }
149
150 pub const fn length(&self) -> MulticodecLength {
152 self.length
153 }
154}
155
156impl<'a> MulticodecLookup<'a> {
157 pub const fn name(&self) -> &'a str {
159 self.name
160 }
161
162 pub const fn prefix_length(&self) -> usize {
164 self.prefix_length
165 }
166
167 pub const fn metadata(&self) -> &MulticodecSpec<'a> {
169 &self.metadata
170 }
171}
172
173impl<'a> MulticodecTable<'a> {
174 pub fn entries(&self) -> &[MulticodecSpec<'a>] {
176 self.entries.as_slice()
177 }
178}
179
180pub fn prefix_for_name(name: &str) -> Result<MulticodecSpec<'static>, MulticodecOperationError> {
182 validate_registry()?;
183 let Some((canonical_name, spec)) = find_codec_spec(name) else {
184 return Err(MulticodecOperationError::UnknownName);
185 };
186 multicodec_spec(
187 canonical_name,
188 spec.tag,
189 spec.key_material,
190 spec.alg,
191 spec.codec,
192 spec.key_length,
193 )
194}
195
196pub fn lookup_prefix(value: &[u8]) -> Result<MulticodecLookup<'static>, MulticodecOperationError> {
198 validate_registry()?;
199 let Some(found) = lookup_codec_prefix(value) else {
200 return Err(MulticodecOperationError::InvalidPrefix);
201 };
202 Ok(MulticodecLookup {
203 name: found.name,
204 prefix_length: found.codec.len(),
205 metadata: multicodec_spec(
206 found.name,
207 found.tag,
208 found.key_material,
209 found.alg,
210 found.codec,
211 found.key_length,
212 )?,
213 })
214}
215
216pub fn strip_prefix(value: &[u8]) -> Result<&[u8], MulticodecOperationError> {
218 match lookup_prefix(value) {
219 Ok(found) => value
220 .get(found.prefix_length()..)
221 .ok_or(MulticodecOperationError::RegistryInvariant),
222 Err(MulticodecOperationError::InvalidPrefix) => Ok(value),
223 Err(error) => Err(error),
224 }
225}
226
227pub fn supported_table() -> Result<MulticodecTable<'static>, MulticodecOperationError> {
229 validate_registry()?;
230 let mut entries = table_entries_with_capacity(MULTICODEC_TABLE.len())?;
231 for (name, spec) in MULTICODEC_TABLE {
232 entries.push(multicodec_spec(
233 name,
234 spec.tag,
235 spec.key_material,
236 spec.alg,
237 spec.codec,
238 spec.key_length,
239 )?);
240 }
241 Ok(MulticodecTable { entries })
242}
243
244fn table_entries_with_capacity(
245 capacity: usize,
246) -> Result<Vec<MulticodecSpec<'static>>, MulticodecOperationError> {
247 let mut entries = Vec::new();
248 entries
249 .try_reserve(capacity)
250 .map_err(|_| MulticodecOperationError::AllocationFailure)?;
251 Ok(entries)
252}
253
254fn find_codec_spec(codec_name: &str) -> Option<(&'static str, &'static CodecSpec)> {
255 MULTICODEC_TABLE
256 .iter()
257 .find(|(name, _)| *name == codec_name)
258 .map(|(name, spec)| (*name, spec))
259}
260
261fn validate_registry() -> Result<(), MulticodecOperationError> {
262 *REGISTRY_VALIDITY.get_or_init(validate_registry_uncached)
263}
264
265fn validate_registry_uncached() -> Result<(), MulticodecOperationError> {
266 validate_registry_entries(MULTICODEC_TABLE)
267}
268
269fn validate_registry_entries(
270 entries: &[(&str, CodecSpec)],
271) -> Result<(), MulticodecOperationError> {
272 if entries.is_empty() {
273 return Err(MulticodecOperationError::RegistryInvariant);
274 }
275 for (index, (name, spec)) in entries.iter().enumerate() {
276 if name.is_empty() || spec.alg.is_empty() || !is_canonical_u64_varint(spec.codec) {
277 return Err(MulticodecOperationError::RegistryInvariant);
278 }
279
280 let tag = semantic_codec_tag(spec.tag)?;
281 let key_material = semantic_key_material_kind(spec.key_material)?;
282 let is_key_tag = tag == CodecTag::Key;
283 let carries_key_material = key_material != KeyMaterialKind::NotKey;
284 if is_key_tag != carries_key_material {
285 return Err(MulticodecOperationError::RegistryInvariant);
286 }
287
288 let next_index = index
289 .checked_add(1)
290 .ok_or(MulticodecOperationError::RegistryInvariant)?;
291 let remaining = entries
292 .get(next_index..)
293 .ok_or(MulticodecOperationError::RegistryInvariant)?;
294 for (other_name, other_spec) in remaining {
295 let ambiguous_prefix = spec.codec.starts_with(other_spec.codec)
296 || other_spec.codec.starts_with(spec.codec);
297 if name == other_name || ambiguous_prefix {
298 return Err(MulticodecOperationError::RegistryInvariant);
299 }
300 }
301 }
302 Ok(())
303}
304
305fn is_canonical_u64_varint(value: &[u8]) -> bool {
306 if value.len() > MAX_U64_VARINT_BYTES {
307 return false;
308 }
309 let Some((&last, leading)) = value.split_last() else {
310 return false;
311 };
312 if last & 0x80 != 0 || leading.iter().any(|byte| byte & 0x80 == 0) {
313 return false;
314 }
315 if !leading.is_empty() && last & 0x7f == 0 {
316 return false;
317 }
318 value.len() < MAX_U64_VARINT_BYTES || last <= 1
319}
320
321fn multicodec_spec(
322 name: &'static str,
323 tag: PrimitiveCodecTag,
324 key_material: PrimitiveKeyMaterialKind,
325 algorithm_name: &'static str,
326 prefix: &'static [u8],
327 key_length: usize,
328) -> Result<MulticodecSpec<'static>, MulticodecOperationError> {
329 let tag = semantic_codec_tag(tag)?;
330 let key_material = semantic_key_material_kind(key_material)?;
331 let length = if key_length != VARIABLE_KEY_LENGTH {
332 MulticodecLength::Fixed(key_length)
333 } else if key_material == KeyMaterialKind::NotKey {
334 MulticodecLength::NotApplicable
335 } else {
336 MulticodecLength::Variable
337 };
338 Ok(MulticodecSpec {
339 name,
340 tag,
341 key_material,
342 algorithm_name,
343 code: prefix,
344 prefix,
345 length,
346 })
347}
348
349fn semantic_codec_tag(tag: PrimitiveCodecTag) -> Result<CodecTag, MulticodecOperationError> {
350 match tag {
351 PrimitiveCodecTag::Encryption => Ok(CodecTag::Encryption),
352 PrimitiveCodecTag::Hash => Ok(CodecTag::Hash),
353 PrimitiveCodecTag::Key => Ok(CodecTag::Key),
354 PrimitiveCodecTag::Multihash => Ok(CodecTag::Multihash),
355 PrimitiveCodecTag::Multikey => Ok(CodecTag::Multikey),
356 _ => Err(MulticodecOperationError::RegistryInvariant),
357 }
358}
359
360fn semantic_key_material_kind(
361 kind: PrimitiveKeyMaterialKind,
362) -> Result<KeyMaterialKind, MulticodecOperationError> {
363 match kind {
364 PrimitiveKeyMaterialKind::NotKey => Ok(KeyMaterialKind::NotKey),
365 PrimitiveKeyMaterialKind::PublicKey => Ok(KeyMaterialKind::PublicKey),
366 PrimitiveKeyMaterialKind::PrivateKey => Ok(KeyMaterialKind::PrivateKey),
367 PrimitiveKeyMaterialKind::SymmetricKey => Ok(KeyMaterialKind::SymmetricKey),
368 _ => Err(MulticodecOperationError::RegistryInvariant),
369 }
370}
371
372#[cfg(test)]
373mod tests;