1use std::cmp::Ordering;
4use std::error::Error;
5use std::fmt;
6use std::hash::{Hash, Hasher};
7use std::num::NonZeroU64;
8
9use crate::limits::{MAX_CONTRACT_LINEAGE_BYTES, MAX_KEY_BYTES, MAX_PROJECTION_GROUP_COMPONENTS};
10use crate::{
11 CanonicalValue, CommitSequence, ContractLineage, ProjectionId, ProjectionPlanHash,
12 decode_canonical_value, encode_canonical_value,
13};
14
15pub const PROJECTION_APPLY_KEY_V1_PREFIX: [u8; 2] = [0x41, 0x01];
17
18pub const PROJECTION_FRONTIER_KEY_V1_PREFIX: [u8; 2] = [0x46, 0x01];
20
21pub const PROJECTION_GROUP_KEY_V1_PREFIX: [u8; 2] = [0x47, 0x01];
23
24#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
26pub struct ProjectionIdentity {
27 contract_lineage: ContractLineage,
28 projection_id: ProjectionId,
29 plan_hash: ProjectionPlanHash,
30}
31
32impl ProjectionIdentity {
33 #[must_use]
35 pub const fn new(
36 contract_lineage: ContractLineage,
37 projection_id: ProjectionId,
38 plan_hash: ProjectionPlanHash,
39 ) -> Self {
40 Self {
41 contract_lineage,
42 projection_id,
43 plan_hash,
44 }
45 }
46
47 pub fn from_canonical_bytes(bytes: &[u8]) -> Result<Self, ProjectionKeyError> {
49 let (identity, consumed) = decode_identity_prefix(bytes)?;
50 if consumed != bytes.len() {
51 return Err(ProjectionKeyError::TrailingBytes);
52 }
53 Ok(identity)
54 }
55
56 #[must_use]
58 pub const fn contract_lineage(&self) -> &ContractLineage {
59 &self.contract_lineage
60 }
61
62 #[must_use]
64 pub const fn projection_id(&self) -> ProjectionId {
65 self.projection_id
66 }
67
68 #[must_use]
70 pub const fn plan_hash(&self) -> ProjectionPlanHash {
71 self.plan_hash
72 }
73
74 #[must_use]
76 pub fn to_canonical_bytes(&self) -> Vec<u8> {
77 let lineage = self.contract_lineage.as_bytes();
78 let mut bytes = Vec::with_capacity(4 + lineage.len() + 4 + 32);
79 bytes.extend_from_slice(&(lineage.len() as u32).to_be_bytes());
80 bytes.extend_from_slice(lineage);
81 bytes.extend_from_slice(&self.projection_id.to_be_bytes());
82 bytes.extend_from_slice(self.plan_hash.as_bytes());
83 bytes
84 }
85}
86
87#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
89pub struct ProjectionGeneration(NonZeroU64);
90
91impl ProjectionGeneration {
92 #[must_use]
94 pub const fn new(value: u64) -> Option<Self> {
95 match NonZeroU64::new(value) {
96 Some(value) => Some(Self(value)),
97 None => None,
98 }
99 }
100
101 #[must_use]
103 pub const fn first() -> Self {
104 Self(NonZeroU64::MIN)
105 }
106
107 #[must_use]
109 pub const fn get(self) -> u64 {
110 self.0.get()
111 }
112
113 #[must_use]
115 pub const fn to_be_bytes(self) -> [u8; 8] {
116 self.get().to_be_bytes()
117 }
118
119 #[must_use]
121 pub const fn checked_next(self) -> Option<Self> {
122 match self.get().checked_add(1) {
123 Some(value) => Self::new(value),
124 None => None,
125 }
126 }
127}
128
129impl TryFrom<u64> for ProjectionGeneration {
130 type Error = ProjectionGenerationError;
131
132 fn try_from(value: u64) -> Result<Self, Self::Error> {
133 Self::new(value).ok_or(ProjectionGenerationError)
134 }
135}
136
137impl From<ProjectionGeneration> for u64 {
138 fn from(value: ProjectionGeneration) -> Self {
139 value.get()
140 }
141}
142
143#[derive(Clone, Copy, Debug, Eq, PartialEq)]
145pub struct ProjectionGenerationError;
146
147impl fmt::Display for ProjectionGenerationError {
148 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
149 formatter.write_str("projection generation must be nonzero")
150 }
151}
152
153impl Error for ProjectionGenerationError {}
154
155#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
157pub struct ProjectionApplyHash([u8; 32]);
158
159impl ProjectionApplyHash {
160 #[must_use]
162 pub const fn from_bytes(bytes: [u8; 32]) -> Self {
163 Self(bytes)
164 }
165
166 #[must_use]
168 pub const fn as_bytes(&self) -> &[u8; 32] {
169 &self.0
170 }
171
172 #[must_use]
174 pub const fn into_bytes(self) -> [u8; 32] {
175 self.0
176 }
177}
178
179#[derive(Clone)]
181pub struct ProjectionGroupKey {
182 bytes: Vec<u8>,
183 identity: ProjectionIdentity,
184 generation: ProjectionGeneration,
185 components: Vec<CanonicalValue>,
186}
187
188impl ProjectionGroupKey {
189 pub fn from_bytes(bytes: Vec<u8>) -> Result<Self, ProjectionKeyError> {
194 let parts = decode_group_parts(&bytes, true)?;
195 Ok(Self {
196 bytes,
197 identity: parts.identity,
198 generation: parts.generation,
199 components: parts.components,
200 })
201 }
202
203 #[must_use]
205 pub fn as_bytes(&self) -> &[u8] {
206 &self.bytes
207 }
208
209 #[must_use]
211 pub fn into_bytes(self) -> Vec<u8> {
212 self.bytes
213 }
214
215 #[must_use]
219 pub fn into_parts(
220 self,
221 ) -> (
222 ProjectionIdentity,
223 ProjectionGeneration,
224 Vec<CanonicalValue>,
225 ) {
226 (self.identity, self.generation, self.components)
227 }
228
229 #[must_use]
231 pub const fn identity(&self) -> &ProjectionIdentity {
232 &self.identity
233 }
234
235 #[must_use]
237 pub const fn generation(&self) -> ProjectionGeneration {
238 self.generation
239 }
240
241 #[must_use]
243 pub fn components(&self) -> &[CanonicalValue] {
244 &self.components
245 }
246}
247
248#[derive(Clone)]
250pub struct ProjectionGroupPrefix {
251 bytes: Vec<u8>,
252 identity: ProjectionIdentity,
253 generation: ProjectionGeneration,
254 components: Vec<CanonicalValue>,
255}
256
257impl ProjectionGroupPrefix {
258 pub fn from_bytes(bytes: Vec<u8>) -> Result<Self, ProjectionKeyError> {
263 let parts = decode_group_parts(&bytes, false)?;
264 Ok(Self {
265 bytes,
266 identity: parts.identity,
267 generation: parts.generation,
268 components: parts.components,
269 })
270 }
271
272 #[must_use]
274 pub fn as_bytes(&self) -> &[u8] {
275 &self.bytes
276 }
277
278 #[must_use]
280 pub const fn identity(&self) -> &ProjectionIdentity {
281 &self.identity
282 }
283
284 #[must_use]
286 pub const fn generation(&self) -> ProjectionGeneration {
287 self.generation
288 }
289
290 #[must_use]
292 pub fn components(&self) -> &[CanonicalValue] {
293 &self.components
294 }
295
296 #[must_use]
300 pub fn exclusive_successor(&self) -> Vec<u8> {
301 let mut successor = self.bytes.clone();
302 for index in (0..successor.len()).rev() {
303 if successor[index] != u8::MAX {
304 successor[index] += 1;
305 successor.truncate(index + 1);
306 return successor;
307 }
308 }
309 unreachable!("a projection group prefix begins with 0x47")
310 }
311}
312
313#[derive(Clone)]
315pub struct ProjectionGroupKeyBuilder(ProjectionComponentsBuilder);
316
317impl ProjectionGroupKeyBuilder {
318 #[must_use]
320 pub fn new(identity: ProjectionIdentity, generation: ProjectionGeneration) -> Self {
321 Self(ProjectionComponentsBuilder::new(identity, generation))
322 }
323
324 pub fn push_component(
326 &mut self,
327 value: CanonicalValue,
328 ) -> Result<&mut Self, ProjectionKeyError> {
329 self.0.push_component(value)?;
330 Ok(self)
331 }
332
333 #[must_use]
335 pub fn as_bytes(&self) -> &[u8] {
336 &self.0.bytes
337 }
338
339 pub fn finish(self) -> Result<ProjectionGroupKey, ProjectionKeyError> {
341 if self.0.components.is_empty() {
342 return Err(ProjectionKeyError::EmptyGroupKey);
343 }
344 Ok(ProjectionGroupKey {
345 bytes: self.0.bytes,
346 identity: self.0.identity,
347 generation: self.0.generation,
348 components: self.0.components,
349 })
350 }
351}
352
353#[derive(Clone)]
355pub struct ProjectionGroupPrefixBuilder(ProjectionComponentsBuilder);
356
357impl ProjectionGroupPrefixBuilder {
358 #[must_use]
360 pub fn new(identity: ProjectionIdentity, generation: ProjectionGeneration) -> Self {
361 Self(ProjectionComponentsBuilder::new(identity, generation))
362 }
363
364 pub fn push_component(
366 &mut self,
367 value: CanonicalValue,
368 ) -> Result<&mut Self, ProjectionKeyError> {
369 self.0.push_component(value)?;
370 Ok(self)
371 }
372
373 #[must_use]
375 pub fn as_bytes(&self) -> &[u8] {
376 &self.0.bytes
377 }
378
379 #[must_use]
381 pub fn finish(self) -> ProjectionGroupPrefix {
382 ProjectionGroupPrefix {
383 bytes: self.0.bytes,
384 identity: self.0.identity,
385 generation: self.0.generation,
386 components: self.0.components,
387 }
388 }
389}
390
391#[derive(Clone)]
392struct ProjectionComponentsBuilder {
393 bytes: Vec<u8>,
394 identity: ProjectionIdentity,
395 generation: ProjectionGeneration,
396 components: Vec<CanonicalValue>,
397}
398
399impl ProjectionComponentsBuilder {
400 fn new(identity: ProjectionIdentity, generation: ProjectionGeneration) -> Self {
401 let identity_bytes = identity.to_canonical_bytes();
402 let mut bytes = Vec::with_capacity(2 + identity_bytes.len() + 8);
403 bytes.extend_from_slice(&PROJECTION_GROUP_KEY_V1_PREFIX);
404 bytes.extend_from_slice(&identity_bytes);
405 bytes.extend_from_slice(&generation.to_be_bytes());
406 Self {
407 bytes,
408 identity,
409 generation,
410 components: Vec::new(),
411 }
412 }
413
414 fn push_component(&mut self, value: CanonicalValue) -> Result<(), ProjectionKeyError> {
415 if self.components.len() == MAX_PROJECTION_GROUP_COMPONENTS {
416 return Err(ProjectionKeyError::TooManyComponents);
417 }
418 let encoded_length = projection_scalar_encoded_length(&value)?;
419 let additional = 4usize
420 .checked_add(encoded_length)
421 .ok_or(ProjectionKeyError::TooLong {
422 actual: usize::MAX,
423 maximum: MAX_KEY_BYTES,
424 })?;
425 let actual =
426 self.bytes
427 .len()
428 .checked_add(additional)
429 .ok_or(ProjectionKeyError::TooLong {
430 actual: usize::MAX,
431 maximum: MAX_KEY_BYTES,
432 })?;
433 if actual > MAX_KEY_BYTES {
434 return Err(ProjectionKeyError::TooLong {
435 actual,
436 maximum: MAX_KEY_BYTES,
437 });
438 }
439
440 let encoded = encode_canonical_value(&value)
441 .map_err(|_| ProjectionKeyError::InvalidCanonicalComponent)?;
442 if encoded.len() != encoded_length {
443 return Err(ProjectionKeyError::InvalidCanonicalComponent);
444 }
445 let encoded_length = u32::try_from(encoded.len())
446 .map_err(|_| ProjectionKeyError::InvalidCanonicalComponent)?;
447 self.bytes.extend_from_slice(&encoded_length.to_be_bytes());
448 self.bytes.extend_from_slice(&encoded);
449 self.components.push(value);
450 Ok(())
451 }
452}
453
454#[derive(Clone)]
456pub struct ProjectionFrontierKey {
457 bytes: Vec<u8>,
458 identity: ProjectionIdentity,
459}
460
461impl ProjectionFrontierKey {
462 #[must_use]
464 pub fn new(identity: ProjectionIdentity) -> Self {
465 let identity_bytes = identity.to_canonical_bytes();
466 let mut bytes = Vec::with_capacity(2 + identity_bytes.len());
467 bytes.extend_from_slice(&PROJECTION_FRONTIER_KEY_V1_PREFIX);
468 bytes.extend_from_slice(&identity_bytes);
469 Self { bytes, identity }
470 }
471
472 pub fn from_bytes(bytes: Vec<u8>) -> Result<Self, ProjectionKeyError> {
474 validate_key_bound(&bytes)?;
475 validate_prefix(&bytes, PROJECTION_FRONTIER_KEY_V1_PREFIX)?;
476 let (identity, consumed) = decode_identity_prefix(&bytes[2..])?;
477 if 2 + consumed != bytes.len() {
478 return Err(ProjectionKeyError::TrailingBytes);
479 }
480 Ok(Self { bytes, identity })
481 }
482
483 #[must_use]
485 pub fn as_bytes(&self) -> &[u8] {
486 &self.bytes
487 }
488
489 #[must_use]
491 pub const fn identity(&self) -> &ProjectionIdentity {
492 &self.identity
493 }
494}
495
496#[derive(Clone)]
498pub struct ProjectionApplyKey {
499 bytes: Vec<u8>,
500 identity: ProjectionIdentity,
501 generation: ProjectionGeneration,
502 commit_sequence: CommitSequence,
503}
504
505impl ProjectionApplyKey {
506 #[must_use]
508 pub fn new(
509 identity: ProjectionIdentity,
510 generation: ProjectionGeneration,
511 commit_sequence: CommitSequence,
512 ) -> Self {
513 let identity_bytes = identity.to_canonical_bytes();
514 let mut bytes = Vec::with_capacity(2 + identity_bytes.len() + 16);
515 bytes.extend_from_slice(&PROJECTION_APPLY_KEY_V1_PREFIX);
516 bytes.extend_from_slice(&identity_bytes);
517 bytes.extend_from_slice(&generation.to_be_bytes());
518 bytes.extend_from_slice(&commit_sequence.to_be_bytes());
519 Self {
520 bytes,
521 identity,
522 generation,
523 commit_sequence,
524 }
525 }
526
527 pub fn from_bytes(bytes: Vec<u8>) -> Result<Self, ProjectionKeyError> {
529 validate_key_bound(&bytes)?;
530 validate_prefix(&bytes, PROJECTION_APPLY_KEY_V1_PREFIX)?;
531 let (identity, consumed) = decode_identity_prefix(&bytes[2..])?;
532 let mut position = 2 + consumed;
533 if bytes.len().saturating_sub(position) != 16 {
534 return Err(ProjectionKeyError::TruncatedOrTrailing);
535 }
536 let generation = decode_generation(read_array::<8>(&bytes, &mut position)?)?;
537 let commit_sequence = decode_commit_sequence(read_array::<8>(&bytes, &mut position)?)?;
538 Ok(Self {
539 bytes,
540 identity,
541 generation,
542 commit_sequence,
543 })
544 }
545
546 #[must_use]
548 pub fn as_bytes(&self) -> &[u8] {
549 &self.bytes
550 }
551
552 #[must_use]
554 pub const fn identity(&self) -> &ProjectionIdentity {
555 &self.identity
556 }
557
558 #[must_use]
560 pub const fn generation(&self) -> ProjectionGeneration {
561 self.generation
562 }
563
564 #[must_use]
566 pub const fn commit_sequence(&self) -> CommitSequence {
567 self.commit_sequence
568 }
569}
570
571macro_rules! byte_key_traits {
572 ($type:ty) => {
573 impl PartialEq for $type {
574 fn eq(&self, other: &Self) -> bool {
575 self.bytes == other.bytes
576 }
577 }
578
579 impl Eq for $type {}
580
581 impl PartialOrd for $type {
582 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
583 Some(self.cmp(other))
584 }
585 }
586
587 impl Ord for $type {
588 fn cmp(&self, other: &Self) -> Ordering {
589 self.bytes.cmp(&other.bytes)
590 }
591 }
592
593 impl Hash for $type {
594 fn hash<H: Hasher>(&self, state: &mut H) {
595 self.bytes.hash(state);
596 }
597 }
598 };
599}
600
601byte_key_traits!(ProjectionGroupKey);
602byte_key_traits!(ProjectionGroupPrefix);
603byte_key_traits!(ProjectionFrontierKey);
604byte_key_traits!(ProjectionApplyKey);
605
606macro_rules! redacted_key_debug {
607 ($type:ty, $name:literal) => {
608 impl fmt::Debug for $type {
609 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
610 formatter
611 .debug_struct($name)
612 .field("bytes", &"[REDACTED]")
613 .field("length", &self.bytes.len())
614 .finish()
615 }
616 }
617 };
618}
619
620redacted_key_debug!(ProjectionGroupKey, "ProjectionGroupKey");
621redacted_key_debug!(ProjectionGroupPrefix, "ProjectionGroupPrefix");
622redacted_key_debug!(ProjectionFrontierKey, "ProjectionFrontierKey");
623redacted_key_debug!(ProjectionApplyKey, "ProjectionApplyKey");
624
625impl TryFrom<Vec<u8>> for ProjectionGroupKey {
626 type Error = ProjectionKeyError;
627
628 fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
629 Self::from_bytes(value)
630 }
631}
632
633impl TryFrom<Vec<u8>> for ProjectionGroupPrefix {
634 type Error = ProjectionKeyError;
635
636 fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
637 Self::from_bytes(value)
638 }
639}
640
641impl TryFrom<Vec<u8>> for ProjectionFrontierKey {
642 type Error = ProjectionKeyError;
643
644 fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
645 Self::from_bytes(value)
646 }
647}
648
649impl TryFrom<Vec<u8>> for ProjectionApplyKey {
650 type Error = ProjectionKeyError;
651
652 fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
653 Self::from_bytes(value)
654 }
655}
656
657#[derive(Clone, Copy, Debug, Eq, PartialEq)]
659pub enum ProjectionKeyError {
660 TooLong {
662 actual: usize,
664 maximum: usize,
666 },
667 Truncated,
669 TrailingBytes,
671 TruncatedOrTrailing,
673 WrongPurpose,
675 UnsupportedVersion,
677 InvalidIdentity,
679 ZeroProjectionId,
681 ZeroGeneration,
683 ZeroCommitSequence,
685 EmptyGroupKey,
687 EmptyComponent,
689 TooManyComponents,
691 NonScalarComponent,
693 InvalidCanonicalComponent,
695}
696
697impl fmt::Display for ProjectionKeyError {
698 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
699 formatter.write_str(match self {
700 Self::TooLong { .. } => "projection key exceeds the hard byte limit",
701 Self::Truncated => "projection key is truncated",
702 Self::TrailingBytes => "projection key has trailing bytes",
703 Self::TruncatedOrTrailing => "projection key has the wrong fixed length",
704 Self::WrongPurpose => "projection key has the wrong purpose",
705 Self::UnsupportedVersion => "projection key version is unsupported",
706 Self::InvalidIdentity => "projection identity is invalid",
707 Self::ZeroProjectionId => "projection ID must be nonzero",
708 Self::ZeroGeneration => "projection generation must be nonzero",
709 Self::ZeroCommitSequence => "commit sequence must be nonzero",
710 Self::EmptyGroupKey => "complete projection group key has no components",
711 Self::EmptyComponent => "projection group component is empty",
712 Self::TooManyComponents => "projection group has too many components",
713 Self::NonScalarComponent => "projection group component is not a scalar",
714 Self::InvalidCanonicalComponent => {
715 "projection group component is not canonically encoded"
716 }
717 })
718 }
719}
720
721impl Error for ProjectionKeyError {}
722
723struct ProjectionGroupParts {
724 identity: ProjectionIdentity,
725 generation: ProjectionGeneration,
726 components: Vec<CanonicalValue>,
727}
728
729fn decode_group_parts(
730 bytes: &[u8],
731 require_nonempty: bool,
732) -> Result<ProjectionGroupParts, ProjectionKeyError> {
733 validate_key_bound(bytes)?;
734 validate_prefix(bytes, PROJECTION_GROUP_KEY_V1_PREFIX)?;
735 let (identity, consumed) = decode_identity_prefix(&bytes[2..])?;
736 let mut position = 2 + consumed;
737 let generation = decode_generation(read_array::<8>(bytes, &mut position)?)?;
738 let mut components = Vec::new();
739 while position < bytes.len() {
740 if components.len() == MAX_PROJECTION_GROUP_COMPONENTS {
741 return Err(ProjectionKeyError::TooManyComponents);
742 }
743 let length = u32::from_be_bytes(read_array::<4>(bytes, &mut position)?) as usize;
744 if length == 0 {
745 return Err(ProjectionKeyError::EmptyComponent);
746 }
747 let end = position
748 .checked_add(length)
749 .ok_or(ProjectionKeyError::Truncated)?;
750 let encoded = bytes
751 .get(position..end)
752 .ok_or(ProjectionKeyError::Truncated)?;
753 let value = decode_canonical_value(encoded)
754 .map_err(|_| ProjectionKeyError::InvalidCanonicalComponent)?;
755 projection_scalar_encoded_length(&value)?;
756 let canonical = encode_canonical_value(&value)
757 .map_err(|_| ProjectionKeyError::InvalidCanonicalComponent)?;
758 if canonical != encoded {
759 return Err(ProjectionKeyError::InvalidCanonicalComponent);
760 }
761 components.push(value);
762 position = end;
763 }
764 if require_nonempty && components.is_empty() {
765 return Err(ProjectionKeyError::EmptyGroupKey);
766 }
767 Ok(ProjectionGroupParts {
768 identity,
769 generation,
770 components,
771 })
772}
773
774fn decode_identity_prefix(bytes: &[u8]) -> Result<(ProjectionIdentity, usize), ProjectionKeyError> {
775 let mut position = 0;
776 let lineage_length = u32::from_be_bytes(read_array::<4>(bytes, &mut position)?) as usize;
777 if lineage_length == 0 || lineage_length > MAX_CONTRACT_LINEAGE_BYTES {
778 return Err(ProjectionKeyError::InvalidIdentity);
779 }
780 let lineage_end = position
781 .checked_add(lineage_length)
782 .ok_or(ProjectionKeyError::InvalidIdentity)?;
783 let lineage_bytes = bytes
784 .get(position..lineage_end)
785 .ok_or(ProjectionKeyError::Truncated)?;
786 let lineage =
787 std::str::from_utf8(lineage_bytes).map_err(|_| ProjectionKeyError::InvalidIdentity)?;
788 let lineage = ContractLineage::new(lineage).map_err(|_| ProjectionKeyError::InvalidIdentity)?;
789 position = lineage_end;
790
791 let projection_raw = u32::from_be_bytes(read_array::<4>(bytes, &mut position)?);
792 if projection_raw == 0 {
793 return Err(ProjectionKeyError::ZeroProjectionId);
794 }
795 let projection_id =
796 ProjectionId::try_from(projection_raw).map_err(|_| ProjectionKeyError::ZeroProjectionId)?;
797 let plan_hash = ProjectionPlanHash::from_bytes(read_array::<32>(bytes, &mut position)?);
798 Ok((
799 ProjectionIdentity::new(lineage, projection_id, plan_hash),
800 position,
801 ))
802}
803
804fn projection_scalar_encoded_length(value: &CanonicalValue) -> Result<usize, ProjectionKeyError> {
805 match value {
806 CanonicalValue::Null
807 | CanonicalValue::List(_)
808 | CanonicalValue::Record(_)
809 | CanonicalValue::Vector(_) => Err(ProjectionKeyError::NonScalarComponent),
810 CanonicalValue::Bool(_) => Ok(3),
811 CanonicalValue::I64(_) | CanonicalValue::U64(_) => Ok(10),
812 CanonicalValue::Decimal(_) => Ok(20),
813 CanonicalValue::Money(_) => Ok(23),
814 CanonicalValue::String(value) => value
815 .len()
816 .checked_add(6)
817 .ok_or(ProjectionKeyError::InvalidCanonicalComponent),
818 CanonicalValue::Bytes(value) => value
819 .len()
820 .checked_add(6)
821 .ok_or(ProjectionKeyError::InvalidCanonicalComponent),
822 CanonicalValue::Timestamp(_) => Ok(14),
823 CanonicalValue::Date(_) => Ok(6),
824 CanonicalValue::Uuid(_) => Ok(18),
825 CanonicalValue::Enum { .. } => Ok(10),
826 }
827}
828
829fn validate_key_bound(bytes: &[u8]) -> Result<(), ProjectionKeyError> {
830 if bytes.len() > MAX_KEY_BYTES {
831 return Err(ProjectionKeyError::TooLong {
832 actual: bytes.len(),
833 maximum: MAX_KEY_BYTES,
834 });
835 }
836 Ok(())
837}
838
839fn validate_prefix(bytes: &[u8], expected: [u8; 2]) -> Result<(), ProjectionKeyError> {
840 let purpose = *bytes.first().ok_or(ProjectionKeyError::Truncated)?;
841 if purpose != expected[0] {
842 return Err(ProjectionKeyError::WrongPurpose);
843 }
844 let version = *bytes.get(1).ok_or(ProjectionKeyError::Truncated)?;
845 if version != expected[1] {
846 return Err(ProjectionKeyError::UnsupportedVersion);
847 }
848 Ok(())
849}
850
851fn decode_generation(bytes: [u8; 8]) -> Result<ProjectionGeneration, ProjectionKeyError> {
852 ProjectionGeneration::new(u64::from_be_bytes(bytes)).ok_or(ProjectionKeyError::ZeroGeneration)
853}
854
855fn decode_commit_sequence(bytes: [u8; 8]) -> Result<CommitSequence, ProjectionKeyError> {
856 let value = u64::from_be_bytes(bytes);
857 if value == 0 {
858 return Err(ProjectionKeyError::ZeroCommitSequence);
859 }
860 CommitSequence::try_from(value).map_err(|_| ProjectionKeyError::ZeroCommitSequence)
861}
862
863fn read_array<const N: usize>(
864 bytes: &[u8],
865 position: &mut usize,
866) -> Result<[u8; N], ProjectionKeyError> {
867 let end = position
868 .checked_add(N)
869 .ok_or(ProjectionKeyError::Truncated)?;
870 let value = bytes
871 .get(*position..end)
872 .ok_or(ProjectionKeyError::Truncated)?;
873 *position = end;
874 value.try_into().map_err(|_| ProjectionKeyError::Truncated)
875}
876
877#[cfg(test)]
878mod tests {
879 use proptest::prelude::*;
880
881 use super::*;
882
883 fn projection_id(value: u32) -> ProjectionId {
884 ProjectionId::try_from(value).expect("nonzero projection ID")
885 }
886
887 fn commit_sequence(value: u64) -> CommitSequence {
888 CommitSequence::try_from(value).expect("nonzero commit sequence")
889 }
890
891 fn identity() -> ProjectionIdentity {
892 ProjectionIdentity::new(
893 ContractLineage::new("budget").expect("valid lineage"),
894 projection_id(0x0102_0304),
895 ProjectionPlanHash::from_bytes(std::array::from_fn(|index| index as u8)),
896 )
897 }
898
899 fn generation() -> ProjectionGeneration {
900 ProjectionGeneration::new(7).expect("nonzero generation")
901 }
902
903 fn hex(input: &str) -> Vec<u8> {
904 input
905 .as_bytes()
906 .chunks_exact(2)
907 .map(|pair| {
908 let pair = std::str::from_utf8(pair).expect("ASCII hex");
909 u8::from_str_radix(pair, 16).expect("valid hex")
910 })
911 .collect()
912 }
913
914 #[test]
915 fn identity_and_fixed_key_vectors_are_stable() {
916 let identity = identity();
917 let identity_bytes = hex(
918 "0000000662756467657401020304000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
919 );
920 assert_eq!(identity.to_canonical_bytes(), identity_bytes);
921 assert_eq!(
922 ProjectionIdentity::from_canonical_bytes(&identity_bytes),
923 Ok(identity.clone())
924 );
925
926 let frontier = ProjectionFrontierKey::new(identity.clone());
927 let mut expected_frontier = vec![0x46, 0x01];
928 expected_frontier.extend_from_slice(&identity_bytes);
929 assert_eq!(frontier.as_bytes(), expected_frontier);
930 assert_eq!(
931 ProjectionFrontierKey::from_bytes(expected_frontier),
932 Ok(frontier)
933 );
934
935 let apply = ProjectionApplyKey::new(identity, generation(), commit_sequence(9));
936 let mut expected_apply = vec![0x41, 0x01];
937 expected_apply.extend_from_slice(&identity_bytes);
938 expected_apply.extend_from_slice(&7_u64.to_be_bytes());
939 expected_apply.extend_from_slice(&9_u64.to_be_bytes());
940 assert_eq!(apply.as_bytes(), expected_apply);
941 assert_eq!(ProjectionApplyKey::from_bytes(expected_apply), Ok(apply));
942 }
943
944 #[test]
945 fn complete_and_prefix_group_vectors_are_stable() {
946 let mut complete = ProjectionGroupKeyBuilder::new(identity(), generation());
947 complete
948 .push_component(CanonicalValue::Bool(true))
949 .expect("bounded")
950 .push_component(CanonicalValue::U64(5))
951 .expect("bounded");
952 let complete = complete.finish().expect("nonempty complete key");
953
954 let mut expected = vec![0x47, 0x01];
955 expected.extend_from_slice(&identity().to_canonical_bytes());
956 expected.extend_from_slice(&7_u64.to_be_bytes());
957 expected.extend_from_slice(&3_u32.to_be_bytes());
958 expected.extend_from_slice(&[0x01, 0x01, 0x01]);
959 expected.extend_from_slice(&10_u32.to_be_bytes());
960 expected.extend_from_slice(&[0x01, 0x03]);
961 expected.extend_from_slice(&5_u64.to_be_bytes());
962 assert_eq!(complete.as_bytes(), expected);
963 assert_eq!(
964 ProjectionGroupKey::from_bytes(expected),
965 Ok(complete.clone())
966 );
967
968 let empty_prefix = ProjectionGroupPrefixBuilder::new(identity(), generation()).finish();
969 assert!(complete.as_bytes().starts_with(empty_prefix.as_bytes()));
970
971 let mut one_prefix = ProjectionGroupPrefixBuilder::new(identity(), generation());
972 one_prefix
973 .push_component(CanonicalValue::Bool(true))
974 .expect("bounded");
975 let one_prefix = one_prefix.finish();
976 assert!(complete.as_bytes().starts_with(one_prefix.as_bytes()));
977 assert!(complete.as_bytes() < one_prefix.exclusive_successor().as_slice());
978 assert_eq!(
979 ProjectionGroupPrefix::from_bytes(one_prefix.as_bytes().to_vec()),
980 Ok(one_prefix)
981 );
982 }
983
984 #[test]
985 fn every_projection_scalar_has_a_complete_key_and_prefix_golden() {
986 let decimal_spec = crate::DecimalSpec::new(5, 2).expect("valid decimal type");
987 let decimal = crate::Decimal::new(decimal_spec, -1234).expect("bounded decimal");
988 let values = vec![
989 CanonicalValue::Bool(true),
990 CanonicalValue::I64(-1),
991 CanonicalValue::U64(u64::MAX),
992 CanonicalValue::Decimal(decimal),
993 CanonicalValue::Money(crate::Money::new(
994 crate::CurrencyCode::new("USD").expect("valid currency"),
995 decimal,
996 )),
997 CanonicalValue::string("é").expect("bounded string"),
998 CanonicalValue::bytes([0, 0xff]).expect("bounded bytes"),
999 CanonicalValue::Timestamp(
1000 crate::Timestamp::new(-1, 999_999_999).expect("valid timestamp"),
1001 ),
1002 CanonicalValue::Date(crate::Date::from_days_since_unix_epoch(-1)),
1003 CanonicalValue::Uuid([
1004 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
1005 0x0e, 0x0f,
1006 ]),
1007 CanonicalValue::Enum {
1008 type_id: crate::EnumTypeId::new(42).expect("nonzero enum type ID"),
1009 variant_id: crate::EnumVariantId::new(7).expect("nonzero enum variant ID"),
1010 },
1011 ];
1012 let encoded_components = [
1013 "010101",
1014 "0102ffffffffffffffff",
1015 "0103ffffffffffffffff",
1016 "01040502fffffffffffffffffffffffffffffb2e",
1017 "01055553440502fffffffffffffffffffffffffffffb2e",
1018 "010600000002c3a9",
1019 "01070000000200ff",
1020 "0108ffffffffffffffff3b9ac9ff",
1021 "0109ffffffff",
1022 "010a000102030405060708090a0b0c0d0e0f",
1023 "010b0000002a00000007",
1024 ]
1025 .map(hex);
1026 assert_eq!(
1027 encoded_components.each_ref().map(|value| value.len()),
1028 [3, 10, 10, 20, 23, 8, 8, 14, 6, 18, 10]
1029 );
1030
1031 let mut complete = ProjectionGroupKeyBuilder::new(identity(), generation());
1032 let mut prefix = ProjectionGroupPrefixBuilder::new(identity(), generation());
1033 for value in &values {
1034 complete
1035 .push_component(value.clone())
1036 .expect("scalar key remains bounded");
1037 prefix
1038 .push_component(value.clone())
1039 .expect("scalar prefix remains bounded");
1040 }
1041 let complete = complete.finish().expect("nonempty complete key");
1042 let prefix = prefix.finish();
1043
1044 let mut expected = vec![0x47, 0x01];
1045 expected.extend_from_slice(&identity().to_canonical_bytes());
1046 expected.extend_from_slice(&generation().to_be_bytes());
1047 for component in encoded_components {
1048 expected.extend_from_slice(&(component.len() as u32).to_be_bytes());
1049 expected.extend_from_slice(&component);
1050 }
1051 assert_eq!(complete.as_bytes(), expected);
1052 assert_eq!(prefix.as_bytes(), expected);
1053 assert_eq!(complete.components(), values);
1054 assert_eq!(prefix.components(), values);
1055 assert_eq!(
1056 ProjectionGroupKey::from_bytes(expected.clone()),
1057 Ok(complete)
1058 );
1059 assert_eq!(ProjectionGroupPrefix::from_bytes(expected), Ok(prefix));
1060 }
1061
1062 #[test]
1063 fn generations_are_nonzero_checked_and_never_wrap() {
1064 assert_eq!(ProjectionGeneration::new(0), None);
1065 assert_eq!(ProjectionGeneration::first().get(), 1);
1066 assert_eq!(
1067 ProjectionGeneration::first().checked_next(),
1068 ProjectionGeneration::new(2)
1069 );
1070 assert_eq!(
1071 ProjectionGeneration::new(u64::MAX)
1072 .expect("nonzero")
1073 .checked_next(),
1074 None
1075 );
1076 }
1077
1078 #[test]
1079 fn group_builder_rejects_non_scalars_and_oversized_components_without_mutation() {
1080 let mut builder = ProjectionGroupKeyBuilder::new(identity(), generation());
1081 let before = builder.as_bytes().to_vec();
1082 assert!(matches!(
1083 builder.push_component(CanonicalValue::Null),
1084 Err(ProjectionKeyError::NonScalarComponent)
1085 ));
1086 assert_eq!(builder.as_bytes(), before);
1087
1088 let oversized = CanonicalValue::string("x".repeat(MAX_KEY_BYTES))
1089 .expect("within canonical document limit");
1090 assert!(matches!(
1091 builder.push_component(oversized),
1092 Err(ProjectionKeyError::TooLong { .. })
1093 ));
1094 assert_eq!(builder.as_bytes(), before);
1095 assert_eq!(builder.finish(), Err(ProjectionKeyError::EmptyGroupKey));
1096 }
1097
1098 #[test]
1099 fn complete_group_key_honors_the_exact_four_kibibyte_boundary() {
1100 let base = ProjectionGroupKeyBuilder::new(identity(), generation())
1101 .as_bytes()
1102 .len();
1103 let exact_string_bytes = MAX_KEY_BYTES - base - 4 - 6;
1104
1105 let mut exact = ProjectionGroupKeyBuilder::new(identity(), generation());
1106 exact
1107 .push_component(
1108 CanonicalValue::string("x".repeat(exact_string_bytes)).expect("bounded value"),
1109 )
1110 .expect("exactly four KiB");
1111 let exact = exact.finish().expect("complete key");
1112 assert_eq!(exact.as_bytes().len(), MAX_KEY_BYTES);
1113 assert_eq!(
1114 ProjectionGroupKey::from_bytes(exact.as_bytes().to_vec()),
1115 Ok(exact)
1116 );
1117
1118 let mut too_large = ProjectionGroupKeyBuilder::new(identity(), generation());
1119 assert!(matches!(
1120 too_large.push_component(
1121 CanonicalValue::string("x".repeat(exact_string_bytes + 1)).expect("bounded value"),
1122 ),
1123 Err(ProjectionKeyError::TooLong {
1124 actual: 4_097,
1125 maximum: MAX_KEY_BYTES,
1126 })
1127 ));
1128 }
1129
1130 #[test]
1131 fn decoders_reject_zero_and_malformed_fields() {
1132 let identity_bytes = identity().to_canonical_bytes();
1133 let projection_id_offset = 4 + "budget".len();
1134 let mut zero_id = identity_bytes.clone();
1135 zero_id[projection_id_offset..projection_id_offset + 4].fill(0);
1136 assert_eq!(
1137 ProjectionIdentity::from_canonical_bytes(&zero_id),
1138 Err(ProjectionKeyError::ZeroProjectionId)
1139 );
1140
1141 let mut zero_generation = ProjectionGroupPrefixBuilder::new(identity(), generation())
1142 .finish()
1143 .as_bytes()
1144 .to_vec();
1145 let generation_offset = 2 + identity_bytes.len();
1146 zero_generation[generation_offset..generation_offset + 8].fill(0);
1147 assert_eq!(
1148 ProjectionGroupPrefix::from_bytes(zero_generation),
1149 Err(ProjectionKeyError::ZeroGeneration)
1150 );
1151
1152 let mut zero_sequence =
1153 ProjectionApplyKey::new(identity(), generation(), commit_sequence(9))
1154 .as_bytes()
1155 .to_vec();
1156 let sequence_offset = 2 + identity_bytes.len() + 8;
1157 zero_sequence[sequence_offset..sequence_offset + 8].fill(0);
1158 assert_eq!(
1159 ProjectionApplyKey::from_bytes(zero_sequence),
1160 Err(ProjectionKeyError::ZeroCommitSequence)
1161 );
1162
1163 let mut wrong_purpose = ProjectionFrontierKey::new(identity()).as_bytes().to_vec();
1164 wrong_purpose[0] = 0x45;
1165 assert_eq!(
1166 ProjectionFrontierKey::from_bytes(wrong_purpose),
1167 Err(ProjectionKeyError::WrongPurpose)
1168 );
1169 }
1170
1171 #[test]
1172 fn group_key_formatting_never_exposes_application_values() {
1173 let mut builder = ProjectionGroupKeyBuilder::new(identity(), generation());
1174 builder
1175 .push_component(CanonicalValue::string("TOP_SECRET").expect("bounded"))
1176 .expect("bounded key");
1177 let key = builder.finish().expect("complete key");
1178 let debug = format!("{key:?}");
1179 assert!(debug.contains("[REDACTED]"));
1180 assert!(!debug.contains("TOP_SECRET"));
1181 }
1182
1183 proptest! {
1184 #[test]
1185 fn canonical_group_keys_round_trip_for_bounded_scalar_sequences(
1186 values in proptest::collection::vec(any::<bool>(), 1..64),
1187 ) {
1188 let mut builder = ProjectionGroupKeyBuilder::new(identity(), generation());
1189 for value in values {
1190 builder
1191 .push_component(CanonicalValue::Bool(value))
1192 .expect("generated key is bounded");
1193 }
1194 let key = builder.finish().expect("nonempty complete key");
1195 prop_assert_eq!(
1196 ProjectionGroupKey::from_bytes(key.as_bytes().to_vec()),
1197 Ok(key)
1198 );
1199 }
1200
1201 #[test]
1202 fn arbitrary_projection_key_bytes_never_panic(
1203 bytes in proptest::collection::vec(any::<u8>(), 0..5_000),
1204 ) {
1205 let _ = ProjectionGroupKey::from_bytes(bytes.clone());
1206 let _ = ProjectionGroupPrefix::from_bytes(bytes.clone());
1207 let _ = ProjectionFrontierKey::from_bytes(bytes.clone());
1208 let _ = ProjectionApplyKey::from_bytes(bytes);
1209 }
1210 }
1211}
1212
1213crate::inspect_owned_capacity_copy_v1!(ProjectionGeneration);
1215
1216crate::inspect_owned_capacity_struct_v1!(ProjectionIdentity {
1217 owned: [contract_lineage],
1218 inline: [projection_id, plan_hash]
1219});
1220crate::inspect_owned_capacity_struct_v1!(ProjectionGroupKey {
1221 owned: [bytes, identity, components],
1222 inline: [generation]
1223});