1use codec_multicodec::{
13 lookup_codec_prefix, CodecSpec, CodecTag as PrimitiveCodecTag, KeyLength,
14 KeyMaterialKind as PrimitiveKeyMaterialKind, MULTICODEC_TABLE,
15};
16use std::sync::OnceLock;
17
18const MAX_UNSIGNED_VARINT_BYTES: usize = 9;
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)]
78#[non_exhaustive]
79pub enum MulticodecLength {
80 Fixed(usize),
82 Variable,
84 NotApplicable,
86}
87
88#[derive(Debug, Clone, Copy, PartialEq, Eq)]
90pub struct MulticodecSpec<'a> {
91 name: &'a str,
92 tag: CodecTag,
93 key_material: KeyMaterialKind,
94 algorithm_name: &'a str,
95 code: &'a [u8],
96 prefix: &'a [u8],
97 length: MulticodecLength,
98}
99
100#[derive(Debug, Clone, Copy, PartialEq, Eq)]
102pub struct MulticodecLookup<'a> {
103 name: &'a str,
104 prefix_length: usize,
105 metadata: MulticodecSpec<'a>,
106}
107
108#[derive(Debug, PartialEq, Eq)]
113pub struct MulticodecTable<'a> {
114 entries: Vec<MulticodecSpec<'a>>,
115}
116
117impl<'a> MulticodecSpec<'a> {
118 pub const fn name(&self) -> &'a str {
120 self.name
121 }
122
123 pub const fn tag(&self) -> CodecTag {
125 self.tag
126 }
127
128 pub const fn key_material(&self) -> KeyMaterialKind {
130 self.key_material
131 }
132
133 pub const fn algorithm_name(&self) -> &'a str {
135 self.algorithm_name
136 }
137
138 pub const fn code(&self) -> &'a [u8] {
140 self.code
141 }
142
143 pub const fn prefix(&self) -> &'a [u8] {
145 self.prefix
146 }
147
148 pub const fn length(&self) -> MulticodecLength {
150 self.length
151 }
152}
153
154impl<'a> MulticodecLookup<'a> {
155 pub const fn name(&self) -> &'a str {
157 self.name
158 }
159
160 pub const fn prefix_length(&self) -> usize {
162 self.prefix_length
163 }
164
165 pub const fn metadata(&self) -> &MulticodecSpec<'a> {
167 &self.metadata
168 }
169}
170
171impl<'a> MulticodecTable<'a> {
172 pub fn entries(&self) -> &[MulticodecSpec<'a>] {
174 self.entries.as_slice()
175 }
176}
177
178pub fn prefix_for_name(name: &str) -> Result<MulticodecSpec<'static>, MulticodecOperationError> {
180 validate_registry()?;
181 let Some((canonical_name, spec)) = find_codec_spec(name) else {
182 return Err(MulticodecOperationError::UnknownName);
183 };
184 multicodec_spec(
185 canonical_name,
186 spec.tag,
187 spec.key_material,
188 spec.alg,
189 spec.codec,
190 spec.key_length,
191 )
192}
193
194pub fn lookup_prefix(value: &[u8]) -> Result<MulticodecLookup<'static>, MulticodecOperationError> {
196 validate_registry()?;
197 let Some(found) = lookup_codec_prefix(value) else {
198 return Err(MulticodecOperationError::InvalidPrefix);
199 };
200 Ok(MulticodecLookup {
201 name: found.name,
202 prefix_length: found.codec.len(),
203 metadata: multicodec_spec(
204 found.name,
205 found.tag,
206 found.key_material,
207 found.alg,
208 found.codec,
209 found.key_length,
210 )?,
211 })
212}
213
214pub fn strip_prefix(value: &[u8]) -> Result<&[u8], MulticodecOperationError> {
216 validate_registry()?;
217 codec_multicodec::strip_codec_prefix(value).map_err(|_| MulticodecOperationError::InvalidPrefix)
218}
219
220pub fn supported_table() -> Result<MulticodecTable<'static>, MulticodecOperationError> {
222 validate_registry()?;
223 let mut entries = table_entries_with_capacity(MULTICODEC_TABLE.len())?;
224 for (name, spec) in MULTICODEC_TABLE {
225 entries.push(multicodec_spec(
226 name,
227 spec.tag,
228 spec.key_material,
229 spec.alg,
230 spec.codec,
231 spec.key_length,
232 )?);
233 }
234 Ok(MulticodecTable { entries })
235}
236
237fn table_entries_with_capacity(
238 capacity: usize,
239) -> Result<Vec<MulticodecSpec<'static>>, MulticodecOperationError> {
240 let mut entries = Vec::new();
241 entries
242 .try_reserve(capacity)
243 .map_err(|_| MulticodecOperationError::AllocationFailure)?;
244 Ok(entries)
245}
246
247fn find_codec_spec(codec_name: &str) -> Option<(&'static str, &'static CodecSpec)> {
248 MULTICODEC_TABLE
249 .iter()
250 .find(|(name, _)| *name == codec_name)
251 .map(|(name, spec)| (*name, spec))
252}
253
254fn validate_registry() -> Result<(), MulticodecOperationError> {
255 *REGISTRY_VALIDITY.get_or_init(validate_registry_uncached)
256}
257
258fn validate_registry_uncached() -> Result<(), MulticodecOperationError> {
259 validate_registry_entries(MULTICODEC_TABLE)
260}
261
262fn validate_registry_entries(
263 entries: &[(&str, CodecSpec)],
264) -> Result<(), MulticodecOperationError> {
265 if entries.is_empty() {
266 return Err(MulticodecOperationError::RegistryInvariant);
267 }
268 for (index, (name, spec)) in entries.iter().enumerate() {
269 if name.is_empty() || spec.alg.is_empty() || !is_canonical_u64_varint(spec.codec) {
270 return Err(MulticodecOperationError::RegistryInvariant);
271 }
272
273 let tag = semantic_codec_tag(spec.tag)?;
274 let key_material = semantic_key_material_kind(spec.key_material)?;
275 let is_key_tag = tag == CodecTag::Key;
276 let carries_key_material = key_material != KeyMaterialKind::NotKey;
277 if is_key_tag != carries_key_material {
278 return Err(MulticodecOperationError::RegistryInvariant);
279 }
280 if (is_key_tag && matches!(spec.key_length, KeyLength::NotApplicable))
281 || (!is_key_tag && matches!(spec.key_length, KeyLength::Variable))
282 || matches!(spec.key_length, KeyLength::Fixed(0))
283 {
284 return Err(MulticodecOperationError::RegistryInvariant);
285 }
286
287 let next_index = index
288 .checked_add(1)
289 .ok_or(MulticodecOperationError::RegistryInvariant)?;
290 let remaining = entries
291 .get(next_index..)
292 .ok_or(MulticodecOperationError::RegistryInvariant)?;
293 for (other_name, other_spec) in remaining {
294 let ambiguous_prefix = spec.codec.starts_with(other_spec.codec)
295 || other_spec.codec.starts_with(spec.codec);
296 if name == other_name || ambiguous_prefix {
297 return Err(MulticodecOperationError::RegistryInvariant);
298 }
299 }
300 }
301 Ok(())
302}
303
304fn is_canonical_u64_varint(value: &[u8]) -> bool {
305 if value.len() > MAX_UNSIGNED_VARINT_BYTES {
306 return false;
307 }
308 let Some((&last, leading)) = value.split_last() else {
309 return false;
310 };
311 if last & 0x80 != 0 || leading.iter().any(|byte| byte & 0x80 == 0) {
312 return false;
313 }
314 if !leading.is_empty() && last & 0x7f == 0 {
315 return false;
316 }
317 true
318}
319
320fn multicodec_spec(
321 name: &'static str,
322 tag: PrimitiveCodecTag,
323 key_material: PrimitiveKeyMaterialKind,
324 algorithm_name: &'static str,
325 prefix: &'static [u8],
326 key_length: KeyLength,
327) -> Result<MulticodecSpec<'static>, MulticodecOperationError> {
328 let tag = semantic_codec_tag(tag)?;
329 let key_material = semantic_key_material_kind(key_material)?;
330 let length = match key_length {
331 KeyLength::Fixed(length) => MulticodecLength::Fixed(length),
332 KeyLength::Variable => MulticodecLength::Variable,
333 KeyLength::NotApplicable => MulticodecLength::NotApplicable,
334 };
335 Ok(MulticodecSpec {
336 name,
337 tag,
338 key_material,
339 algorithm_name,
340 code: prefix,
341 prefix,
342 length,
343 })
344}
345
346fn semantic_codec_tag(tag: PrimitiveCodecTag) -> Result<CodecTag, MulticodecOperationError> {
347 match tag {
348 PrimitiveCodecTag::Encryption => Ok(CodecTag::Encryption),
349 PrimitiveCodecTag::Hash => Ok(CodecTag::Hash),
350 PrimitiveCodecTag::Key => Ok(CodecTag::Key),
351 PrimitiveCodecTag::Multihash => Ok(CodecTag::Multihash),
352 PrimitiveCodecTag::Multikey => Ok(CodecTag::Multikey),
353 _ => Err(MulticodecOperationError::RegistryInvariant),
354 }
355}
356
357fn semantic_key_material_kind(
358 kind: PrimitiveKeyMaterialKind,
359) -> Result<KeyMaterialKind, MulticodecOperationError> {
360 match kind {
361 PrimitiveKeyMaterialKind::NotKey => Ok(KeyMaterialKind::NotKey),
362 PrimitiveKeyMaterialKind::PublicKey => Ok(KeyMaterialKind::PublicKey),
363 PrimitiveKeyMaterialKind::PrivateKey => Ok(KeyMaterialKind::PrivateKey),
364 PrimitiveKeyMaterialKind::SymmetricKey => Ok(KeyMaterialKind::SymmetricKey),
365 _ => Err(MulticodecOperationError::RegistryInvariant),
366 }
367}
368
369#[cfg(test)]
370mod tests;