1use std::collections::BTreeMap;
19use std::fmt;
20
21use reverie_syscalls::Sysno;
22use serde::Deserialize;
23use serde::Serialize;
24
25use super::InstructionPointer;
26use super::SchedEvent;
27use super::SigWrapper;
28use crate::pid::DetTid;
29use crate::time::LogicalTime;
30
31#[derive(
35 Clone,
36 Copy,
37 Debug,
38 Eq,
39 PartialEq,
40 Ord,
41 PartialOrd,
42 Serialize,
43 Deserialize
44)]
45#[serde(deny_unknown_fields)]
46pub struct Owner {
47 pub tid: DetTid,
49 pub thread_generation: u64,
51 pub birth_tid: DetTid,
53 pub birth_generation: u64,
55 pub address_space_creator: DetTid,
57 pub address_space_generation: u64,
59}
60
61#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
62pub enum Phase {
63 Prehook,
64 Posthook,
65 TimerMaximum,
67}
68
69#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
72pub enum Cause {
73 SoftwareCheckin,
74 PmuTimer,
75 NoRcbTimer,
76 ExplicitSchedYield,
77}
78
79#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
83#[serde(deny_unknown_fields)]
84pub enum Entry {
85 Syscall {
86 syscall: Sysno,
87 occurrence: u64,
88 },
89 Rdtsc,
90 Cpuid,
91 PmuTimer,
92 NoRcbTimer,
93 ThreadStart,
94 Exec,
95 SignalEntry {
96 signal: SigWrapper,
97 occurrence: u64,
98 announced: bool,
99 },
100 Signal {
101 signal: SigWrapper,
102 occurrence: u64,
103 announced: bool,
104 delivery: u64,
105 },
106}
107
108#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
109#[serde(deny_unknown_fields)]
110pub struct Identity {
111 pub owner: Owner,
112 pub cause: Cause,
113 pub phase: Phase,
114 pub entry: Entry,
115 pub committed_rcbs: u64,
117 pub checkin: u64,
119 pub slice: u64,
122}
123
124#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
125#[serde(deny_unknown_fields)]
126pub struct Boundary {
127 pub identity: Identity,
128 pub count: u32,
130 pub start_rip: Option<InstructionPointer>,
131 pub end_rip: Option<InstructionPointer>,
132 pub end_time: Option<LogicalTime>,
133}
134
135#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
138pub enum Record {
139 Event(SchedEvent),
140 TimesliceEnd(Boundary),
141}
142
143impl Record {
144 pub fn check_observation(&self, index: usize, observed: &Record) -> Result<(), Error> {
147 let mut comparable = observed.clone();
148 match (self, &mut comparable) {
149 (Record::Event(e), Record::Event(o)) => {
150 if e.end_time.is_none() {
151 o.end_time = None;
152 }
153 if e.start_rip.is_none() {
154 o.start_rip = None;
155 }
156 if e.end_rip.is_none() {
157 o.end_rip = None;
158 }
159 }
160 (Record::TimesliceEnd(e), Record::TimesliceEnd(o)) => {
161 if e.end_time.is_none() {
162 o.end_time = None;
163 }
164 if e.start_rip.is_none() {
165 o.start_rip = None;
166 }
167 if e.end_rip.is_none() {
168 o.end_rip = None;
169 }
170 }
171 _ => {}
172 }
173 if self != &comparable {
174 return Err(Error::Mismatch {
175 index,
176 expected: Box::new(self.clone()),
177 observed: Box::new(observed.clone()),
178 });
179 }
180 Ok(())
181 }
182}
183
184#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
188#[serde(try_from = "Wire", into = "Wire")]
189pub struct Schedule {
190 records: Vec<Record>,
191}
192
193#[derive(Clone, Debug, Serialize, Deserialize)]
194#[serde(deny_unknown_fields)]
195struct Wire {
196 protocol: String,
197 version: u32,
198 record_count: usize,
199 boundary_count: usize,
200 records: Vec<Record>,
201}
202
203const PROTOCOL: &str = "hermit-slice-boundaries";
204const VERSION: u32 = 1;
205
206#[derive(Clone, Debug, Eq, PartialEq)]
209pub enum Error {
210 UnsupportedProtocol {
211 protocol: String,
212 version: u32,
213 },
214 RecordCount {
215 declared: usize,
216 actual: usize,
217 },
218 BoundaryCount {
219 declared: usize,
220 actual: usize,
221 },
222 InvalidBoundary {
223 index: usize,
224 reason: &'static str,
225 },
226 InvalidEvent {
227 index: usize,
228 reason: &'static str,
229 },
230 Mismatch {
231 index: usize,
232 expected: Box<Record>,
233 observed: Box<Record>,
234 },
235 Exhausted {
236 index: usize,
237 observed: Box<Record>,
238 },
239 Incomplete {
240 index: usize,
241 remaining: usize,
242 },
243 UnsupportedEdit {
244 operation: &'static str,
245 },
246}
247
248impl fmt::Display for Error {
249 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
250 write!(f, "slice-boundary protocol refusal: {self:?}")
251 }
252}
253
254impl std::error::Error for Error {}
255
256impl TryFrom<Wire> for Schedule {
257 type Error = Error;
258
259 fn try_from(wire: Wire) -> Result<Self, Error> {
260 if wire.protocol != PROTOCOL || wire.version != VERSION {
261 return Err(Error::UnsupportedProtocol {
262 protocol: wire.protocol,
263 version: wire.version,
264 });
265 }
266 if wire.record_count != wire.records.len() {
267 return Err(Error::RecordCount {
268 declared: wire.record_count,
269 actual: wire.records.len(),
270 });
271 }
272 let actual = boundary_count(&wire.records);
273 if wire.boundary_count != actual {
274 return Err(Error::BoundaryCount {
275 declared: wire.boundary_count,
276 actual,
277 });
278 }
279 Self::new(wire.records)
280 }
281}
282
283impl From<Schedule> for Wire {
284 fn from(schedule: Schedule) -> Self {
285 Self {
286 protocol: PROTOCOL.into(),
287 version: VERSION,
288 record_count: schedule.records.len(),
289 boundary_count: boundary_count(&schedule.records),
290 records: schedule.records,
291 }
292 }
293}
294
295fn boundary_count(records: &[Record]) -> usize {
296 records
297 .iter()
298 .filter(|record| matches!(record, Record::TimesliceEnd(_)))
299 .count()
300}
301
302impl Boundary {
303 pub fn validate_shape(&self, index: usize) -> Result<(), Error> {
306 let boundary = self;
307 let id = self.identity;
308 let invalid = |reason| Error::InvalidBoundary { index, reason };
309 if boundary.count != 1 {
310 return Err(invalid("boundary count must be one"));
311 }
312 if id.owner.tid.as_raw() <= 0
313 || id.owner.birth_tid.as_raw() <= 0
314 || id.owner.address_space_creator.as_raw() <= 0
315 || id.checkin == 0
316 || id.slice == 0
317 {
318 return Err(invalid("nonpositive owner or zero occurrence"));
319 }
320 match id.entry {
321 Entry::Syscall { occurrence: 0, .. }
322 | Entry::Signal { delivery: 0, .. }
323 | Entry::Signal { occurrence: 0, .. }
324 | Entry::SignalEntry { occurrence: 0, .. } => {
325 return Err(invalid("zero syscall or signal occurrence"));
326 }
327 Entry::Signal { signal, .. } | Entry::SignalEntry { signal, .. }
328 if !(1..=64).contains(&signal.raw()) =>
329 {
330 return Err(invalid("invalid logical signal"));
331 }
332 _ => {}
333 }
334 if (matches!(id.entry, Entry::SignalEntry { .. }) && id.phase != Phase::Prehook)
335 || (matches!(id.entry, Entry::Signal { .. }) && id.phase != Phase::Posthook)
336 {
337 return Err(invalid("signal entry/delivery phase mismatch"));
338 }
339 if id.phase == Phase::TimerMaximum && id.entry != Entry::NoRcbTimer {
340 return Err(invalid(
341 "maximum-body phase requires the actual no-RCB timer",
342 ));
343 }
344 let cause_matches = match id.cause {
345 Cause::PmuTimer => id.entry == Entry::PmuTimer,
346 Cause::NoRcbTimer => id.entry == Entry::NoRcbTimer,
347 Cause::ExplicitSchedYield => matches!(
348 id.entry,
349 Entry::Syscall {
350 syscall: Sysno::sched_yield,
351 ..
352 }
353 ),
354 Cause::SoftwareCheckin => !matches!(id.entry, Entry::PmuTimer | Entry::NoRcbTimer),
355 };
356 if !cause_matches {
357 return Err(invalid("cause disagrees with actual semantic entry"));
358 }
359 Ok(())
360 }
361}
362
363impl Schedule {
364 pub fn new(records: Vec<Record>) -> Result<Self, Error> {
365 let mut previous: BTreeMap<Owner, Identity> = BTreeMap::new();
366 let mut latest_owner: BTreeMap<DetTid, Owner> = BTreeMap::new();
367 let mut latest_birth: BTreeMap<(DetTid, u64), Owner> = BTreeMap::new();
368 for (index, record) in records.iter().enumerate() {
369 if let Record::Event(event) = record {
370 if event.dettid.as_raw() <= 0 || event.count == 0 {
371 return Err(Error::InvalidEvent {
372 index,
373 reason: "event owner and count must be positive",
374 });
375 }
376 continue;
377 }
378 let Record::TimesliceEnd(boundary) = record else {
379 unreachable!();
380 };
381 let id = boundary.identity;
382 let invalid = |reason| Error::InvalidBoundary { index, reason };
383 boundary.validate_shape(index)?;
384 if let Some(last) = latest_owner.get(&id.owner.tid)
385 && (id.owner.thread_generation < last.thread_generation
386 || (id.owner.thread_generation == last.thread_generation
387 && (id.owner.birth_tid != last.birth_tid
388 || id.owner.birth_generation != last.birth_generation
389 || id.owner.address_space_generation < last.address_space_generation
390 || (id.owner.address_space_generation
391 == last.address_space_generation
392 && id.owner.address_space_creator != last.address_space_creator))))
393 {
394 return Err(invalid("owner generation moved backwards"));
395 }
396 let birth = (id.owner.birth_tid, id.owner.birth_generation);
397 if let Some(last) = latest_birth.get(&birth)
398 && (id.owner.address_space_generation < last.address_space_generation
399 || (id.owner.address_space_generation == last.address_space_generation
400 && id.owner != *last))
401 {
402 return Err(invalid(
403 "logical survivor moved without a new exec incarnation",
404 ));
405 }
406 if let Some(last) = previous.get(&id.owner) {
407 if last.slice.checked_add(1) != Some(id.slice) {
408 return Err(invalid("missing, duplicated or reordered slice occurrence"));
409 }
410 if id.checkin < last.checkin || id.committed_rcbs < last.committed_rcbs {
411 return Err(invalid("check-in or guest RCB count moved backwards"));
412 }
413 if id.checkin == last.checkin
414 && (id.entry != last.entry
415 || id.phase != last.phase
416 || id.committed_rcbs != last.committed_rcbs)
417 {
418 return Err(invalid("one semantic check-in has conflicting identities"));
419 }
420 } else if id.slice != 1 {
421 return Err(invalid("first completed slice must be one"));
422 }
423 previous.insert(id.owner, id);
424 latest_owner.insert(id.owner.tid, id.owner);
425 latest_birth.insert(birth, id.owner);
426 }
427 Ok(Self { records })
428 }
429
430 pub fn records(&self) -> &[Record] {
431 &self.records
432 }
433
434 pub fn strip_times(&mut self) {
437 for record in &mut self.records {
438 match record {
439 Record::Event(event) => event.end_time = None,
440 Record::TimesliceEnd(boundary) => boundary.end_time = None,
441 }
442 }
443 }
444
445 pub fn into_legacy_events(self) -> Result<Vec<SchedEvent>, Error> {
449 Err(Error::UnsupportedEdit {
450 operation: "discard protocol into legacy event vector",
451 })
452 }
453
454 pub fn edit(&mut self, operation: Edit) -> Result<(), Error> {
455 Err(Error::UnsupportedEdit {
456 operation: operation.name(),
457 })
458 }
459
460 pub fn cursor(&self) -> Cursor<'_> {
461 Cursor {
462 schedule: self,
463 next: 0,
464 }
465 }
466}
467
468#[derive(Clone, Copy, Debug)]
469pub enum Edit {
470 Split,
471 Search,
472 Minimize,
473}
474
475impl Edit {
476 fn name(self) -> &'static str {
477 match self {
478 Self::Split => "split boundary schedule",
479 Self::Search => "search boundary schedule",
480 Self::Minimize => "minimize boundary schedule",
481 }
482 }
483}
484
485pub struct Cursor<'a> {
489 schedule: &'a Schedule,
490 next: usize,
491}
492
493impl Cursor<'_> {
494 pub fn stored_index(&self) -> usize {
495 self.next
496 }
497
498 pub fn expected(&self) -> Option<&Record> {
499 self.schedule.records.get(self.next)
500 }
501
502 pub fn observe(&mut self, observed: &Record) -> Result<(), Error> {
503 let Some(expected) = self.expected() else {
504 return Err(Error::Exhausted {
505 index: self.next,
506 observed: Box::new(observed.clone()),
507 });
508 };
509 expected.check_observation(self.next, observed)?;
510 self.next += 1;
511 Ok(())
512 }
513
514 pub fn finish(self) -> Result<(), Error> {
515 let remaining = self.schedule.records.len() - self.next;
516 if remaining == 0 {
517 Ok(())
518 } else {
519 Err(Error::Incomplete {
520 index: self.next,
521 remaining,
522 })
523 }
524 }
525}
526
527#[cfg(test)]
528mod tests {
529 use super::*;
530 use crate::schedule::Op;
531
532 #[test]
533 fn birth_identity_is_required_and_cannot_move_without_exec() {
534 let first = boundary();
535 let bytes = serde_json::to_value(&first).unwrap();
536 for missing in ["birth_tid", "birth_generation"] {
537 let mut old = bytes.clone();
538 old["identity"]["owner"]
539 .as_object_mut()
540 .unwrap()
541 .remove(missing);
542 assert!(serde_json::from_value::<Boundary>(old).is_err());
543 }
544 let mut changed = first.clone();
545 changed.identity.owner.tid = DetTid::from_raw(4);
546 assert!(
547 Schedule::new(vec![
548 Record::TimesliceEnd(first.clone()),
549 Record::TimesliceEnd(changed.clone())
550 ])
551 .is_err()
552 );
553 changed.identity.owner.address_space_creator = DetTid::from_raw(4);
554 changed.identity.owner.address_space_generation += 1;
555 assert!(
556 Schedule::new(vec![
557 Record::TimesliceEnd(first.clone()),
558 Record::TimesliceEnd(changed.clone())
559 ])
560 .is_ok()
561 );
562 let mut retired = first.clone();
563 retired.identity.slice += 1;
564 assert!(
565 Schedule::new(vec![
566 Record::TimesliceEnd(first),
567 Record::TimesliceEnd(changed),
568 Record::TimesliceEnd(retired)
569 ])
570 .is_err()
571 );
572 }
573
574 fn boundary() -> Boundary {
575 Boundary {
576 identity: Identity {
577 owner: Owner {
578 tid: DetTid::from_raw(3),
579 thread_generation: 0,
580 birth_tid: DetTid::from_raw(3),
581 birth_generation: 0,
582 address_space_creator: DetTid::from_raw(3),
583 address_space_generation: 0,
584 },
585 cause: Cause::SoftwareCheckin,
586 phase: Phase::Posthook,
587 entry: Entry::Syscall {
588 syscall: Sysno::write,
589 occurrence: 7,
590 },
591 committed_rcbs: 83,
592 checkin: 10,
593 slice: 1,
594 },
595 count: 1,
596 start_rip: InstructionPointer::new(0x1000),
597 end_rip: InstructionPointer::new(0x1002),
598 end_time: Some(LogicalTime::from_rcbs(83)),
599 }
600 }
601
602 fn one() -> Schedule {
603 Schedule::new(vec![Record::TimesliceEnd(boundary())]).unwrap()
604 }
605
606 #[test]
607 fn envelope_round_trip_preserves_every_field() {
608 let schedule = one();
609 let encoded = serde_json::to_vec(&schedule).unwrap();
610 assert_eq!(
611 serde_json::from_slice::<Schedule>(&encoded).unwrap(),
612 schedule
613 );
614 assert_eq!(
615 serde_json::to_vec(&serde_json::from_slice::<Schedule>(&encoded).unwrap()).unwrap(),
616 encoded
617 );
618 }
619
620 #[test]
621 fn empty_schedule_still_requires_supported_protocol() {
622 let original = Schedule::new(vec![]).unwrap();
623 let value = serde_json::to_value(&original).unwrap();
624 assert_eq!(value["protocol"], PROTOCOL);
625 assert_eq!(value["version"], VERSION);
626 for key in ["protocol", "version", "record_count", "boundary_count"] {
627 let mut bad = value.clone();
628 bad.as_object_mut().unwrap().remove(key);
629 assert!(
630 serde_json::from_value::<Schedule>(bad).is_err(),
631 "missing {key}"
632 );
633 }
634 for bad_version in [0, 2, u32::MAX] {
635 let mut bad = value.clone();
636 bad["version"] = bad_version.into();
637 assert!(serde_json::from_value::<Schedule>(bad).is_err());
638 }
639 let mut bad = value;
640 bad["protocol"] = "unknown".into();
641 assert!(serde_json::from_value::<Schedule>(bad).is_err());
642 }
643
644 #[test]
645 fn deleting_sole_boundary_cannot_select_legacy_replay() {
646 let mut bad = serde_json::to_value(one()).unwrap();
647 bad["records"] = serde_json::json!([]);
648 assert!(serde_json::from_value::<Schedule>(bad.clone()).is_err());
649 bad["record_count"] = 0.into();
650 assert!(serde_json::from_value::<Schedule>(bad.clone()).is_err());
651 bad["boundary_count"] = 0.into();
652 let empty: Schedule = serde_json::from_value(bad).unwrap();
653 assert!(matches!(
654 empty.clone().into_legacy_events(),
655 Err(Error::UnsupportedEdit { .. })
656 ));
657 assert!(matches!(
658 empty.cursor().observe(&Record::TimesliceEnd(boundary())),
659 Err(Error::Exhausted { index: 0, .. })
660 ));
661 }
662
663 #[test]
664 fn malformed_fields_are_not_ignored() {
665 let value = serde_json::to_value(one()).unwrap();
666 for pointer in [
667 "",
668 "/records/0/TimesliceEnd",
669 "/records/0/TimesliceEnd/identity",
670 "/records/0/TimesliceEnd/identity/owner",
671 ] {
672 let mut bad = value.clone();
673 bad.pointer_mut(pointer)
674 .unwrap()
675 .as_object_mut()
676 .unwrap()
677 .insert("future_field".into(), true.into());
678 assert!(
679 serde_json::from_value::<Schedule>(bad).is_err(),
680 "unknown field at {pointer}"
681 );
682 }
683 let mut bad = value.clone();
684 bad["records"][0]["TimesliceEnd"]["identity"]["cause"] = "Unknown".into();
685 assert!(serde_json::from_value::<Schedule>(bad).is_err());
686 let encoded = serde_json::to_vec(&one()).unwrap();
687 assert!(serde_json::from_slice::<Schedule>(&encoded[..encoded.len() - 1]).is_err());
688 }
689
690 #[test]
691 fn counts_and_cause_are_validated_on_deserialization() {
692 for count in [0, 2, u32::MAX] {
693 let mut bad = serde_json::to_value(one()).unwrap();
694 bad["records"][0]["TimesliceEnd"]["count"] = count.into();
695 assert!(serde_json::from_value::<Schedule>(bad).is_err());
696 }
697 for cause in [
698 Cause::PmuTimer,
699 Cause::NoRcbTimer,
700 Cause::ExplicitSchedYield,
701 ] {
702 let mut b = boundary();
703 b.identity.cause = cause;
704 assert!(Schedule::new(vec![Record::TimesliceEnd(b)]).is_err());
705 }
706 for (cause, entry) in [
707 (Cause::PmuTimer, Entry::PmuTimer),
708 (Cause::NoRcbTimer, Entry::NoRcbTimer),
709 (
710 Cause::ExplicitSchedYield,
711 Entry::Syscall {
712 syscall: Sysno::sched_yield,
713 occurrence: 1,
714 },
715 ),
716 ] {
717 let mut b = boundary();
718 b.identity.cause = cause;
719 b.identity.entry = entry;
720 assert!(Schedule::new(vec![Record::TimesliceEnd(b)]).is_ok());
721 }
722 }
723
724 #[test]
725 fn every_mandatory_identity_field_is_exact_before_cursor_advance() {
726 let original = boundary();
727 let schedule = one();
728 let mut mutations = vec![];
729 let mut b = original.clone();
730 b.identity.owner.tid = DetTid::from_raw(4);
731 mutations.push(b);
732 let mut b = original.clone();
733 b.identity.owner.thread_generation += 1;
734 mutations.push(b);
735 let mut b = original.clone();
736 b.identity.owner.address_space_generation += 1;
737 mutations.push(b);
738 let mut b = original.clone();
739 b.identity.cause = Cause::ExplicitSchedYield;
740 mutations.push(b);
741 let mut b = original.clone();
742 b.identity.phase = Phase::Prehook;
743 mutations.push(b);
744 let mut b = original.clone();
745 b.identity.committed_rcbs += 1;
746 mutations.push(b);
747 let mut b = original.clone();
748 b.identity.checkin += 1;
749 mutations.push(b);
750 let mut b = original.clone();
751 b.identity.slice += 1;
752 mutations.push(b);
753 let mut b = original.clone();
754 b.identity.entry = Entry::Syscall {
755 syscall: Sysno::read,
756 occurrence: 7,
757 };
758 mutations.push(b);
759 let mut b = original.clone();
760 b.identity.entry = Entry::Syscall {
761 syscall: Sysno::write,
762 occurrence: 8,
763 };
764 mutations.push(b);
765 let mut b = original.clone();
766 b.count = 2;
767 mutations.push(b);
768 let mut b = original.clone();
769 b.end_rip = InstructionPointer::new(0x2000);
770 mutations.push(b);
771 let mut b = original.clone();
772 b.start_rip = None;
773 mutations.push(b);
774 let mut b = original.clone();
775 b.end_time = None;
776 mutations.push(b);
777 let mut cursor = schedule.cursor();
778 for mutation in mutations {
779 assert!(matches!(
780 cursor.observe(&Record::TimesliceEnd(mutation)),
781 Err(Error::Mismatch { index: 0, .. })
782 ));
783 assert_eq!(cursor.stored_index(), 0);
784 }
785 cursor.observe(&Record::TimesliceEnd(original)).unwrap();
786 cursor.finish().unwrap();
787 }
788
789 #[test]
790 fn strip_times_retains_payload_and_rip_evidence() {
791 let mut schedule = one();
792 let before = schedule.clone();
793 schedule.strip_times();
794 let mut expected = boundary();
795 expected.end_time = None;
796 assert_eq!(schedule.records(), &[Record::TimesliceEnd(expected)]);
797 let mut cursor = schedule.cursor();
798 cursor.observe(&before.records()[0]).unwrap();
799 cursor.finish().unwrap();
800 let mut wrong_rcb = boundary();
801 wrong_rcb.identity.committed_rcbs += 1;
802 assert!(
803 schedule
804 .cursor()
805 .observe(&Record::TimesliceEnd(wrong_rcb))
806 .is_err()
807 );
808 }
809
810 #[test]
811 fn adjacent_zero_rcb_boundaries_are_distinct_and_cannot_be_duplicated() {
812 let first = boundary();
813 let mut second = first.clone();
814 second.identity.slice = 2;
815 let schedule = Schedule::new(vec![
816 Record::TimesliceEnd(first.clone()),
817 Record::TimesliceEnd(second.clone()),
818 ])
819 .unwrap();
820 let mut cursor = schedule.cursor();
821 cursor
822 .observe(&Record::TimesliceEnd(first.clone()))
823 .unwrap();
824 assert!(
825 cursor
826 .observe(&Record::TimesliceEnd(first.clone()))
827 .is_err()
828 );
829 assert_eq!(cursor.stored_index(), 1);
830 cursor.observe(&Record::TimesliceEnd(second)).unwrap();
831 cursor.finish().unwrap();
832 assert!(
833 Schedule::new(vec![
834 Record::TimesliceEnd(first.clone()),
835 Record::TimesliceEnd(first)
836 ])
837 .is_err()
838 );
839 }
840
841 #[test]
842 fn missing_and_reordered_slices_are_refused_even_with_equal_rcbs() {
843 let first = boundary();
844 for slice in [0, 1, 3, u64::MAX] {
845 let mut second = first.clone();
846 second.identity.slice = slice;
847 assert!(
848 Schedule::new(vec![
849 Record::TimesliceEnd(first.clone()),
850 Record::TimesliceEnd(second)
851 ])
852 .is_err()
853 );
854 }
855 }
856
857 #[test]
858 fn repeated_site_requires_actual_checkin_and_syscall_occurrence() {
859 let first = boundary();
860 let mut second = first.clone();
861 second.identity.slice += 1;
862 second.identity.checkin += 1;
863 second.identity.entry = Entry::Syscall {
864 syscall: Sysno::write,
865 occurrence: 8,
866 };
867 let mut schedule = Schedule::new(vec![
868 Record::TimesliceEnd(first.clone()),
869 Record::TimesliceEnd(second.clone()),
870 ])
871 .unwrap();
872 schedule.strip_times();
873 let mut cursor = schedule.cursor();
874 cursor
875 .observe(&Record::TimesliceEnd(first.clone()))
876 .unwrap();
877 let mut stale = first;
878 stale.identity.slice = 2;
879 assert!(cursor.observe(&Record::TimesliceEnd(stale)).is_err());
880 assert_eq!(cursor.stored_index(), 1);
881 cursor.observe(&Record::TimesliceEnd(second)).unwrap();
882 }
883
884 #[test]
885 fn exec_generation_does_not_accept_an_old_boundary() {
886 let old = boundary();
887 let mut new = old.clone();
888 new.identity.owner.address_space_generation += 1;
889 new.identity.committed_rcbs = 0;
890 new.identity.checkin = 1;
891 let schedule = Schedule::new(vec![
892 Record::TimesliceEnd(old.clone()),
893 Record::TimesliceEnd(new.clone()),
894 ])
895 .unwrap();
896 let mut cursor = schedule.cursor();
897 cursor.observe(&Record::TimesliceEnd(old.clone())).unwrap();
898 assert!(cursor.observe(&Record::TimesliceEnd(old)).is_err());
899 cursor.observe(&Record::TimesliceEnd(new)).unwrap();
900 cursor.finish().unwrap();
901 }
902
903 #[test]
904 fn serialized_stream_cannot_return_to_an_old_generation() {
905 let old = boundary();
906 let mut new = old.clone();
907 new.identity.owner.address_space_generation += 1;
908 new.identity.committed_rcbs = 0;
909 new.identity.checkin = 1;
910 let mut stale = old.clone();
911 stale.identity.slice = 2;
912 assert!(
913 Schedule::new(vec![
914 Record::TimesliceEnd(old),
915 Record::TimesliceEnd(new),
916 Record::TimesliceEnd(stale),
917 ])
918 .is_err()
919 );
920 }
921
922 #[test]
923 fn recorded_cause_is_not_inferred_from_rip_or_rcb() {
924 let mut timer = boundary();
925 timer.identity.cause = Cause::PmuTimer;
926 timer.identity.entry = Entry::PmuTimer;
927 let schedule = Schedule::new(vec![Record::TimesliceEnd(timer)]).unwrap();
928 assert!(
929 schedule
930 .cursor()
931 .observe(&Record::TimesliceEnd(boundary()))
932 .is_err()
933 );
934 }
935
936 #[test]
937 fn missing_extra_and_skipped_records_cannot_report_completion() {
938 assert!(matches!(
939 one().cursor().finish(),
940 Err(Error::Incomplete {
941 index: 0,
942 remaining: 1
943 })
944 ));
945 let schedule = one();
946 let mut cursor = schedule.cursor();
947 cursor.observe(&Record::TimesliceEnd(boundary())).unwrap();
948 assert!(matches!(
949 cursor.observe(&Record::TimesliceEnd(boundary())),
950 Err(Error::Exhausted { index: 1, .. })
951 ));
952 assert_eq!(cursor.stored_index(), 1);
953 let event = Record::Event(SchedEvent::branches(DetTid::from_raw(3), 83));
954 let schedule = Schedule::new(vec![
955 event.clone(),
956 Record::TimesliceEnd(boundary()),
957 event.clone(),
958 ])
959 .unwrap();
960 let mut cursor = schedule.cursor();
961 cursor.observe(&event).unwrap();
962 assert!(cursor.observe(&event).is_err());
963 assert_eq!(cursor.stored_index(), 1);
964 assert!(cursor.finish().is_err());
965 }
966
967 #[test]
968 fn stored_indices_include_boundaries() {
969 let event = Record::Event(SchedEvent::branches(DetTid::from_raw(3), 83));
970 let schedule = Schedule::new(vec![
971 event.clone(),
972 Record::TimesliceEnd(boundary()),
973 event.clone(),
974 ])
975 .unwrap();
976 let mut cursor = schedule.cursor();
977 for (index, record) in schedule.records().iter().enumerate() {
978 assert_eq!(cursor.stored_index(), index);
979 assert_eq!(cursor.expected(), Some(record));
980 cursor.observe(record).unwrap();
981 }
982 assert_eq!(cursor.stored_index(), 3);
983 cursor.finish().unwrap();
984 }
985
986 #[test]
987 fn unsupported_edits_fail_before_output_changes() {
988 for mut schedule in [one(), Schedule::new(vec![]).unwrap()] {
989 let before = serde_json::to_vec(&schedule).unwrap();
990 for edit in [Edit::Split, Edit::Search, Edit::Minimize] {
991 assert!(matches!(
992 schedule.edit(edit),
993 Err(Error::UnsupportedEdit { .. })
994 ));
995 assert_eq!(serde_json::to_vec(&schedule).unwrap(), before);
996 }
997 assert!(schedule.into_legacy_events().is_err());
998 }
999 }
1000
1001 #[test]
1002 fn legacy_operation_tags_are_unchanged() {
1003 for (op, json, tag) in [
1004 (Op::Branch, "\"Branch\"", 0),
1005 (Op::Rdtsc, "\"Rdtsc\"", 1),
1006 (Op::Cpuid, "\"Cpuid\"", 2),
1007 (Op::OtherInstructions, "\"OtherInstructions\"", 4),
1008 ] {
1009 assert_eq!(serde_json::to_string(&op).unwrap(), json);
1010 assert_eq!(
1011 bincode::serde::encode_to_vec(op, bincode::config::standard()).unwrap(),
1012 vec![tag]
1013 );
1014 }
1015 }
1016}