1use anyhow::{bail, Context, Result};
13use serde::{Deserialize, Serialize};
14
15pub const RECORD_DESCRIPTOR_MAGIC: &[u8; 4] = b"BRD1";
16pub const RECORD_DESCRIPTOR_VERSION: u8 = 2;
17pub const RECORD_DESCRIPTOR_PREFIX_LENGTH: usize = 19;
18
19pub const METADATA_GRAYSCALE_NIBBLE_BASE: u8 = 120;
20
21pub const SIGNED_RELEASE_REFERENCE_VERSION: u8 = 2;
24pub const SIGNED_RELEASE_REFERENCE_HASH_LENGTH: usize = 32;
25pub const SIGNED_RELEASE_REFERENCE_SIGNATURE_LENGTH: usize = 64;
26pub const SIGNED_RELEASE_REFERENCE_MAX_KEY_ID_LENGTH: usize = u16::MAX as usize;
27
28pub const RECORD_PROFILE_SINGLE45_CODE: u8 = 0;
29pub const RECORD_PROFILE_LP_CODE: u8 = 1;
30pub const RECORD_PROFILE_SINGLE45: &str = "single45";
31pub const RECORD_PROFILE_LP: &str = "lp";
32
33pub const RELEASE_ID_LENGTH: usize = 16;
34
35pub const SEGMENT_DESCRIPTOR_CRC32: u8 = 1;
36pub const SEGMENT_STREAM_BYTE_LENGTH: u8 = 2;
37pub const SEGMENT_RECORD_PROFILE: u8 = 4;
38pub const SEGMENT_TITLE: u8 = 5;
39pub const SEGMENT_ARTIST: u8 = 6;
40pub const SEGMENT_PAYLOAD_ENCODING: u8 = 7;
41pub const SEGMENT_RELEASE_ID: u8 = 8;
42pub const SEGMENT_CATALOG_NUMBER: u8 = 9;
43pub const SEGMENT_LABEL: u8 = 10;
44pub const SEGMENT_ARTWORK_CREDIT: u8 = 11;
45pub const SEGMENT_CANONICAL_URL: u8 = 13;
46pub const SEGMENT_CREATED_AT: u8 = 14;
47pub const SEGMENT_SIGNED_RELEASE_REFERENCE: u8 = 16;
48pub const SEGMENT_BSC_POINTER: u8 = 21;
49pub const SEGMENT_TONED_CARRIER_MAP: u8 = 22;
50
51pub const PAYLOAD_ENCODING_RGB: &str = "rgb";
52pub const PAYLOAD_ENCODING_TONED_V1: &str = "toned-v1";
53pub const PAYLOAD_ENCODING_RGB_CODE: u8 = 0;
54pub const PAYLOAD_ENCODING_TONED_V1_CODE: u8 = 1;
55
56pub const TONED_CARRIER_MAP_VERSION: u8 = 1;
57pub const TONED_ORDERING_BASE_PROXIMITY: u8 = 0;
58pub const TONED_ORDERING_CHROMA_PROXIMITY: u8 = 1;
59pub const TONED_MIN_BITS_PER_PIXEL: u8 = 1;
60pub const TONED_MAX_BITS_PER_PIXEL: u8 = 24;
61pub const TONED_MAX_SPAN_COUNT: usize = u16::MAX as usize;
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
64#[serde(rename_all = "camelCase")]
65pub enum ToneOrdering {
66 BaseProximity,
67 ChromaProximity,
68}
69
70impl ToneOrdering {
71 pub fn wire_code(self) -> u8 {
72 match self {
73 Self::BaseProximity => TONED_ORDERING_BASE_PROXIMITY,
74 Self::ChromaProximity => TONED_ORDERING_CHROMA_PROXIMITY,
75 }
76 }
77
78 pub fn from_wire_code(code: u8) -> Result<Self> {
79 match code {
80 TONED_ORDERING_BASE_PROXIMITY => Ok(Self::BaseProximity),
81 TONED_ORDERING_CHROMA_PROXIMITY => Ok(Self::ChromaProximity),
82 _ => bail!("unknown toned carrier ordering code {code}"),
83 }
84 }
85}
86
87#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
88#[serde(rename_all = "camelCase")]
89pub struct ToneSpanDescriptor {
90 pub byte_length: usize,
91 pub base: [u8; 3],
92 pub luma_tolerance: u8,
93 pub bits_per_pixel: u8,
94 pub ordering: ToneOrdering,
95}
96
97#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
98#[serde(rename_all = "camelCase")]
99pub struct ResolvedToneSpan {
100 pub index: usize,
101 pub byte_offset: usize,
102 pub byte_length: usize,
103 pub pixel_offset: usize,
104 pub pixel_count: usize,
105 pub base: [u8; 3],
106 pub luma_tolerance: u8,
107 pub bits_per_pixel: u8,
108 pub ordering: ToneOrdering,
109}
110
111#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
116#[serde(rename_all = "camelCase")]
117pub struct SignedReleaseReference {
118 pub version: u8,
119 pub release_commitment_sha256: [u8; SIGNED_RELEASE_REFERENCE_HASH_LENGTH],
120 pub key_id: Vec<u8>,
121 pub signature: Vec<u8>,
122}
123
124impl SignedReleaseReference {
125 pub fn validate(&self) -> Result<()> {
126 if self.version != SIGNED_RELEASE_REFERENCE_VERSION {
127 bail!(
128 "unsupported signed release reference version: {}",
129 self.version
130 );
131 }
132 if self.key_id.is_empty() {
133 bail!("signature key ID must not be empty");
134 }
135 if self.key_id.len() > SIGNED_RELEASE_REFERENCE_MAX_KEY_ID_LENGTH {
136 bail!("signature key ID exceeds u16 length limit");
137 }
138 if self.signature.len() != SIGNED_RELEASE_REFERENCE_SIGNATURE_LENGTH {
139 bail!("signature must be exactly {SIGNED_RELEASE_REFERENCE_SIGNATURE_LENGTH} bytes");
140 }
141 Ok(())
142 }
143}
144
145#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
152#[serde(rename_all = "camelCase")]
153pub struct RecordDescriptor {
154 pub version: u8,
155 pub checksum_protected: bool,
156 pub b_value_bits: u64,
157 pub record_profile: String,
158 pub stream_byte_length: usize,
159 pub payload_encoding: String,
160 pub title: Option<String>,
161 pub artist: Option<String>,
162 pub release_id: Option<[u8; RELEASE_ID_LENGTH]>,
163 pub catalog_number: Option<String>,
164 pub label: Option<String>,
165 pub artwork_credit: Option<String>,
166 pub canonical_url: Option<String>,
167 pub created_at: Option<u64>,
168 pub signed_release_reference: Option<SignedReleaseReference>,
169 pub bsc_pointer: Option<Vec<u8>>,
170 pub tone_spans: Vec<ToneSpanDescriptor>,
171}
172
173impl RecordDescriptor {
174 pub fn b_value(&self) -> f64 {
175 f64::from_bits(self.b_value_bits)
176 }
177}
178
179#[derive(Debug, Clone, PartialEq, Eq)]
180pub struct DescriptorPrefix {
181 pub version: u8,
182 pub payload_len: usize,
183 pub segment_count: usize,
184 pub segment_stream_len: usize,
185 pub b_value_bits: u64,
186}
187
188pub fn metadata_pixel_count_for_byte_length(byte_length: usize) -> usize {
189 byte_length.saturating_mul(2)
190}
191
192pub fn metadata_byte_capacity_for_pixel_count(pixel_count: usize) -> usize {
193 pixel_count / 2
194}
195
196pub fn metadata_bytes_from_grayscale_rgba(
197 rgba: &[u8],
198 indices: &[usize],
199 byte_length: usize,
200 label: &str,
201) -> Result<Vec<u8>> {
202 let pixel_count = metadata_pixel_count_for_byte_length(byte_length);
203 if indices.len() < pixel_count {
204 bail!("{label} spiral capacity is too small");
205 }
206
207 let mut bytes = Vec::with_capacity(byte_length);
208 for byte_number in 0..byte_length {
209 let mut nibbles = [0u8; 2];
210 for nibble_index in 0..2 {
211 let pixel_index = indices[byte_number * 2 + nibble_index];
212 let rgba_index = pixel_index
213 .checked_mul(4)
214 .context("metadata RGBA index overflow")?;
215 if rgba_index + 3 >= rgba.len() {
216 bail!("{label} spiral pixel index is outside RGBA buffer");
217 }
218
219 let red = rgba[rgba_index];
220 let green = rgba[rgba_index + 1];
221 let blue = rgba[rgba_index + 2];
222 let alpha = rgba[rgba_index + 3];
223
224 if alpha == 0 {
225 bail!("{label} spiral pixel is empty");
226 }
227 if red != green || green != blue {
228 bail!("{label} metadata pixel is not grayscale");
229 }
230
231 let nibble = red
232 .checked_sub(METADATA_GRAYSCALE_NIBBLE_BASE)
233 .context("metadata pixel is below grayscale nibble range")?;
234 if nibble > 0x0f {
235 bail!("{label} metadata pixel is outside grayscale nibble range");
236 }
237 nibbles[nibble_index] = nibble;
238 }
239 bytes.push((nibbles[0] << 4) | nibbles[1]);
240 }
241 Ok(bytes)
242}
243
244pub fn record_profile_code(record_profile: &str) -> Result<u8> {
245 match record_profile {
246 RECORD_PROFILE_SINGLE45 => Ok(RECORD_PROFILE_SINGLE45_CODE),
247 RECORD_PROFILE_LP => Ok(RECORD_PROFILE_LP_CODE),
248 other => bail!("unsupported canonical record profile {other}"),
249 }
250}
251
252pub fn record_profile_from_code(code: u8) -> Result<String> {
253 match code {
254 RECORD_PROFILE_SINGLE45_CODE => Ok(RECORD_PROFILE_SINGLE45.to_string()),
255 RECORD_PROFILE_LP_CODE => Ok(RECORD_PROFILE_LP.to_string()),
256 other => bail!("unknown record profile code {other}"),
257 }
258}
259
260pub fn payload_encoding_code(payload_encoding: &str) -> Result<u8> {
261 match payload_encoding {
262 PAYLOAD_ENCODING_RGB => Ok(PAYLOAD_ENCODING_RGB_CODE),
263 PAYLOAD_ENCODING_TONED_V1 => Ok(PAYLOAD_ENCODING_TONED_V1_CODE),
264 other => bail!("unsupported canonical payload encoding {other}"),
265 }
266}
267
268pub fn payload_encoding_from_code(code: u8) -> Result<String> {
269 match code {
270 PAYLOAD_ENCODING_RGB_CODE => Ok(PAYLOAD_ENCODING_RGB.to_string()),
271 PAYLOAD_ENCODING_TONED_V1_CODE => Ok(PAYLOAD_ENCODING_TONED_V1.to_string()),
272 other => bail!("unknown payload encoding code {other}"),
273 }
274}
275
276const RELEASE_ID_TAGGED_PREFIX: &str = "rel_";
277const RELEASE_ID_ULID_TEXT_LENGTH: usize = 26;
278const CROCKFORD_BASE32: &[u8; 32] = b"0123456789ABCDEFGHJKMNPQRSTVWXYZ";
279
280pub fn release_id_to_bytes(text: &str) -> Result<[u8; RELEASE_ID_LENGTH]> {
282 let ulid_text = text
283 .strip_prefix(RELEASE_ID_TAGGED_PREFIX)
284 .context("release ID is missing the rel_ prefix")?;
285 if ulid_text.len() != RELEASE_ID_ULID_TEXT_LENGTH {
286 bail!("release ID must be 26 Crockford Base32 characters");
287 }
288
289 let mut bits: u128 = 0;
290 for (index, byte) in ulid_text.bytes().enumerate() {
291 let upper = byte.to_ascii_uppercase();
292 let digit = CROCKFORD_BASE32
293 .iter()
294 .position(|&candidate| candidate == upper)
295 .context("release ID contains a non-canonical Crockford Base32 character")?;
296
297 if index == 0 && digit > 7 {
302 bail!("release ID exceeds the 128-bit ULID range");
303 }
304
305 bits = (bits << 5) | digit as u128;
306 }
307 Ok(bits.to_be_bytes())
308}
309
310pub fn release_id_to_text(bytes: [u8; RELEASE_ID_LENGTH]) -> String {
313 let mut value = u128::from_be_bytes(bytes);
314 let mut chars = [b'0'; RELEASE_ID_ULID_TEXT_LENGTH];
315 for index in (0..RELEASE_ID_ULID_TEXT_LENGTH).rev() {
316 chars[index] = CROCKFORD_BASE32[(value & 0x1f) as usize];
317 value >>= 5;
318 }
319 let mut text = String::with_capacity(RELEASE_ID_TAGGED_PREFIX.len() + RELEASE_ID_ULID_TEXT_LENGTH);
320 text.push_str(RELEASE_ID_TAGGED_PREFIX);
321 text.push_str(std::str::from_utf8(&chars).expect("Crockford Base32 alphabet is ASCII"));
322 text
323}
324
325pub fn decode_descriptor_prefix(bytes: &[u8]) -> Result<DescriptorPrefix> {
326 if bytes.len() < RECORD_DESCRIPTOR_PREFIX_LENGTH {
327 bail!("record descriptor payload too short");
328 }
329 if &bytes[..4] != RECORD_DESCRIPTOR_MAGIC {
330 bail!("record descriptor magic mismatch");
331 }
332
333 let version = bytes[4];
334 let payload_len =
335 u16::from_be_bytes(bytes[5..7].try_into().expect("slice length")) as usize;
336 let segment_count =
337 u16::from_be_bytes(bytes[7..9].try_into().expect("slice length")) as usize;
338 let segment_stream_len =
339 u16::from_be_bytes(bytes[9..11].try_into().expect("slice length")) as usize;
340 let b_value_bits =
341 u64::from_be_bytes(bytes[11..19].try_into().expect("slice length"));
342
343 if payload_len < RECORD_DESCRIPTOR_PREFIX_LENGTH || payload_len > bytes.len() {
344 bail!("record descriptor payload length is invalid");
345 }
346
347 Ok(DescriptorPrefix {
348 version,
349 payload_len,
350 segment_count,
351 segment_stream_len,
352 b_value_bits,
353 })
354}
355
356pub fn validate_tone_span(span: &ToneSpanDescriptor, index: usize) -> Result<()> {
357 if span.byte_length == 0 {
358 bail!("tone span {index} byte length must be greater than zero");
359 }
360 if !(TONED_MIN_BITS_PER_PIXEL..=TONED_MAX_BITS_PER_PIXEL)
361 .contains(&span.bits_per_pixel)
362 {
363 bail!(
364 "tone span {index} bits per pixel must be between {} and {}",
365 TONED_MIN_BITS_PER_PIXEL,
366 TONED_MAX_BITS_PER_PIXEL
367 );
368 }
369 Ok(())
370}
371
372pub fn resolve_tone_spans(
373 spans: &[ToneSpanDescriptor],
374 expected_byte_length: Option<usize>,
375) -> Result<Vec<ResolvedToneSpan>> {
376 if spans.is_empty() {
377 bail!("toned-v1 carrier map must contain at least one span");
378 }
379 if spans.len() > TONED_MAX_SPAN_COUNT {
380 bail!("tone span count exceeds u16 range");
381 }
382
383 let mut byte_offset = 0usize;
384 let mut pixel_offset = 0usize;
385 let mut resolved = Vec::with_capacity(spans.len());
386
387 for (index, span) in spans.iter().enumerate() {
388 validate_tone_span(span, index)?;
389 let bit_length = span
390 .byte_length
391 .checked_mul(8)
392 .context("tone span bit length overflow")?;
393 let pixel_count =
394 bit_length.div_ceil(usize::from(span.bits_per_pixel));
395
396 resolved.push(ResolvedToneSpan {
397 index,
398 byte_offset,
399 byte_length: span.byte_length,
400 pixel_offset,
401 pixel_count,
402 base: span.base,
403 luma_tolerance: span.luma_tolerance,
404 bits_per_pixel: span.bits_per_pixel,
405 ordering: span.ordering,
406 });
407
408 byte_offset = byte_offset
409 .checked_add(span.byte_length)
410 .context("tone span total byte length overflow")?;
411 pixel_offset = pixel_offset
412 .checked_add(pixel_count)
413 .context("tone span total pixel count overflow")?;
414 }
415
416 if let Some(expected) = expected_byte_length {
417 if byte_offset != expected {
418 bail!(
419 "tone spans cover {byte_offset} bytes, expected {expected}"
420 );
421 }
422 }
423
424 Ok(resolved)
425}
426
427pub fn toned_pixel_count(
428 spans: &[ToneSpanDescriptor],
429 expected_byte_length: Option<usize>,
430) -> Result<usize> {
431 Ok(resolve_tone_spans(spans, expected_byte_length)?
432 .last()
433 .map(|span| span.pixel_offset + span.pixel_count)
434 .unwrap_or(0))
435}
436
437pub fn encode_toned_carrier_map(
438 spans: &[ToneSpanDescriptor],
439 expected_byte_length: Option<usize>,
440) -> Result<Vec<u8>> {
441 resolve_tone_spans(spans, expected_byte_length)?;
442
443 let mut out = Vec::new();
444 out.push(TONED_CARRIER_MAP_VERSION);
445 out.extend_from_slice(
446 &u16::try_from(spans.len())
447 .context("tone span count exceeds u16")?
448 .to_be_bytes(),
449 );
450
451 for span in spans {
452 push_varuint(
453 &mut out,
454 u64::try_from(span.byte_length)
455 .context("tone span byte length exceeds u64")?,
456 );
457 out.extend_from_slice(&span.base);
458 out.push(span.luma_tolerance);
459 out.push(span.bits_per_pixel);
460 out.push(span.ordering.wire_code());
461 }
462
463 Ok(out)
464}
465
466pub fn decode_toned_carrier_map(
467 bytes: &[u8],
468 expected_byte_length: Option<usize>,
469) -> Result<Vec<ToneSpanDescriptor>> {
470 let mut cursor = ByteCursor::new(bytes);
471 let version = cursor.read_u8("toned carrier map version")?;
472 if version != TONED_CARRIER_MAP_VERSION {
473 bail!("unsupported toned carrier map version {version}");
474 }
475
476 let count = usize::from(cursor.read_u16be("tone span count")?);
477 if count == 0 {
478 bail!("toned-v1 carrier map must contain at least one span");
479 }
480
481 let mut spans = Vec::with_capacity(count);
482 for index in 0..count {
483 let byte_length = usize::try_from(
484 cursor.read_varuint("tone span byte length")?,
485 )
486 .context("tone span byte length exceeds usize")?;
487 let base = [
488 cursor.read_u8("tone span base red")?,
489 cursor.read_u8("tone span base green")?,
490 cursor.read_u8("tone span base blue")?,
491 ];
492 let luma_tolerance =
493 cursor.read_u8("tone span luma tolerance")?;
494 let bits_per_pixel =
495 cursor.read_u8("tone span bits per pixel")?;
496 let ordering = ToneOrdering::from_wire_code(
497 cursor.read_u8("tone span ordering")?,
498 )?;
499
500 let span = ToneSpanDescriptor {
501 byte_length,
502 base,
503 luma_tolerance,
504 bits_per_pixel,
505 ordering,
506 };
507 validate_tone_span(&span, index)?;
508 spans.push(span);
509 }
510
511 if cursor.remaining() != 0 {
512 bail!(
513 "toned carrier map contains {} trailing bytes",
514 cursor.remaining()
515 );
516 }
517
518 resolve_tone_spans(&spans, expected_byte_length)?;
519 Ok(spans)
520}
521
522fn push_varuint(out: &mut Vec<u8>, mut value: u64) {
523 loop {
524 let mut byte = (value & 0x7f) as u8;
525 value >>= 7;
526 if value != 0 {
527 byte |= 0x80;
528 }
529 out.push(byte);
530 if value == 0 {
531 break;
532 }
533 }
534}
535
536pub fn decode_signed_release_reference(bytes: &[u8]) -> Result<SignedReleaseReference> {
537 let mut cursor = ByteCursor::new(bytes);
538
539 let version = cursor.read_u8("signed release reference version")?;
540 let release_commitment_sha256 = cursor
541 .read_bytes(
542 SIGNED_RELEASE_REFERENCE_HASH_LENGTH,
543 "release commitment SHA-256",
544 )?
545 .try_into()
546 .expect("length checked");
547 let key_id_len = cursor.read_u16be("signature key ID length")? as usize;
548 let key_id = cursor.read_bytes(key_id_len, "signature key ID")?.to_vec();
549 let signature = cursor
550 .read_bytes(SIGNED_RELEASE_REFERENCE_SIGNATURE_LENGTH, "signature")?
551 .to_vec();
552
553 if cursor.remaining() != 0 {
554 bail!(
555 "signed release reference contains {} trailing bytes",
556 cursor.remaining()
557 );
558 }
559
560 let reference = SignedReleaseReference {
561 version,
562 release_commitment_sha256,
563 key_id,
564 signature,
565 };
566 reference.validate()?;
567 Ok(reference)
568}
569
570pub fn decode_record_descriptor_bytes(bytes: &[u8]) -> Result<RecordDescriptor> {
571 let prefix = decode_descriptor_prefix(bytes)?;
572
573 if prefix.version != RECORD_DESCRIPTOR_VERSION {
574 bail!("record descriptor version mismatch");
575 }
576 if prefix.payload_len != RECORD_DESCRIPTOR_PREFIX_LENGTH + prefix.segment_stream_len {
577 bail!("record descriptor segment stream length mismatch");
578 }
579
580 let body = &bytes[RECORD_DESCRIPTOR_PREFIX_LENGTH..prefix.payload_len];
581 let mut offset = 0usize;
582 let mut parsed_segments = 0usize;
583
584 let mut crc32_range = None;
585 let mut crc32 = None;
586 let mut stream_byte_length = None;
587 let mut record_profile = None;
588 let mut payload_encoding = None;
589 let mut title = None;
590 let mut artist = None;
591 let mut release_id = None;
592 let mut catalog_number = None;
593 let mut label = None;
594 let mut artwork_credit = None;
595 let mut canonical_url = None;
596 let mut created_at = None;
597 let mut signed_release_reference = None;
598 let mut bsc_pointer = None;
599 let mut tone_spans = None;
600
601 while offset < body.len() {
602 if parsed_segments >= prefix.segment_count {
603 bail!("record descriptor contains more segments than declared");
604 }
605 if offset + 3 > body.len() {
606 bail!("record descriptor segment is truncated");
607 }
608
609 let kind = body[offset];
610 let len = u16::from_be_bytes(
611 body[offset + 1..offset + 3]
612 .try_into()
613 .expect("slice length"),
614 ) as usize;
615 let payload_start = offset + 3;
616 let payload_end = payload_start
617 .checked_add(len)
618 .context("record descriptor segment length overflow")?;
619 if payload_end > body.len() {
620 bail!("record descriptor segment payload is truncated");
621 }
622
623 let payload = &body[payload_start..payload_end];
624 match kind {
625 SEGMENT_DESCRIPTOR_CRC32 => {
626 if crc32.is_some() {
627 bail!("duplicate record descriptor CRC32 segment");
628 }
629 if payload.len() != 4 {
630 bail!("record descriptor CRC32 segment has invalid length");
631 }
632 crc32 = Some(u32::from_be_bytes(
633 payload.try_into().expect("slice length"),
634 ));
635 let absolute_start = RECORD_DESCRIPTOR_PREFIX_LENGTH + payload_start;
636 crc32_range = Some(absolute_start..absolute_start + payload.len());
637 }
638 SEGMENT_STREAM_BYTE_LENGTH => {
639 if stream_byte_length.is_some() {
640 bail!("duplicate stream byte length segment");
641 }
642 if payload.len() != 4 {
643 bail!("stream byte length segment has invalid length");
644 }
645 let raw_len = u32::from_be_bytes(payload.try_into().expect("slice length"));
646 if raw_len == 0 {
647 bail!("stream byte length must not be zero");
648 }
649 stream_byte_length = Some(raw_len as usize);
650 }
651 SEGMENT_RECORD_PROFILE => {
652 if payload.len() != 1 {
653 bail!("record profile segment has invalid length");
654 }
655 assign_once(
656 &mut record_profile,
657 record_profile_from_code(payload[0])?,
658 "record profile",
659 )?
660 }
661 SEGMENT_PAYLOAD_ENCODING => {
662 if payload.len() != 1 {
663 bail!("payload encoding segment has invalid length");
664 }
665 assign_once(
666 &mut payload_encoding,
667 payload_encoding_from_code(payload[0])?,
668 "payload encoding",
669 )?
670 }
671 SEGMENT_TITLE => assign_once(
672 &mut title,
673 decode_optional_text(payload, "title")?,
674 "title",
675 )?,
676 SEGMENT_ARTIST => assign_once(
677 &mut artist,
678 decode_optional_text(payload, "artist")?,
679 "artist",
680 )?,
681 SEGMENT_RELEASE_ID => {
682 if payload.len() != RELEASE_ID_LENGTH {
683 bail!("release ID segment has invalid length");
684 }
685 assign_once(
686 &mut release_id,
687 <[u8; RELEASE_ID_LENGTH]>::try_from(payload).expect("length checked"),
688 "release ID",
689 )?
690 }
691 SEGMENT_CATALOG_NUMBER => assign_once(
692 &mut catalog_number,
693 decode_optional_text(payload, "catalog number")?,
694 "catalog number",
695 )?,
696 SEGMENT_LABEL => assign_once(
697 &mut label,
698 decode_optional_text(payload, "label")?,
699 "label",
700 )?,
701 SEGMENT_ARTWORK_CREDIT => assign_once(
702 &mut artwork_credit,
703 decode_optional_text(payload, "artwork credit")?,
704 "artwork credit",
705 )?,
706 SEGMENT_CANONICAL_URL => assign_once(
707 &mut canonical_url,
708 decode_optional_text(payload, "canonical URL")?,
709 "canonical URL",
710 )?,
711 SEGMENT_CREATED_AT => {
712 if payload.len() != 8 {
713 bail!("created-at segment has invalid length");
714 }
715 assign_once(
716 &mut created_at,
717 u64::from_be_bytes(payload.try_into().expect("slice length")),
718 "created-at timestamp",
719 )?
720 }
721 SEGMENT_SIGNED_RELEASE_REFERENCE => {
722 if signed_release_reference.is_some() {
723 bail!("duplicate signed release reference segment");
724 }
725 signed_release_reference =
726 Some(decode_signed_release_reference(payload)?);
727 }
728 SEGMENT_BSC_POINTER => {
729 if bsc_pointer.is_some() {
730 bail!("duplicate BSC pointer segment");
731 }
732 if payload.is_empty() {
733 bail!("BSC pointer segment must not be empty");
734 }
735 bsc_pointer = Some(payload.to_vec());
736 }
737 SEGMENT_TONED_CARRIER_MAP => {
738 if tone_spans.is_some() {
739 bail!("duplicate toned carrier map segment");
740 }
741 tone_spans = Some(decode_toned_carrier_map(payload, None)?);
742 }
743 _ => bail!("unsupported canonical record descriptor segment type {kind}"),
744 }
745
746 offset = payload_end;
747 parsed_segments += 1;
748 }
749
750 if parsed_segments != prefix.segment_count {
751 bail!(
752 "record descriptor segment count mismatch: declared {}, parsed {}",
753 prefix.segment_count,
754 parsed_segments
755 );
756 }
757
758 let expected = crc32.context("record descriptor CRC32 segment is missing")?;
759 let range = crc32_range.context("record descriptor CRC32 segment is missing")?;
760 let mut canonical = bytes[..prefix.payload_len].to_vec();
761 canonical[range].fill(0);
762
763 if compute_descriptor_crc32(&canonical) != expected {
764 bail!("record descriptor CRC32 mismatch");
765 }
766
767 let b_value = f64::from_bits(prefix.b_value_bits);
768 if !(b_value.is_finite() && b_value > 0.0) {
769 bail!("decoded invalid b_value");
770 }
771
772 let record_profile =
773 record_profile.context("record profile segment is missing")?;
774 let stream_byte_length = stream_byte_length
775 .context("stream byte length segment is missing")?;
776 let payload_encoding =
777 payload_encoding.context("payload encoding segment is missing")?;
778 let tone_spans = tone_spans.unwrap_or_default();
779
780 match payload_encoding.as_str() {
781 PAYLOAD_ENCODING_RGB => {
782 if !tone_spans.is_empty() {
783 bail!("rgb payload encoding must not include a toned carrier map");
784 }
785 }
786 PAYLOAD_ENCODING_TONED_V1 => {
787 if tone_spans.is_empty() {
788 bail!("toned-v1 payload encoding requires a toned carrier map");
789 }
790 resolve_tone_spans(&tone_spans, Some(stream_byte_length))?;
791 }
792 other => bail!("unsupported canonical payload encoding {other}"),
793 }
794
795 Ok(RecordDescriptor {
796 version: prefix.version,
797 checksum_protected: true,
798 b_value_bits: prefix.b_value_bits,
799 record_profile,
800 stream_byte_length,
801 payload_encoding,
802 title: title.flatten(),
803 artist: artist.flatten(),
804 release_id,
805 catalog_number: catalog_number.flatten(),
806 label: label.flatten(),
807 artwork_credit: artwork_credit.flatten(),
808 canonical_url: canonical_url.flatten(),
809 created_at,
810 signed_release_reference,
811 bsc_pointer,
812 tone_spans,
813 })
814}
815
816pub fn compute_descriptor_crc32(bytes: &[u8]) -> u32 {
817 record_core::crc32_ieee(bytes)
818}
819
820fn decode_optional_text(payload: &[u8], label: &str) -> Result<Option<String>> {
821 if payload.is_empty() {
822 return Ok(None);
823 }
824 Ok(Some(decode_text(payload, label)?))
825}
826
827fn decode_text(payload: &[u8], label: &str) -> Result<String> {
828 let value = String::from_utf8(payload.to_vec())
829 .with_context(|| format!("record descriptor {label} is not valid UTF-8"))?;
830 if value.chars().any(char::is_control) {
831 bail!("record descriptor {label} contains control characters");
832 }
833 Ok(value)
834}
835
836fn assign_once<T>(
837 destination: &mut Option<T>,
838 value: T,
839 label: &str,
840) -> Result<()> {
841 if destination.is_some() {
842 bail!("duplicate {label} segment");
843 }
844 *destination = Some(value);
845 Ok(())
846}
847
848#[derive(Clone, Copy)]
849struct ByteCursor<'a> {
850 bytes: &'a [u8],
851 offset: usize,
852}
853
854impl<'a> ByteCursor<'a> {
855 fn new(bytes: &'a [u8]) -> Self {
856 Self { bytes, offset: 0 }
857 }
858
859 fn remaining(self) -> usize {
860 self.bytes.len().saturating_sub(self.offset)
861 }
862
863 fn read_u8(&mut self, label: &str) -> Result<u8> {
864 let value = *self
865 .bytes
866 .get(self.offset)
867 .with_context(|| format!("{label} is truncated"))?;
868 self.offset += 1;
869 Ok(value)
870 }
871
872 fn read_u16be(&mut self, label: &str) -> Result<u16> {
873 let end = self
874 .offset
875 .checked_add(2)
876 .with_context(|| format!("{label} offset overflow"))?;
877 let bytes = self
878 .bytes
879 .get(self.offset..end)
880 .with_context(|| format!("{label} is truncated"))?;
881 self.offset = end;
882 Ok(u16::from_be_bytes(bytes.try_into().expect("length checked")))
883 }
884
885 fn read_varuint(&mut self, label: &str) -> Result<u64> {
886 let start = self.offset;
887 let mut value = 0u64;
888 let mut shift = 0u32;
889
890 for byte_index in 0..10 {
891 let byte = self.read_u8(label)?;
892 let payload = u64::from(byte & 0x7f);
893
894 if shift == 63 && payload > 1 {
895 bail!("{label} exceeds u64 range");
896 }
897
898 value |= payload
899 .checked_shl(shift)
900 .with_context(|| format!("{label} shift overflow"))?;
901
902 if byte & 0x80 == 0 {
903 let consumed = self.offset - start;
904 if consumed > 1 {
905 let minimum = 1u64 << (7 * (consumed - 1));
906 if value < minimum {
907 bail!(
908 "{label} uses non-canonical overlong varuint encoding"
909 );
910 }
911 }
912 return Ok(value);
913 }
914
915 shift += 7;
916 if byte_index == 9 {
917 bail!("{label} exceeds ten-byte varuint limit");
918 }
919 }
920
921 unreachable!()
922 }
923
924 fn read_bytes(&mut self, length: usize, label: &str) -> Result<&'a [u8]> {
925 let end = self
926 .offset
927 .checked_add(length)
928 .with_context(|| format!("{label} length overflow"))?;
929 let bytes = self
930 .bytes
931 .get(self.offset..end)
932 .with_context(|| format!("{label} is truncated"))?;
933 self.offset = end;
934 Ok(bytes)
935 }
936}
937
938#[cfg(test)]
939mod tests {
940 use super::*;
941
942 #[test]
943 fn record_profile_codes_round_trip() {
944 assert_eq!(record_profile_code("single45").unwrap(), 0);
945 assert_eq!(record_profile_code("lp").unwrap(), 1);
946 assert_eq!(record_profile_from_code(0).unwrap(), "single45");
947 assert_eq!(record_profile_from_code(1).unwrap(), "lp");
948 assert!(record_profile_from_code(2).is_err());
949 }
950
951 #[test]
952 fn payload_encoding_codes_round_trip() {
953 assert_eq!(payload_encoding_code("rgb").unwrap(), 0);
954 assert_eq!(payload_encoding_code("toned-v1").unwrap(), 1);
955 assert_eq!(payload_encoding_from_code(0).unwrap(), "rgb");
956 assert_eq!(payload_encoding_from_code(1).unwrap(), "toned-v1");
957 assert!(payload_encoding_from_code(2).is_err());
958 }
959
960 #[test]
961 fn release_id_text_round_trips_through_bytes() {
962 let bytes = [
963 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60,
964 0x70, 0x80,
965 ];
966 let text = release_id_to_text(bytes);
967 assert!(text.starts_with("rel_"));
968 assert_eq!(text.len(), 4 + 26);
969 assert_eq!(release_id_to_bytes(&text).unwrap(), bytes);
970 }
971
972 #[test]
973 fn release_id_rejects_missing_prefix() {
974 assert!(release_id_to_bytes("01ARZ3NDEKTSV4RRFFQ69G5FAV").is_err());
975 }
976
977 #[test]
978 fn release_id_rejects_values_above_the_ulid_range() {
979 assert!(
980 release_id_to_bytes("rel_Z1ARZ3NDEKTSV4RRFFQ69G5FAV").is_err()
981 );
982 }
983
984 #[test]
985 fn release_id_accepts_the_maximum_canonical_ulid() {
986 let text = "rel_7ZZZZZZZZZZZZZZZZZZZZZZZZZ";
987 let bytes = release_id_to_bytes(text).unwrap();
988 assert_eq!(release_id_to_text(bytes), text);
989 }
990
991 #[test]
992 fn binary_reference_round_trips_through_decoder() {
993 let mut bytes = Vec::new();
994 bytes.push(SIGNED_RELEASE_REFERENCE_VERSION);
995 bytes.extend_from_slice(&[0x11; 32]);
996 bytes.extend_from_slice(&3u16.to_be_bytes());
997 bytes.extend_from_slice(b"key");
998 bytes.extend_from_slice(&[0x22; 64]);
999
1000 let decoded = decode_signed_release_reference(&bytes).unwrap();
1001 assert_eq!(decoded.release_commitment_sha256, [0x11; 32]);
1002 assert_eq!(decoded.key_id, b"key");
1003 assert_eq!(decoded.signature, vec![0x22; 64]);
1004 }
1005 #[test]
1006 fn toned_carrier_map_round_trips() {
1007 let spans = vec![
1008 ToneSpanDescriptor {
1009 byte_length: 1024,
1010 base: [255, 192, 203],
1011 luma_tolerance: 16,
1012 bits_per_pixel: 21,
1013 ordering: ToneOrdering::ChromaProximity,
1014 },
1015 ToneSpanDescriptor {
1016 byte_length: 513,
1017 base: [20, 40, 80],
1018 luma_tolerance: 8,
1019 bits_per_pixel: 18,
1020 ordering: ToneOrdering::BaseProximity,
1021 },
1022 ];
1023
1024 let bytes =
1025 encode_toned_carrier_map(&spans, Some(1537)).unwrap();
1026 let decoded =
1027 decode_toned_carrier_map(&bytes, Some(1537)).unwrap();
1028
1029 assert_eq!(decoded, spans);
1030 }
1031
1032 #[test]
1033 fn toned_offsets_are_derived() {
1034 let spans = vec![
1035 ToneSpanDescriptor {
1036 byte_length: 5,
1037 base: [1, 2, 3],
1038 luma_tolerance: 0,
1039 bits_per_pixel: 8,
1040 ordering: ToneOrdering::BaseProximity,
1041 },
1042 ToneSpanDescriptor {
1043 byte_length: 7,
1044 base: [4, 5, 6],
1045 luma_tolerance: 1,
1046 bits_per_pixel: 4,
1047 ordering: ToneOrdering::ChromaProximity,
1048 },
1049 ];
1050
1051 let resolved = resolve_tone_spans(&spans, Some(12)).unwrap();
1052 assert_eq!(resolved[0].byte_offset, 0);
1053 assert_eq!(resolved[1].byte_offset, 5);
1054 assert_eq!(resolved[0].pixel_count, 5);
1055 assert_eq!(resolved[1].pixel_offset, 5);
1056 assert_eq!(resolved[1].pixel_count, 14);
1057 }
1058
1059 #[test]
1060 fn toned_map_rejects_overlong_varuint() {
1061 let bytes = [
1062 TONED_CARRIER_MAP_VERSION,
1063 0,
1064 1,
1065 0x81,
1066 0x00,
1067 0,
1068 0,
1069 0,
1070 0,
1071 8,
1072 TONED_ORDERING_BASE_PROXIMITY,
1073 ];
1074 assert!(decode_toned_carrier_map(&bytes, None).is_err());
1075 }
1076
1077}