Skip to main content

ic_memory/
physical.rs

1use serde::{Deserialize, Serialize};
2
3const COMMIT_MARKER: u64 = 0x4943_4D45_4D43_4F4D;
4const FNV_OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
5const FNV_PRIME: u64 = 0x0000_0100_0000_01b3;
6
7///
8/// ProtectedGenerationSlot
9///
10/// One physical generation slot that can participate in protected recovery.
11///
12/// This is an advanced low-level API for framework or stable-IO owners. Most
13/// callers should use the ledger commit/recovery flow instead of implementing
14/// physical slot recovery directly.
15pub trait ProtectedGenerationSlot: Eq {
16    /// Generation encoded by this slot.
17    fn generation(&self) -> u64;
18
19    /// Return whether the slot passed its marker/checksum validation.
20    fn validates(&self) -> bool;
21}
22
23///
24/// DualProtectedCommitStore
25///
26/// Physical store with two protected generation slots.
27///
28/// This is an advanced low-level API for framework or stable-IO owners. Normal
29/// allocation flows recover and commit ledgers through the higher-level ledger
30/// commit APIs.
31pub trait DualProtectedCommitStore {
32    /// Protected slot record type.
33    type Slot: ProtectedGenerationSlot;
34
35    /// Borrow the first physical slot.
36    fn slot0(&self) -> Option<&Self::Slot>;
37
38    /// Borrow the second physical slot.
39    fn slot1(&self) -> Option<&Self::Slot>;
40
41    /// Return true when no commit slot has ever been written.
42    fn is_uninitialized(&self) -> bool {
43        self.slot0().is_none() && self.slot1().is_none()
44    }
45
46    /// Return the highest-generation valid physical slot.
47    fn authoritative_slot(&self) -> Result<AuthoritativeSlot<'_, Self::Slot>, CommitRecoveryError> {
48        select_authoritative_slot(self.slot0(), self.slot1())
49    }
50
51    /// Return the slot that should receive the next staged generation write.
52    ///
53    /// The result is derived from validated recovery state. It does not trust a
54    /// separate current-pointer/header field.
55    fn inactive_slot_index(&self) -> CommitSlotIndex {
56        match self.authoritative_slot() {
57            Ok(authoritative) => authoritative.index.opposite(),
58            Err(_) => CommitSlotIndex::Slot0,
59        }
60    }
61}
62
63///
64/// CommitSlotIndex
65///
66/// Physical dual-slot index selected by protected recovery.
67#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
68pub enum CommitSlotIndex {
69    /// First physical commit slot.
70    Slot0,
71    /// Second physical commit slot.
72    Slot1,
73}
74
75impl CommitSlotIndex {
76    /// Return the opposite physical slot.
77    #[must_use]
78    pub const fn opposite(self) -> Self {
79        match self {
80            Self::Slot0 => Self::Slot1,
81            Self::Slot1 => Self::Slot0,
82        }
83    }
84}
85
86///
87/// AuthoritativeSlot
88///
89/// Highest-generation valid slot selected by protected recovery.
90#[derive(Clone, Copy, Debug, Eq, PartialEq)]
91pub struct AuthoritativeSlot<'slot, T> {
92    /// Physical slot index.
93    pub index: CommitSlotIndex,
94    /// Valid committed generation in that slot.
95    pub record: &'slot T,
96}
97
98/// Select the highest-generation valid physical slot.
99///
100/// This is an advanced recovery helper for framework or stable-IO owners. It
101/// only selects among supplied protected slots; it does not decode or validate
102/// the allocation ledger payload.
103pub fn select_authoritative_slot<'slot, T: ProtectedGenerationSlot>(
104    slot0: Option<&'slot T>,
105    slot1: Option<&'slot T>,
106) -> Result<AuthoritativeSlot<'slot, T>, CommitRecoveryError> {
107    let slot0 = slot0
108        .filter(|slot| slot.validates())
109        .map(|record| AuthoritativeSlot {
110            index: CommitSlotIndex::Slot0,
111            record,
112        });
113    let slot1 = slot1
114        .filter(|slot| slot.validates())
115        .map(|record| AuthoritativeSlot {
116            index: CommitSlotIndex::Slot1,
117            record,
118        });
119
120    match (slot0, slot1) {
121        (Some(left), Some(right))
122            if left.record.generation() == right.record.generation()
123                && left.record != right.record =>
124        {
125            Err(CommitRecoveryError::AmbiguousGeneration {
126                generation: left.record.generation(),
127            })
128        }
129        (Some(left), Some(right)) if right.record.generation() > left.record.generation() => {
130            Ok(right)
131        }
132        (Some(left), Some(_) | None) => Ok(left),
133        (None, Some(right)) => Ok(right),
134        (None, None) => Err(CommitRecoveryError::NoValidGeneration),
135    }
136}
137
138///
139/// CommittedGenerationBytes
140///
141/// Physically committed ledger generation payload protected by a checksum.
142///
143/// This is an advanced low-level DTO for framework or stable-IO owners. Its
144/// recovered bytes are untrusted until marker/checksum validation and ledger
145/// decoding/integrity validation have both succeeded.
146#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
147#[serde(deny_unknown_fields)]
148pub struct CommittedGenerationBytes {
149    /// Generation number represented by this payload.
150    pub(crate) generation: u64,
151    /// Physical commit marker. Readers reject records with an invalid marker.
152    pub(crate) commit_marker: u64,
153    /// Checksum over the generation, marker, and payload bytes.
154    pub(crate) checksum: u64,
155    /// Encoded ledger generation payload.
156    pub(crate) payload: Vec<u8>,
157}
158
159impl CommittedGenerationBytes {
160    /// Build a committed generation record.
161    #[must_use]
162    pub fn new(generation: u64, payload: Vec<u8>) -> Self {
163        let mut record = Self {
164            generation,
165            commit_marker: COMMIT_MARKER,
166            checksum: 0,
167            payload,
168        };
169        record.checksum = generation_checksum(&record);
170        record
171    }
172
173    /// Return the generation number represented by this payload.
174    #[must_use]
175    pub const fn generation(&self) -> u64 {
176        self.generation
177    }
178
179    /// Return the physical commit marker.
180    ///
181    /// This is diagnostic data from a recovered record. Callers should use
182    /// [`CommittedGenerationBytes::validates`] before treating the record as
183    /// authoritative.
184    #[must_use]
185    pub const fn commit_marker(&self) -> u64 {
186        self.commit_marker
187    }
188
189    /// Return the checksum over the generation, marker, and payload bytes.
190    ///
191    /// The checksum is non-cryptographic and detects torn writes or accidental
192    /// corruption only.
193    #[must_use]
194    pub const fn checksum(&self) -> u64 {
195        self.checksum
196    }
197
198    /// Borrow the encoded ledger generation payload.
199    #[must_use]
200    pub fn payload(&self) -> &[u8] {
201        &self.payload
202    }
203
204    /// Return whether the marker and checksum validate.
205    #[must_use]
206    pub fn validates(&self) -> bool {
207        self.commit_marker == COMMIT_MARKER && self.checksum == generation_checksum(self)
208    }
209}
210
211impl ProtectedGenerationSlot for CommittedGenerationBytes {
212    fn generation(&self) -> u64 {
213        self.generation
214    }
215
216    fn validates(&self) -> bool {
217        self.validates()
218    }
219}
220
221///
222/// DualCommitStore
223///
224/// Dual-slot protected commit protocol for encoded ledger generations.
225///
226/// This is an advanced low-level API for framework or stable-IO owners. Most
227/// applications should recover, validate, and commit through the allocation
228/// ledger flow rather than manipulating encoded physical commit slots directly.
229///
230/// Writers stage a complete generation record into the inactive slot. Readers
231/// recover by selecting the highest-generation valid slot. A torn or partial
232/// write cannot become authoritative unless its marker and checksum validate.
233///
234/// The checksum is for torn-write and accidental-corruption detection only. It
235/// is not a cryptographic hash and does not provide adversarial tamper
236/// resistance.
237#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
238#[serde(deny_unknown_fields)]
239pub struct DualCommitStore {
240    /// First physical commit slot.
241    pub(crate) slot0: Option<CommittedGenerationBytes>,
242    /// Second physical commit slot.
243    pub(crate) slot1: Option<CommittedGenerationBytes>,
244}
245
246impl DualCommitStore {
247    /// Return true when no commit slot has ever been written.
248    #[must_use]
249    pub const fn is_uninitialized(&self) -> bool {
250        self.slot0.is_none() && self.slot1.is_none()
251    }
252
253    /// Borrow the first physical commit slot.
254    ///
255    /// Slot records are untrusted recovered state until recovery selects an
256    /// authoritative generation.
257    #[must_use]
258    pub const fn slot0(&self) -> Option<&CommittedGenerationBytes> {
259        self.slot0.as_ref()
260    }
261
262    /// Borrow the second physical commit slot.
263    ///
264    /// Slot records are untrusted recovered state until recovery selects an
265    /// authoritative generation.
266    #[must_use]
267    pub const fn slot1(&self) -> Option<&CommittedGenerationBytes> {
268        self.slot1.as_ref()
269    }
270
271    /// Return the highest-generation valid committed record.
272    pub fn authoritative(&self) -> Result<&CommittedGenerationBytes, CommitRecoveryError> {
273        self.authoritative_slot()
274            .map(|authoritative| authoritative.record)
275    }
276
277    /// Build a read-only recovery diagnostic for the protected commit slots.
278    #[must_use]
279    pub fn diagnostic(&self) -> CommitStoreDiagnostic {
280        CommitStoreDiagnostic::from_store(self)
281    }
282
283    /// Commit a new payload to the inactive slot.
284    ///
285    /// The returned store models the post-write physical state. If a real
286    /// substrate traps before the inactive slot is fully written, the prior
287    /// valid slot remains authoritative under `authoritative`.
288    pub fn commit_payload(
289        &mut self,
290        payload: Vec<u8>,
291    ) -> Result<&CommittedGenerationBytes, CommitRecoveryError> {
292        let next_generation =
293            match self.authoritative() {
294                Ok(record) => record.generation.checked_add(1).ok_or(
295                    CommitRecoveryError::GenerationOverflow {
296                        generation: record.generation,
297                    },
298                )?,
299                Err(CommitRecoveryError::NoValidGeneration) if self.is_uninitialized() => 0,
300                Err(err) => return Err(err),
301            };
302
303        self.commit_payload_at_generation(next_generation, payload)
304    }
305
306    /// Commit `payload` as an explicitly numbered physical generation.
307    ///
308    /// This is the low-level physical-slot primitive used by
309    /// [`crate::LedgerCommitStore`]. Normal ledger commits should use
310    /// [`crate::LedgerCommitStore::commit`] or [`crate::AllocationBootstrap`] so
311    /// payloads are decoded, compatibility-checked, and integrity-validated
312    /// before they can become authoritative.
313    ///
314    /// The physical slot generation is checked against the recovered physical
315    /// predecessor. This method does not inspect `payload`.
316    pub fn commit_payload_at_generation(
317        &mut self,
318        generation: u64,
319        payload: Vec<u8>,
320    ) -> Result<&CommittedGenerationBytes, CommitRecoveryError> {
321        match self.authoritative() {
322            Ok(record) => {
323                let expected = record.generation.checked_add(1).ok_or(
324                    CommitRecoveryError::GenerationOverflow {
325                        generation: record.generation,
326                    },
327                )?;
328                if generation != expected {
329                    return Err(CommitRecoveryError::UnexpectedGeneration {
330                        expected,
331                        actual: generation,
332                    });
333                }
334            }
335            Err(CommitRecoveryError::NoValidGeneration) if self.is_uninitialized() => {}
336            Err(err) => return Err(err),
337        }
338
339        let next = CommittedGenerationBytes::new(generation, payload);
340
341        if self.inactive_slot_index() == CommitSlotIndex::Slot0 {
342            self.slot0 = Some(next);
343        } else {
344            self.slot1 = Some(next);
345        }
346
347        self.authoritative()
348    }
349
350    /// Simulate a torn write into the inactive slot.
351    ///
352    /// This helper is intentionally part of the model because recovery behavior
353    /// is an ABI requirement, not an implementation detail.
354    #[cfg(test)]
355    pub fn write_corrupt_inactive_slot(&mut self, generation: u64, payload: Vec<u8>) {
356        let mut corrupt = CommittedGenerationBytes::new(generation, payload);
357        corrupt.checksum = corrupt.checksum.wrapping_add(1);
358
359        if self.inactive_slot_index() == CommitSlotIndex::Slot0 {
360            self.slot0 = Some(corrupt);
361        } else {
362            self.slot1 = Some(corrupt);
363        }
364    }
365}
366
367impl DualProtectedCommitStore for DualCommitStore {
368    type Slot = CommittedGenerationBytes;
369
370    fn slot0(&self) -> Option<&Self::Slot> {
371        self.slot0.as_ref()
372    }
373
374    fn slot1(&self) -> Option<&Self::Slot> {
375        self.slot1.as_ref()
376    }
377}
378
379///
380/// CommitStoreDiagnostic
381///
382/// Read-only diagnostic summary of protected commit recovery state.
383#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
384#[serde(deny_unknown_fields)]
385pub struct CommitStoreDiagnostic {
386    /// First physical commit slot diagnostic.
387    pub slot0: CommitSlotDiagnostic,
388    /// Second physical commit slot diagnostic.
389    pub slot1: CommitSlotDiagnostic,
390    /// Highest valid generation selected by recovery.
391    pub authoritative_generation: Option<u64>,
392    /// Recovery error when no authoritative generation can be selected.
393    pub recovery_error: Option<CommitRecoveryError>,
394}
395
396impl CommitStoreDiagnostic {
397    /// Build a read-only recovery diagnostic from a dual protected commit store.
398    #[must_use]
399    pub fn from_store<S: DualProtectedCommitStore>(store: &S) -> Self {
400        let (authoritative_generation, recovery_error) = match store.authoritative_slot() {
401            Ok(slot) => (Some(slot.record.generation()), None),
402            Err(err) => (None, Some(err)),
403        };
404        Self {
405            slot0: CommitSlotDiagnostic::from_slot(store.slot0()),
406            slot1: CommitSlotDiagnostic::from_slot(store.slot1()),
407            authoritative_generation,
408            recovery_error,
409        }
410    }
411}
412
413///
414/// CommitSlotDiagnostic
415///
416/// Read-only diagnostic summary for one protected commit slot.
417#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
418#[serde(deny_unknown_fields)]
419pub struct CommitSlotDiagnostic {
420    /// Whether a physical slot record is present.
421    pub present: bool,
422    /// Generation encoded by the slot, if present.
423    pub generation: Option<u64>,
424    /// Whether marker and checksum validation succeeded.
425    pub valid: bool,
426}
427
428impl CommitSlotDiagnostic {
429    fn from_slot<T: ProtectedGenerationSlot>(slot: Option<&T>) -> Self {
430        match slot {
431            Some(record) => Self {
432                present: true,
433                generation: Some(record.generation()),
434                valid: record.validates(),
435            },
436            None => Self {
437                present: false,
438                generation: None,
439                valid: false,
440            },
441        }
442    }
443}
444
445///
446/// CommitRecoveryError
447///
448/// Protected commit recovery failure.
449#[derive(Clone, Copy, Debug, Deserialize, Eq, thiserror::Error, PartialEq, Serialize)]
450pub enum CommitRecoveryError {
451    /// No committed slot passed marker and checksum validation.
452    #[error("no valid committed ledger generation")]
453    NoValidGeneration,
454    /// Both physical slots validated at the same generation but contained different bytes.
455    #[error("ambiguous committed ledger generation {generation}")]
456    AmbiguousGeneration {
457        /// Ambiguous physical generation.
458        generation: u64,
459    },
460    /// Physical generation advancement would overflow.
461    #[error("committed ledger generation {generation} cannot be advanced without overflow")]
462    GenerationOverflow {
463        /// Last valid physical generation.
464        generation: u64,
465    },
466    /// Caller attempted to commit a physical generation other than the next generation.
467    #[error("expected committed ledger generation {expected}, got {actual}")]
468    UnexpectedGeneration {
469        /// Expected next physical generation.
470        expected: u64,
471        /// Actual requested physical generation.
472        actual: u64,
473    },
474}
475
476fn generation_checksum(generation: &CommittedGenerationBytes) -> u64 {
477    let mut hash = FNV_OFFSET;
478    hash = hash_u64(hash, generation.generation);
479    hash = hash_u64(hash, generation.commit_marker);
480    hash = hash_usize(hash, generation.payload.len());
481    for byte in &generation.payload {
482        hash = hash_byte(hash, *byte);
483    }
484    hash
485}
486
487fn hash_usize(hash: u64, value: usize) -> u64 {
488    hash_u64(hash, value as u64)
489}
490
491fn hash_u64(mut hash: u64, value: u64) -> u64 {
492    for byte in value.to_le_bytes() {
493        hash = hash_byte(hash, byte);
494    }
495    hash
496}
497
498const fn hash_byte(hash: u64, byte: u8) -> u64 {
499    (hash ^ byte as u64).wrapping_mul(FNV_PRIME)
500}
501
502#[cfg(test)]
503mod tests {
504    use super::*;
505
506    fn payload(value: u8) -> Vec<u8> {
507        vec![value; 4]
508    }
509
510    #[test]
511    fn committed_generation_validates_marker_and_checksum() {
512        let mut generation = CommittedGenerationBytes::new(7, payload(1));
513        assert!(generation.validates());
514
515        generation.checksum = generation.checksum.wrapping_add(1);
516        assert!(!generation.validates());
517    }
518
519    #[test]
520    fn physical_commit_accessors_expose_read_only_state() {
521        let mut store = DualCommitStore::default();
522        store.commit_payload(payload(1)).expect("first commit");
523
524        let slot = store.slot0().expect("first slot");
525
526        assert_eq!(slot.generation(), 0);
527        assert_eq!(slot.payload(), payload(1).as_slice());
528        assert_eq!(slot.commit_marker(), COMMIT_MARKER);
529        assert_eq!(slot.checksum(), generation_checksum(slot));
530        assert!(store.slot1().is_none());
531    }
532
533    #[test]
534    fn authoritative_selects_highest_valid_generation() {
535        let mut store = DualCommitStore::default();
536        store.commit_payload(payload(1)).expect("first commit");
537        store.commit_payload(payload(2)).expect("second commit");
538
539        let authoritative = store.authoritative().expect("authoritative");
540        let authoritative_slot =
541            select_authoritative_slot(store.slot0.as_ref(), store.slot1.as_ref())
542                .expect("authoritative slot");
543
544        assert_eq!(authoritative.generation, 1);
545        assert_eq!(authoritative.payload, payload(2));
546        assert_eq!(authoritative_slot.index, CommitSlotIndex::Slot1);
547        assert_eq!(authoritative_slot.record.payload, payload(2));
548    }
549
550    #[test]
551    fn corrupt_newer_slot_leaves_prior_generation_authoritative() {
552        let mut store = DualCommitStore::default();
553        store.commit_payload(payload(1)).expect("first commit");
554        store.write_corrupt_inactive_slot(1, payload(2));
555
556        let authoritative = store.authoritative().expect("authoritative");
557
558        assert_eq!(authoritative.generation, 0);
559        assert_eq!(authoritative.payload, payload(1));
560    }
561
562    #[test]
563    fn no_valid_generation_fails_closed() {
564        let mut store = DualCommitStore::default();
565        store.write_corrupt_inactive_slot(0, payload(1));
566        store.write_corrupt_inactive_slot(1, payload(2));
567
568        let err = store.authoritative().expect_err("no valid slot");
569
570        assert_eq!(err, CommitRecoveryError::NoValidGeneration);
571    }
572
573    #[test]
574    fn same_generation_identical_slots_recover_deterministically() {
575        let committed = CommittedGenerationBytes::new(7, payload(1));
576        let store = DualCommitStore {
577            slot0: Some(committed.clone()),
578            slot1: Some(committed),
579        };
580
581        let authoritative = store.authoritative_slot().expect("authoritative");
582
583        assert_eq!(authoritative.index, CommitSlotIndex::Slot0);
584        assert_eq!(authoritative.record.generation, 7);
585    }
586
587    #[test]
588    fn same_generation_divergent_slots_fail_closed() {
589        let store = DualCommitStore {
590            slot0: Some(CommittedGenerationBytes::new(7, payload(1))),
591            slot1: Some(CommittedGenerationBytes::new(7, payload(2))),
592        };
593
594        let err = store.authoritative().expect_err("ambiguous generation");
595
596        assert_eq!(
597            err,
598            CommitRecoveryError::AmbiguousGeneration { generation: 7 }
599        );
600    }
601
602    #[test]
603    fn physical_generation_overflow_fails_closed() {
604        let mut store = DualCommitStore {
605            slot0: Some(CommittedGenerationBytes::new(u64::MAX, payload(1))),
606            slot1: None,
607        };
608
609        let err = store
610            .commit_payload(payload(2))
611            .expect_err("overflow must fail");
612
613        assert_eq!(
614            err,
615            CommitRecoveryError::GenerationOverflow {
616                generation: u64::MAX
617            }
618        );
619    }
620
621    #[test]
622    fn diagnostic_reports_authoritative_generation_and_corrupt_slots() {
623        let mut store = DualCommitStore::default();
624        store.commit_payload(payload(1)).expect("first commit");
625        store.write_corrupt_inactive_slot(1, payload(2));
626
627        let diagnostic = store.diagnostic();
628
629        assert_eq!(diagnostic.authoritative_generation, Some(0));
630        assert_eq!(diagnostic.recovery_error, None);
631        assert_eq!(diagnostic.slot0.generation, Some(0));
632        assert!(diagnostic.slot0.valid);
633        assert_eq!(diagnostic.slot1.generation, Some(1));
634        assert!(!diagnostic.slot1.valid);
635    }
636
637    #[test]
638    fn diagnostic_reports_no_valid_generation_for_empty_store() {
639        let diagnostic = DualCommitStore::default().diagnostic();
640
641        assert_eq!(diagnostic.authoritative_generation, None);
642        assert_eq!(
643            diagnostic.recovery_error,
644            Some(CommitRecoveryError::NoValidGeneration)
645        );
646        assert!(!diagnostic.slot0.present);
647        assert!(!diagnostic.slot1.present);
648    }
649
650    #[test]
651    fn diagnostic_builds_from_any_dual_protected_store() {
652        #[derive(Eq, PartialEq)]
653        struct TestSlot {
654            generation: u64,
655            valid: bool,
656        }
657
658        impl ProtectedGenerationSlot for TestSlot {
659            fn generation(&self) -> u64 {
660                self.generation
661            }
662
663            fn validates(&self) -> bool {
664                self.valid
665            }
666        }
667
668        struct TestStore {
669            slot0: Option<TestSlot>,
670            slot1: Option<TestSlot>,
671        }
672
673        impl DualProtectedCommitStore for TestStore {
674            type Slot = TestSlot;
675
676            fn slot0(&self) -> Option<&Self::Slot> {
677                self.slot0.as_ref()
678            }
679
680            fn slot1(&self) -> Option<&Self::Slot> {
681                self.slot1.as_ref()
682            }
683        }
684
685        let diagnostic = CommitStoreDiagnostic::from_store(&TestStore {
686            slot0: Some(TestSlot {
687                generation: 8,
688                valid: true,
689            }),
690            slot1: Some(TestSlot {
691                generation: 9,
692                valid: false,
693            }),
694        });
695
696        assert_eq!(diagnostic.authoritative_generation, Some(8));
697        assert!(diagnostic.slot0.valid);
698        assert_eq!(diagnostic.slot1.generation, Some(9));
699        assert!(!diagnostic.slot1.valid);
700    }
701
702    #[test]
703    fn uninitialized_distinguishes_empty_from_corrupt() {
704        let mut store = DualCommitStore::default();
705        assert!(store.is_uninitialized());
706
707        store.write_corrupt_inactive_slot(0, payload(1));
708
709        assert!(!store.is_uninitialized());
710    }
711
712    #[test]
713    fn commit_after_corrupt_slot_advances_from_prior_valid_generation() {
714        let mut store = DualCommitStore::default();
715        store.commit_payload(payload(1)).expect("first commit");
716        store.write_corrupt_inactive_slot(1, payload(2));
717        store.commit_payload(payload(3)).expect("third commit");
718
719        let authoritative = store.authoritative().expect("authoritative");
720
721        assert_eq!(authoritative.generation, 1);
722        assert_eq!(authoritative.payload, payload(3));
723    }
724}