1use crate::access_policy::{AccessClass, AccessPolicy, Denial, ResourceId, WritePrecondition};
13use crate::audit_ring::{AuditRecord, AuditRing, Decision, OpStatus};
14use crate::hardware_backend::{BackendError, Clock, Timer};
15use crate::protocol_resource_adapter::ResourceBackend;
16use std::collections::BTreeMap;
17
18#[derive(Clone, Copy, Debug)]
20pub struct ExecLimits {
21 pub max_snapshot_items: usize,
23 pub max_sequence_items: usize,
25 pub max_delay_ns: i64,
27 pub max_sequence_duration_ns: i64,
29}
30
31impl Default for ExecLimits {
32 fn default() -> Self {
33 Self {
34 max_snapshot_items: 64,
35 max_sequence_items: 64,
36 max_delay_ns: 5_000_000_000, max_sequence_duration_ns: 1_000_000_000, }
39 }
40}
41
42#[derive(Clone, Copy, Debug, PartialEq, Eq)]
44pub struct SnapshotItem {
45 pub resource: ResourceId,
47 pub offset: u64,
49}
50
51#[derive(Clone, Copy, Debug, PartialEq, Eq)]
53pub struct ReadOutcome {
54 pub value: u32,
56 pub audit_seq: u64,
58 pub timestamp_ns: i64,
60}
61
62#[derive(Clone, Copy, Debug, PartialEq, Eq)]
64pub enum ReadError {
65 Denied {
67 denial: Denial,
69 audit_seq: u64,
71 },
72 Backend {
74 error: BackendError,
76 audit_seq: u64,
78 },
79}
80
81#[derive(Clone, Copy, Debug, PartialEq, Eq)]
83pub struct WriteOutcome {
84 pub readback_value: u32,
86 pub audit_seq: u64,
88 pub timestamp_ns: i64,
90}
91
92#[derive(Clone, Copy, Debug, PartialEq, Eq)]
94pub enum WriteError {
95 Denied {
97 denial: Denial,
99 audit_seq: u64,
101 },
102 Backend {
104 error: BackendError,
106 audit_seq: u64,
108 },
109}
110
111#[derive(Clone, Copy, Debug, PartialEq, Eq)]
113pub struct PollOutcome {
114 pub value: u32,
116 pub audit_seq: u64,
118 pub timestamp_ns: i64,
120}
121
122#[derive(Clone, Copy, Debug, PartialEq, Eq)]
124pub enum PollError {
125 Denied {
127 denial: Denial,
129 audit_seq: u64,
131 },
132 Backend {
134 error: BackendError,
136 audit_seq: u64,
138 },
139}
140
141#[derive(Clone, Copy, Debug, PartialEq, Eq)]
143pub struct GpioReadOutcome {
144 pub value: bool,
145 pub audit_seq: u64,
146 pub timestamp_ns: i64,
147}
148
149#[derive(Clone, Copy, Debug, PartialEq, Eq)]
151pub enum GpioReadError {
152 Denied { denial: Denial, audit_seq: u64 },
153 Backend { error: BackendError, audit_seq: u64 },
154}
155
156#[derive(Clone, Copy, Debug, PartialEq, Eq)]
158pub struct GpioWriteOutcome {
159 pub audit_seq: u64,
160 pub timestamp_ns: i64,
161}
162
163#[derive(Clone, Copy, Debug, PartialEq, Eq)]
165pub enum GpioWriteError {
166 Denied { denial: Denial, audit_seq: u64 },
167 Backend { error: BackendError, audit_seq: u64 },
168}
169
170#[derive(Clone, Debug, PartialEq, Eq)]
172pub struct I2cTransferOutcome {
173 pub read_data: Vec<u8>,
174 pub audit_seq: u64,
175 pub timestamp_ns: i64,
176}
177
178#[derive(Clone, Copy, Debug, PartialEq, Eq)]
180pub enum I2cTransferError {
181 Denied { denial: Denial, audit_seq: u64 },
182 Backend { error: BackendError, audit_seq: u64 },
183}
184
185#[derive(Clone, Debug, PartialEq, Eq)]
187pub struct SpiTransmitOutcome {
188 pub rx_data: Vec<u8>,
189 pub audit_seq: u64,
190 pub timestamp_ns: i64,
191}
192
193#[derive(Clone, Copy, Debug, PartialEq, Eq)]
195pub enum SpiTransmitError {
196 Denied { denial: Denial, audit_seq: u64 },
197 Backend { error: BackendError, audit_seq: u64 },
198}
199
200#[derive(Clone, Debug, PartialEq, Eq)]
202pub struct ClockEnableOutcome {
203 pub audit_seq: u64,
204 pub timestamp_ns: i64,
205}
206
207#[derive(Clone, Debug, PartialEq, Eq)]
209pub struct ClockDisableOutcome {
210 pub audit_seq: u64,
211 pub timestamp_ns: i64,
212}
213
214#[derive(Clone, Debug, PartialEq, Eq)]
216pub struct ClockIsEnabledOutcome {
217 pub enabled: bool,
218 pub audit_seq: u64,
219 pub timestamp_ns: i64,
220}
221
222#[derive(Clone, Debug, PartialEq, Eq)]
224pub struct ClockSetRateOutcome {
225 pub audit_seq: u64,
226 pub timestamp_ns: i64,
227}
228
229#[derive(Clone, Debug, PartialEq, Eq)]
231pub struct ClockQueryRateOutcome {
232 pub hz_out: u64,
233 pub audit_seq: u64,
234 pub timestamp_ns: i64,
235}
236
237#[derive(Clone, Debug, PartialEq, Eq)]
239pub struct ClockGetRateOutcome {
240 pub hz: u64,
241 pub audit_seq: u64,
242 pub timestamp_ns: i64,
243}
244
245#[derive(Clone, Copy, Debug, PartialEq, Eq)]
247pub enum ClockOpError {
248 Denied { denial: Denial, audit_seq: u64 },
249 Backend { error: BackendError, audit_seq: u64 },
250}
251
252#[derive(Clone, Debug, PartialEq, Eq)]
254pub struct ResetAssertOutcome {
255 pub audit_seq: u64,
256 pub timestamp_ns: i64,
257}
258
259#[derive(Clone, Debug, PartialEq, Eq)]
261pub struct ResetDeassertOutcome {
262 pub audit_seq: u64,
263 pub timestamp_ns: i64,
264}
265
266#[derive(Clone, Debug, PartialEq, Eq)]
268pub struct ResetToggleOutcome {
269 pub audit_seq: u64,
270 pub timestamp_ns: i64,
271}
272
273#[derive(Clone, Debug, PartialEq, Eq)]
275pub struct ResetStatusOutcome {
276 pub asserted: bool,
277 pub audit_seq: u64,
278 pub timestamp_ns: i64,
279}
280
281#[derive(Clone, Copy, Debug, PartialEq, Eq)]
283pub enum ResetOpError {
284 Denied { denial: Denial, audit_seq: u64 },
285 Backend { error: BackendError, audit_seq: u64 },
286}
287
288#[derive(Clone, Debug, PartialEq, Eq)]
290pub struct SerialReadOutcome {
291 pub data: Vec<u8>,
292 pub audit_seq: u64,
293 pub timestamp_ns: i64,
294}
295
296#[derive(Clone, Debug, PartialEq, Eq)]
298pub struct SerialWriteOutcome {
299 pub audit_seq: u64,
300 pub timestamp_ns: i64,
301}
302
303#[derive(Clone, Copy, Debug, PartialEq, Eq)]
305pub enum SerialOpError {
306 Denied { denial: Denial, audit_seq: u64 },
307 Backend { error: BackendError, audit_seq: u64 },
308}
309
310#[derive(Clone, Debug, PartialEq, Eq)]
312pub enum SequenceItem {
313 Read32 {
314 resource: ResourceId,
315 offset: u64,
316 },
317 Write32 {
318 resource: ResourceId,
319 offset: u64,
320 value: u32,
321 write_mask: u32,
322 precondition: Option<WritePrecondition>,
323 readback: bool,
324 },
325 Poll32 {
326 resource: ResourceId,
327 offset: u64,
328 expected: u32,
329 mask: u32,
330 interval_ns: i64,
331 timeout_ns: i64,
332 },
333 DelayNs(i64),
334 Barrier,
335 GpioRead {
336 resource: ResourceId,
337 },
338 GpioWrite {
339 resource: ResourceId,
340 value: bool,
341 },
342 I2cTransfer {
343 resource: ResourceId,
344 write_data: Vec<u8>,
345 read_length: u32,
346 },
347 SpiTransmit {
348 resource: ResourceId,
349 tx_data: Vec<u8>,
350 },
351}
352
353#[derive(Clone, Debug, PartialEq, Eq)]
355pub enum SequenceItemOutcome {
356 Read32(ReadOutcome),
357 Write32(WriteOutcome),
358 Poll32(PollOutcome),
359 DelayNs,
360 Barrier,
361 Error(Denial),
362 Backend(BackendError),
363 GpioRead(GpioReadOutcome),
364 GpioWrite(GpioWriteOutcome),
365 I2cTransfer(I2cTransferOutcome),
366 SpiTransmit(SpiTransmitOutcome),
367}
368
369#[derive(Clone, Debug, PartialEq, Eq)]
371pub struct SequenceItemResult {
372 pub index: u32,
373 pub ok: bool,
374 pub outcome: SequenceItemOutcome,
375}
376
377#[derive(Clone, Debug, PartialEq, Eq)]
379pub struct SequenceOutcome {
380 pub results: Vec<SequenceItemResult>,
381 pub complete: bool,
382}
383
384#[derive(Clone, Copy, Debug, PartialEq, Eq)]
386pub enum SequenceError {
387 TooManyItems { max: usize, audit_seq: u64 },
388 Rejected { index: usize, denial: Denial, audit_seq: u64 },
389}
390
391#[derive(Clone, Copy, Debug, PartialEq, Eq)]
393pub struct SnapshotItemResult {
394 pub item: SnapshotItem,
396 pub value: Result<u32, BackendError>,
398 pub audit_seq: u64,
400 pub timestamp_ns: i64,
402}
403
404#[derive(Clone, Debug, PartialEq, Eq)]
406pub struct SnapshotOutcome {
407 pub results: Vec<SnapshotItemResult>,
409 pub complete: bool,
413}
414
415#[derive(Clone, Copy, Debug, PartialEq, Eq)]
417pub enum SnapshotError {
418 TooManyItems {
420 max: usize,
422 audit_seq: u64,
424 },
425 Rejected {
427 index: usize,
429 denial: Denial,
431 audit_seq: u64,
433 },
434}
435
436#[derive(Debug)]
439pub struct Executor<B, C> {
440 backends: BTreeMap<ResourceId, B>,
441 clock: C,
442 limits: ExecLimits,
443}
444
445impl<B: ResourceBackend, C: Clock> Executor<B, C> {
446 pub fn new(backends: BTreeMap<ResourceId, B>, clock: C, limits: ExecLimits) -> Self {
448 Self { backends, clock, limits }
449 }
450
451 pub fn limits(&self) -> ExecLimits {
453 self.limits
454 }
455
456 pub fn backend(&self, resource: ResourceId) -> Option<&B> {
458 self.backends.get(&resource)
459 }
460
461 pub fn poll_read_once(
463 &mut self,
464 resource: ResourceId,
465 offset: u64,
466 ) -> Result<u32, BackendError> {
467 let backend = self.backends.get_mut(&resource).ok_or(BackendError::Fault)?;
468 backend.read32(offset)
469 }
470
471 pub fn barrier(&mut self) {
473 for backend in self.backends.values_mut() {
474 backend.barrier();
475 }
476 }
477
478 pub fn read32(
480 &mut self,
481 policy: &AccessPolicy,
482 audit: &mut AuditRing,
483 session: u64,
484 resource: ResourceId,
485 offset: u64,
486 ) -> Result<ReadOutcome, ReadError> {
487 self.read32_internal(policy, audit, session, resource, offset, None)
488 }
489
490 pub fn read32_internal(
492 &mut self,
493 policy: &AccessPolicy,
494 audit: &mut AuditRing,
495 session: u64,
496 resource: ResourceId,
497 offset: u64,
498 item_index: Option<u32>,
499 ) -> Result<ReadOutcome, ReadError> {
500 let timestamp_ns = self.clock.now_ns();
501 let access_class =
502 if item_index.is_some() { AccessClass::Sequence } else { AccessClass::ReadOnce };
503 if let Err(denial) = policy.check_read32(resource, offset, access_class) {
504 let mut record = op_record(
505 session,
506 resource,
507 "read32",
508 offset,
509 Decision::Denied(denial),
510 OpStatus::Rejected,
511 None,
512 timestamp_ns,
513 );
514 record.item_index = item_index;
515 let audit_seq = audit.append(record);
516 return Err(ReadError::Denied { denial, audit_seq });
517 }
518 let Some(backend) = self.backends.get_mut(&resource) else {
519 let mut record = op_record(
520 session,
521 resource,
522 "read32",
523 offset,
524 Decision::Allowed,
525 OpStatus::BackendFault,
526 None,
527 timestamp_ns,
528 );
529 record.item_index = item_index;
530 let audit_seq = audit.append(record);
531 return Err(ReadError::Backend { error: BackendError::Fault, audit_seq });
532 };
533 match backend.read32(offset) {
534 Ok(value) => {
535 let mut record = op_record(
536 session,
537 resource,
538 "read32",
539 offset,
540 Decision::Allowed,
541 OpStatus::Ok,
542 Some(value),
543 timestamp_ns,
544 );
545 record.item_index = item_index;
546 let audit_seq = audit.append(record);
547 Ok(ReadOutcome { value, audit_seq, timestamp_ns })
548 }
549 Err(error) => {
550 let mut record = op_record(
551 session,
552 resource,
553 "read32",
554 offset,
555 Decision::Allowed,
556 OpStatus::BackendFault,
557 None,
558 timestamp_ns,
559 );
560 record.item_index = item_index;
561 let audit_seq = audit.append(record);
562 Err(ReadError::Backend { error, audit_seq })
563 }
564 }
565 }
566
567 pub fn write32(
569 &mut self,
570 policy: &AccessPolicy,
571 audit: &mut AuditRing,
572 session: u64,
573 resource: ResourceId,
574 offset: u64,
575 value: u32,
576 write_mask: u32,
577 precondition: Option<WritePrecondition>,
578 readback: bool,
579 ) -> Result<WriteOutcome, WriteError> {
580 self.write32_internal(
581 policy,
582 audit,
583 session,
584 resource,
585 offset,
586 value,
587 write_mask,
588 precondition,
589 readback,
590 None,
591 )
592 }
593
594 pub fn write32_internal(
596 &mut self,
597 policy: &AccessPolicy,
598 audit: &mut AuditRing,
599 session: u64,
600 resource: ResourceId,
601 offset: u64,
602 value: u32,
603 write_mask: u32,
604 precondition: Option<WritePrecondition>,
605 readback: bool,
606 item_index: Option<u32>,
607 ) -> Result<WriteOutcome, WriteError> {
608 let timestamp_ns = self.clock.now_ns();
609 let reg = match policy.check_write32(resource, offset, write_mask, precondition.as_ref()) {
610 Ok(r) => *r,
611 Err(denial) => {
612 let mut record = op_record(
613 session,
614 resource,
615 "write32",
616 offset,
617 Decision::Denied(denial),
618 OpStatus::Rejected,
619 None,
620 timestamp_ns,
621 );
622 record.item_index = item_index;
623 let audit_seq = audit.append(record);
624 return Err(WriteError::Denied { denial, audit_seq });
625 }
626 };
627
628 let Some(backend) = self.backends.get_mut(&resource) else {
629 let mut record = op_record(
630 session,
631 resource,
632 "write32",
633 offset,
634 Decision::Allowed,
635 OpStatus::BackendFault,
636 None,
637 timestamp_ns,
638 );
639 record.item_index = item_index;
640 let audit_seq = audit.append(record);
641 return Err(WriteError::Backend { error: BackendError::Fault, audit_seq });
642 };
643
644 let needs_before_read =
645 write_mask != 0xFFFF_FFFF || precondition.is_some() || reg.require_precondition;
646 let mut before_val = 0u32;
647 if needs_before_read {
648 let read_ts = self.clock.now_ns();
649 match backend.read32(offset) {
650 Ok(val) => {
651 before_val = val;
652 let mut record = op_record(
653 session,
654 resource,
655 "write32_before_read",
656 offset,
657 Decision::Allowed,
658 OpStatus::Ok,
659 Some(val),
660 read_ts,
661 );
662 record.item_index = item_index;
663 audit.append(record);
664 }
665 Err(error) => {
666 let mut record = op_record(
667 session,
668 resource,
669 "write32_before_read",
670 offset,
671 Decision::Allowed,
672 OpStatus::BackendFault,
673 None,
674 read_ts,
675 );
676 record.item_index = item_index;
677 let audit_seq = audit.append(record);
678 return Err(WriteError::Backend { error, audit_seq });
679 }
680 }
681 }
682
683 if let Some(pre) = precondition {
684 if (before_val & pre.mask) != (pre.expected & pre.mask) {
685 let fail_ts = self.clock.now_ns();
686 let mut record = op_record(
687 session,
688 resource,
689 "write32",
690 offset,
691 Decision::Denied(Denial::PreconditionFailed),
692 OpStatus::Rejected,
693 Some(before_val),
694 fail_ts,
695 );
696 record.item_index = item_index;
697 let audit_seq = audit.append(record);
698 return Err(WriteError::Denied { denial: Denial::PreconditionFailed, audit_seq });
699 }
700 }
701
702 let final_val = if write_mask == 0xFFFF_FFFF {
703 value
704 } else {
705 (before_val & !write_mask) | (value & write_mask)
706 };
707
708 let write_ts = self.clock.now_ns();
709 if let Err(error) = backend.write32(offset, final_val) {
710 let mut record = op_record(
711 session,
712 resource,
713 "write32",
714 offset,
715 Decision::Allowed,
716 OpStatus::BackendFault,
717 Some(final_val),
718 write_ts,
719 );
720 record.item_index = item_index;
721 let audit_seq = audit.append(record);
722 return Err(WriteError::Backend { error, audit_seq });
723 }
724
725 let mut record = op_record(
726 session,
727 resource,
728 "write32",
729 offset,
730 Decision::Allowed,
731 OpStatus::Ok,
732 Some(final_val),
733 write_ts,
734 );
735 record.item_index = item_index;
736 let audit_seq = audit.append(record);
737
738 backend.barrier();
739
740 let do_readback = readback || reg.readback;
741 let readback_val = if do_readback {
742 let rb_ts = self.clock.now_ns();
743 match backend.read32(offset) {
744 Ok(rb) => {
745 let mut rb_record = op_record(
746 session,
747 resource,
748 "write32_readback",
749 offset,
750 Decision::Allowed,
751 OpStatus::Ok,
752 Some(rb),
753 rb_ts,
754 );
755 rb_record.item_index = item_index;
756 audit.append(rb_record);
757 rb
758 }
759 Err(error) => {
760 let mut rb_record = op_record(
761 session,
762 resource,
763 "write32_readback",
764 offset,
765 Decision::Allowed,
766 OpStatus::BackendFault,
767 None,
768 rb_ts,
769 );
770 rb_record.item_index = item_index;
771 let rb_audit_seq = audit.append(rb_record);
772 return Err(WriteError::Backend { error, audit_seq: rb_audit_seq });
773 }
774 }
775 } else {
776 0
777 };
778
779 Ok(WriteOutcome { readback_value: readback_val, audit_seq, timestamp_ns: write_ts })
780 }
781
782 pub fn poll32_read_step(
784 &mut self,
785 audit: &mut AuditRing,
786 session: u64,
787 resource: ResourceId,
788 offset: u64,
789 expected: u32,
790 mask: u32,
791 item_index: Option<u32>,
792 ) -> Result<Result<PollOutcome, u32>, PollError> {
793 let timestamp_ns = self.clock.now_ns();
794 let val = match self.poll_read_once(resource, offset) {
795 Ok(v) => v,
796 Err(error) => {
797 let mut record = op_record(
798 session,
799 resource,
800 "poll32",
801 offset,
802 Decision::Allowed,
803 OpStatus::BackendFault,
804 None,
805 timestamp_ns,
806 );
807 record.item_index = item_index;
808 let audit_seq = audit.append(record);
809 return Err(PollError::Backend { error, audit_seq });
810 }
811 };
812 if (val & mask) == (expected & mask) {
813 let mut record = op_record(
814 session,
815 resource,
816 "poll32",
817 offset,
818 Decision::Allowed,
819 OpStatus::Ok,
820 Some(val),
821 timestamp_ns,
822 );
823 record.item_index = item_index;
824 let audit_seq = audit.append(record);
825 Ok(Ok(PollOutcome { value: val, audit_seq, timestamp_ns }))
826 } else {
827 Ok(Err(val))
828 }
829 }
830
831 pub fn poll32_timeout(
833 &mut self,
834 audit: &mut AuditRing,
835 session: u64,
836 resource: ResourceId,
837 offset: u64,
838 last_val: Option<u32>,
839 item_index: Option<u32>,
840 ) -> PollError {
841 let timestamp_ns = self.clock.now_ns();
842 let mut record = op_record(
843 session,
844 resource,
845 "poll32",
846 offset,
847 Decision::Denied(Denial::Timeout),
848 OpStatus::Rejected,
849 last_val,
850 timestamp_ns,
851 );
852 record.item_index = item_index;
853 let audit_seq = audit.append(record);
854 PollError::Denied { denial: Denial::Timeout, audit_seq }
855 }
856
857 pub fn prevalidate_sequence(
859 &self,
860 policy: &AccessPolicy,
861 items: &[SequenceItem],
862 ) -> Result<(), (usize, Denial)> {
863 if items.len() > self.limits.max_sequence_items {
864 return Err((0, Denial::LimitExceeded));
865 }
866 for (index, item) in items.iter().enumerate() {
867 match item {
868 SequenceItem::Read32 { resource, offset } => {
869 policy
870 .check_read32(*resource, *offset, AccessClass::Sequence)
871 .map_err(|d| (index, d))?;
872 }
873 SequenceItem::Write32 { resource, offset, write_mask, precondition, .. } => {
874 policy
875 .check_write32(*resource, *offset, *write_mask, precondition.as_ref())
876 .map_err(|d| (index, d))?;
877 }
878 SequenceItem::Poll32 {
879 resource, offset, mask, interval_ns, timeout_ns, ..
880 } => {
881 policy
882 .check_poll32(*resource, *offset, *mask, *interval_ns, *timeout_ns)
883 .map_err(|d| (index, d))?;
884 if *timeout_ns > self.limits.max_delay_ns {
885 return Err((index, Denial::LimitExceeded));
886 }
887 }
888 SequenceItem::DelayNs(delay_ns) => {
889 if *delay_ns < 0 || *delay_ns > self.limits.max_delay_ns {
890 return Err((index, Denial::LimitExceeded));
891 }
892 }
893 SequenceItem::Barrier => {}
894 SequenceItem::GpioRead { resource } => {
895 policy.check_gpio_read(*resource).map_err(|d| (index, d))?;
896 }
897 SequenceItem::GpioWrite { resource, .. } => {
898 policy.check_gpio_write(*resource).map_err(|d| (index, d))?;
899 }
900 SequenceItem::I2cTransfer { resource, write_data, read_length } => {
901 policy
902 .check_i2c_transfer(*resource, write_data.len(), *read_length as usize)
903 .map_err(|d| (index, d))?;
904 }
905 SequenceItem::SpiTransmit { resource, tx_data } => {
906 policy.check_spi_transmit(*resource, tx_data.len()).map_err(|d| (index, d))?;
907 }
908 }
909 }
910 Ok(())
911 }
912
913 pub fn snapshot(
917 &mut self,
918 policy: &AccessPolicy,
919 audit: &mut AuditRing,
920 session: u64,
921 items: &[SnapshotItem],
922 ) -> Result<SnapshotOutcome, SnapshotError> {
923 if items.len() > self.limits.max_snapshot_items {
924 let timestamp_ns = self.clock.now_ns();
925 let audit_seq = audit.append(AuditRecord {
926 session: Some(session),
927 resource: None,
928 operation: "snapshot",
929 offset: None,
930 decision: Decision::Denied(Denial::LimitExceeded),
931 status: OpStatus::Rejected,
932 value: None,
933 timestamp_ns,
934 run_id: None,
935 item_index: None,
936 });
937 return Err(SnapshotError::TooManyItems {
938 max: self.limits.max_snapshot_items,
939 audit_seq,
940 });
941 }
942 for (index, item) in items.iter().enumerate() {
943 if let Err(denial) =
944 policy.check_read32(item.resource, item.offset, AccessClass::Snapshot)
945 {
946 let timestamp_ns = self.clock.now_ns();
947 let mut record = op_record(
948 session,
949 item.resource,
950 "snapshot",
951 item.offset,
952 Decision::Denied(denial),
953 OpStatus::Rejected,
954 None,
955 timestamp_ns,
956 );
957 record.item_index = Some(index as u32);
958 let audit_seq = audit.append(record);
959 return Err(SnapshotError::Rejected { index, denial, audit_seq });
960 }
961 }
962 let mut results = Vec::with_capacity(items.len());
963 let mut complete = true;
964 for (index, item) in items.iter().enumerate() {
965 let timestamp_ns = self.clock.now_ns();
966 let value = match self.backends.get_mut(&item.resource) {
967 Some(backend) => backend.read32(item.offset),
968 None => Err(BackendError::Fault),
969 };
970 let (status, audited_value) = match value {
971 Ok(read) => (OpStatus::Ok, Some(read)),
972 Err(_) => (OpStatus::BackendFault, None),
973 };
974 let mut record = op_record(
975 session,
976 item.resource,
977 "snapshot_read32",
978 item.offset,
979 Decision::Allowed,
980 status,
981 audited_value,
982 timestamp_ns,
983 );
984 record.item_index = Some(index as u32);
985 let audit_seq = audit.append(record);
986 results.push(SnapshotItemResult { item: *item, value, audit_seq, timestamp_ns });
987 if value.is_err() {
988 complete = false;
989 break;
990 }
991 }
992 Ok(SnapshotOutcome { results, complete })
993 }
994
995 pub fn gpio_read(
997 &mut self,
998 policy: &AccessPolicy,
999 audit: &mut AuditRing,
1000 session: u64,
1001 resource: ResourceId,
1002 ) -> Result<GpioReadOutcome, GpioReadError> {
1003 self.gpio_read_internal(policy, audit, session, resource, None)
1004 }
1005
1006 pub fn gpio_read_internal(
1008 &mut self,
1009 policy: &AccessPolicy,
1010 audit: &mut AuditRing,
1011 session: u64,
1012 resource: ResourceId,
1013 item_index: Option<u32>,
1014 ) -> Result<GpioReadOutcome, GpioReadError> {
1015 let timestamp_ns = self.clock.now_ns();
1016 if let Err(denial) = policy.check_gpio_read(resource) {
1017 let mut record = op_record(
1018 session,
1019 resource,
1020 "gpio_read",
1021 0,
1022 Decision::Denied(denial),
1023 OpStatus::Rejected,
1024 None,
1025 timestamp_ns,
1026 );
1027 record.item_index = item_index;
1028 let audit_seq = audit.append(record);
1029 return Err(GpioReadError::Denied { denial, audit_seq });
1030 }
1031 let Some(backend) = self.backends.get_mut(&resource) else {
1032 let mut record = op_record(
1033 session,
1034 resource,
1035 "gpio_read",
1036 0,
1037 Decision::Allowed,
1038 OpStatus::BackendFault,
1039 None,
1040 timestamp_ns,
1041 );
1042 record.item_index = item_index;
1043 let audit_seq = audit.append(record);
1044 return Err(GpioReadError::Backend { error: BackendError::Fault, audit_seq });
1045 };
1046 match backend.gpio_read() {
1047 Ok(value) => {
1048 let mut record = op_record(
1049 session,
1050 resource,
1051 "gpio_read",
1052 0,
1053 Decision::Allowed,
1054 OpStatus::Ok,
1055 Some(value as u32),
1056 timestamp_ns,
1057 );
1058 record.item_index = item_index;
1059 let audit_seq = audit.append(record);
1060 Ok(GpioReadOutcome { value, audit_seq, timestamp_ns })
1061 }
1062 Err(error) => {
1063 let mut record = op_record(
1064 session,
1065 resource,
1066 "gpio_read",
1067 0,
1068 Decision::Allowed,
1069 OpStatus::BackendFault,
1070 None,
1071 timestamp_ns,
1072 );
1073 record.item_index = item_index;
1074 let audit_seq = audit.append(record);
1075 Err(GpioReadError::Backend { error, audit_seq })
1076 }
1077 }
1078 }
1079
1080 pub fn gpio_write(
1082 &mut self,
1083 policy: &AccessPolicy,
1084 audit: &mut AuditRing,
1085 session: u64,
1086 resource: ResourceId,
1087 value: bool,
1088 ) -> Result<GpioWriteOutcome, GpioWriteError> {
1089 self.gpio_write_internal(policy, audit, session, resource, value, None)
1090 }
1091
1092 pub fn gpio_write_internal(
1094 &mut self,
1095 policy: &AccessPolicy,
1096 audit: &mut AuditRing,
1097 session: u64,
1098 resource: ResourceId,
1099 value: bool,
1100 item_index: Option<u32>,
1101 ) -> Result<GpioWriteOutcome, GpioWriteError> {
1102 let timestamp_ns = self.clock.now_ns();
1103 if let Err(denial) = policy.check_gpio_write(resource) {
1104 let mut record = op_record(
1105 session,
1106 resource,
1107 "gpio_write",
1108 0,
1109 Decision::Denied(denial),
1110 OpStatus::Rejected,
1111 Some(value as u32),
1112 timestamp_ns,
1113 );
1114 record.item_index = item_index;
1115 let audit_seq = audit.append(record);
1116 return Err(GpioWriteError::Denied { denial, audit_seq });
1117 }
1118 let Some(backend) = self.backends.get_mut(&resource) else {
1119 let mut record = op_record(
1120 session,
1121 resource,
1122 "gpio_write",
1123 0,
1124 Decision::Allowed,
1125 OpStatus::BackendFault,
1126 Some(value as u32),
1127 timestamp_ns,
1128 );
1129 record.item_index = item_index;
1130 let audit_seq = audit.append(record);
1131 return Err(GpioWriteError::Backend { error: BackendError::Fault, audit_seq });
1132 };
1133 match backend.gpio_write(value) {
1134 Ok(()) => {
1135 let mut record = op_record(
1136 session,
1137 resource,
1138 "gpio_write",
1139 0,
1140 Decision::Allowed,
1141 OpStatus::Ok,
1142 Some(value as u32),
1143 timestamp_ns,
1144 );
1145 record.item_index = item_index;
1146 let audit_seq = audit.append(record);
1147 Ok(GpioWriteOutcome { audit_seq, timestamp_ns })
1148 }
1149 Err(error) => {
1150 let mut record = op_record(
1151 session,
1152 resource,
1153 "gpio_write",
1154 0,
1155 Decision::Allowed,
1156 OpStatus::BackendFault,
1157 Some(value as u32),
1158 timestamp_ns,
1159 );
1160 record.item_index = item_index;
1161 let audit_seq = audit.append(record);
1162 Err(GpioWriteError::Backend { error, audit_seq })
1163 }
1164 }
1165 }
1166
1167 pub fn i2c_transfer(
1169 &mut self,
1170 policy: &AccessPolicy,
1171 audit: &mut AuditRing,
1172 session: u64,
1173 resource: ResourceId,
1174 write_data: &[u8],
1175 read_length: u32,
1176 ) -> Result<I2cTransferOutcome, I2cTransferError> {
1177 self.i2c_transfer_internal(policy, audit, session, resource, write_data, read_length, None)
1178 }
1179
1180 pub fn i2c_transfer_internal(
1182 &mut self,
1183 policy: &AccessPolicy,
1184 audit: &mut AuditRing,
1185 session: u64,
1186 resource: ResourceId,
1187 write_data: &[u8],
1188 read_length: u32,
1189 item_index: Option<u32>,
1190 ) -> Result<I2cTransferOutcome, I2cTransferError> {
1191 let timestamp_ns = self.clock.now_ns();
1192 if let Err(denial) =
1193 policy.check_i2c_transfer(resource, write_data.len(), read_length as usize)
1194 {
1195 let mut record = op_record(
1196 session,
1197 resource,
1198 "i2c_transfer",
1199 0,
1200 Decision::Denied(denial),
1201 OpStatus::Rejected,
1202 None,
1203 timestamp_ns,
1204 );
1205 record.item_index = item_index;
1206 let audit_seq = audit.append(record);
1207 return Err(I2cTransferError::Denied { denial, audit_seq });
1208 }
1209 let Some(backend) = self.backends.get_mut(&resource) else {
1210 let mut record = op_record(
1211 session,
1212 resource,
1213 "i2c_transfer",
1214 0,
1215 Decision::Allowed,
1216 OpStatus::BackendFault,
1217 None,
1218 timestamp_ns,
1219 );
1220 record.item_index = item_index;
1221 let audit_seq = audit.append(record);
1222 return Err(I2cTransferError::Backend { error: BackendError::Fault, audit_seq });
1223 };
1224 match backend.i2c_transfer(write_data, read_length as usize) {
1225 Ok(read_data) => {
1226 let mut record = op_record(
1227 session,
1228 resource,
1229 "i2c_transfer",
1230 0,
1231 Decision::Allowed,
1232 OpStatus::Ok,
1233 Some(read_data.len() as u32),
1234 timestamp_ns,
1235 );
1236 record.item_index = item_index;
1237 let audit_seq = audit.append(record);
1238 Ok(I2cTransferOutcome { read_data, audit_seq, timestamp_ns })
1239 }
1240 Err(error) => {
1241 let mut record = op_record(
1242 session,
1243 resource,
1244 "i2c_transfer",
1245 0,
1246 Decision::Allowed,
1247 OpStatus::BackendFault,
1248 None,
1249 timestamp_ns,
1250 );
1251 record.item_index = item_index;
1252 let audit_seq = audit.append(record);
1253 Err(I2cTransferError::Backend { error, audit_seq })
1254 }
1255 }
1256 }
1257
1258 pub fn spi_transmit(
1260 &mut self,
1261 policy: &AccessPolicy,
1262 audit: &mut AuditRing,
1263 session: u64,
1264 resource: ResourceId,
1265 tx_data: &[u8],
1266 ) -> Result<SpiTransmitOutcome, SpiTransmitError> {
1267 self.spi_transmit_internal(policy, audit, session, resource, tx_data, None)
1268 }
1269
1270 pub fn spi_transmit_internal(
1272 &mut self,
1273 policy: &AccessPolicy,
1274 audit: &mut AuditRing,
1275 session: u64,
1276 resource: ResourceId,
1277 tx_data: &[u8],
1278 item_index: Option<u32>,
1279 ) -> Result<SpiTransmitOutcome, SpiTransmitError> {
1280 let timestamp_ns = self.clock.now_ns();
1281 if let Err(denial) = policy.check_spi_transmit(resource, tx_data.len()) {
1282 let mut record = op_record(
1283 session,
1284 resource,
1285 "spi_transmit",
1286 0,
1287 Decision::Denied(denial),
1288 OpStatus::Rejected,
1289 None,
1290 timestamp_ns,
1291 );
1292 record.item_index = item_index;
1293 let audit_seq = audit.append(record);
1294 return Err(SpiTransmitError::Denied { denial, audit_seq });
1295 }
1296 let Some(backend) = self.backends.get_mut(&resource) else {
1297 let mut record = op_record(
1298 session,
1299 resource,
1300 "spi_transmit",
1301 0,
1302 Decision::Allowed,
1303 OpStatus::BackendFault,
1304 None,
1305 timestamp_ns,
1306 );
1307 record.item_index = item_index;
1308 let audit_seq = audit.append(record);
1309 return Err(SpiTransmitError::Backend { error: BackendError::Fault, audit_seq });
1310 };
1311 match backend.spi_transmit(tx_data) {
1312 Ok(rx_data) => {
1313 let mut record = op_record(
1314 session,
1315 resource,
1316 "spi_transmit",
1317 0,
1318 Decision::Allowed,
1319 OpStatus::Ok,
1320 Some(rx_data.len() as u32),
1321 timestamp_ns,
1322 );
1323 record.item_index = item_index;
1324 let audit_seq = audit.append(record);
1325 Ok(SpiTransmitOutcome { rx_data, audit_seq, timestamp_ns })
1326 }
1327 Err(error) => {
1328 let mut record = op_record(
1329 session,
1330 resource,
1331 "spi_transmit",
1332 0,
1333 Decision::Allowed,
1334 OpStatus::BackendFault,
1335 None,
1336 timestamp_ns,
1337 );
1338 record.item_index = item_index;
1339 let audit_seq = audit.append(record);
1340 Err(SpiTransmitError::Backend { error, audit_seq })
1341 }
1342 }
1343 }
1344
1345 pub fn clock_enable(
1346 &mut self,
1347 policy: &AccessPolicy,
1348 audit: &mut AuditRing,
1349 session: u64,
1350 resource: ResourceId,
1351 ) -> Result<ClockEnableOutcome, ClockOpError> {
1352 let timestamp_ns = self.clock.now_ns();
1353 if let Err(denial) = policy.check_clock_enable(resource) {
1354 let record = op_record(
1355 session,
1356 resource,
1357 "clock_enable",
1358 0,
1359 Decision::Denied(denial),
1360 OpStatus::Rejected,
1361 None,
1362 timestamp_ns,
1363 );
1364 let audit_seq = audit.append(record);
1365 return Err(ClockOpError::Denied { denial, audit_seq });
1366 }
1367 let Some(backend) = self.backends.get_mut(&resource) else {
1368 let record = op_record(
1369 session,
1370 resource,
1371 "clock_enable",
1372 0,
1373 Decision::Allowed,
1374 OpStatus::BackendFault,
1375 None,
1376 timestamp_ns,
1377 );
1378 let audit_seq = audit.append(record);
1379 return Err(ClockOpError::Backend { error: BackendError::Fault, audit_seq });
1380 };
1381 match backend.clock_enable() {
1382 Ok(()) => {
1383 let record = op_record(
1384 session,
1385 resource,
1386 "clock_enable",
1387 0,
1388 Decision::Allowed,
1389 OpStatus::Ok,
1390 None,
1391 timestamp_ns,
1392 );
1393 let audit_seq = audit.append(record);
1394 Ok(ClockEnableOutcome { audit_seq, timestamp_ns })
1395 }
1396 Err(error) => {
1397 let record = op_record(
1398 session,
1399 resource,
1400 "clock_enable",
1401 0,
1402 Decision::Allowed,
1403 OpStatus::BackendFault,
1404 None,
1405 timestamp_ns,
1406 );
1407 let audit_seq = audit.append(record);
1408 Err(ClockOpError::Backend { error, audit_seq })
1409 }
1410 }
1411 }
1412
1413 pub fn clock_disable(
1414 &mut self,
1415 policy: &AccessPolicy,
1416 audit: &mut AuditRing,
1417 session: u64,
1418 resource: ResourceId,
1419 ) -> Result<ClockDisableOutcome, ClockOpError> {
1420 let timestamp_ns = self.clock.now_ns();
1421 if let Err(denial) = policy.check_clock_disable(resource) {
1422 let record = op_record(
1423 session,
1424 resource,
1425 "clock_disable",
1426 0,
1427 Decision::Denied(denial),
1428 OpStatus::Rejected,
1429 None,
1430 timestamp_ns,
1431 );
1432 let audit_seq = audit.append(record);
1433 return Err(ClockOpError::Denied { denial, audit_seq });
1434 }
1435 let Some(backend) = self.backends.get_mut(&resource) else {
1436 let record = op_record(
1437 session,
1438 resource,
1439 "clock_disable",
1440 0,
1441 Decision::Allowed,
1442 OpStatus::BackendFault,
1443 None,
1444 timestamp_ns,
1445 );
1446 let audit_seq = audit.append(record);
1447 return Err(ClockOpError::Backend { error: BackendError::Fault, audit_seq });
1448 };
1449 match backend.clock_disable() {
1450 Ok(()) => {
1451 let record = op_record(
1452 session,
1453 resource,
1454 "clock_disable",
1455 0,
1456 Decision::Allowed,
1457 OpStatus::Ok,
1458 None,
1459 timestamp_ns,
1460 );
1461 let audit_seq = audit.append(record);
1462 Ok(ClockDisableOutcome { audit_seq, timestamp_ns })
1463 }
1464 Err(error) => {
1465 let record = op_record(
1466 session,
1467 resource,
1468 "clock_disable",
1469 0,
1470 Decision::Allowed,
1471 OpStatus::BackendFault,
1472 None,
1473 timestamp_ns,
1474 );
1475 let audit_seq = audit.append(record);
1476 Err(ClockOpError::Backend { error, audit_seq })
1477 }
1478 }
1479 }
1480
1481 pub fn clock_is_enabled(
1482 &mut self,
1483 policy: &AccessPolicy,
1484 audit: &mut AuditRing,
1485 session: u64,
1486 resource: ResourceId,
1487 ) -> Result<ClockIsEnabledOutcome, ClockOpError> {
1488 let timestamp_ns = self.clock.now_ns();
1489 if let Err(denial) = policy.check_clock_is_enabled(resource) {
1490 let record = op_record(
1491 session,
1492 resource,
1493 "clock_is_enabled",
1494 0,
1495 Decision::Denied(denial),
1496 OpStatus::Rejected,
1497 None,
1498 timestamp_ns,
1499 );
1500 let audit_seq = audit.append(record);
1501 return Err(ClockOpError::Denied { denial, audit_seq });
1502 }
1503 let Some(backend) = self.backends.get_mut(&resource) else {
1504 let record = op_record(
1505 session,
1506 resource,
1507 "clock_is_enabled",
1508 0,
1509 Decision::Allowed,
1510 OpStatus::BackendFault,
1511 None,
1512 timestamp_ns,
1513 );
1514 let audit_seq = audit.append(record);
1515 return Err(ClockOpError::Backend { error: BackendError::Fault, audit_seq });
1516 };
1517 match backend.clock_is_enabled() {
1518 Ok(enabled) => {
1519 let record = op_record(
1520 session,
1521 resource,
1522 "clock_is_enabled",
1523 0,
1524 Decision::Allowed,
1525 OpStatus::Ok,
1526 Some(enabled as u32),
1527 timestamp_ns,
1528 );
1529 let audit_seq = audit.append(record);
1530 Ok(ClockIsEnabledOutcome { enabled, audit_seq, timestamp_ns })
1531 }
1532 Err(error) => {
1533 let record = op_record(
1534 session,
1535 resource,
1536 "clock_is_enabled",
1537 0,
1538 Decision::Allowed,
1539 OpStatus::BackendFault,
1540 None,
1541 timestamp_ns,
1542 );
1543 let audit_seq = audit.append(record);
1544 Err(ClockOpError::Backend { error, audit_seq })
1545 }
1546 }
1547 }
1548
1549 pub fn clock_set_rate(
1550 &mut self,
1551 policy: &AccessPolicy,
1552 audit: &mut AuditRing,
1553 session: u64,
1554 resource: ResourceId,
1555 hz: u64,
1556 ) -> Result<ClockSetRateOutcome, ClockOpError> {
1557 let timestamp_ns = self.clock.now_ns();
1558 if let Err(denial) = policy.check_clock_set_rate(resource) {
1559 let record = op_record(
1560 session,
1561 resource,
1562 "clock_set_rate",
1563 0,
1564 Decision::Denied(denial),
1565 OpStatus::Rejected,
1566 None,
1567 timestamp_ns,
1568 );
1569 let audit_seq = audit.append(record);
1570 return Err(ClockOpError::Denied { denial, audit_seq });
1571 }
1572 let Some(backend) = self.backends.get_mut(&resource) else {
1573 let record = op_record(
1574 session,
1575 resource,
1576 "clock_set_rate",
1577 0,
1578 Decision::Allowed,
1579 OpStatus::BackendFault,
1580 None,
1581 timestamp_ns,
1582 );
1583 let audit_seq = audit.append(record);
1584 return Err(ClockOpError::Backend { error: BackendError::Fault, audit_seq });
1585 };
1586 match backend.clock_set_rate(hz) {
1587 Ok(()) => {
1588 let record = op_record(
1589 session,
1590 resource,
1591 "clock_set_rate",
1592 0,
1593 Decision::Allowed,
1594 OpStatus::Ok,
1595 Some((hz & 0xffffffff) as u32),
1596 timestamp_ns,
1597 );
1598 let audit_seq = audit.append(record);
1599 Ok(ClockSetRateOutcome { audit_seq, timestamp_ns })
1600 }
1601 Err(error) => {
1602 let record = op_record(
1603 session,
1604 resource,
1605 "clock_set_rate",
1606 0,
1607 Decision::Allowed,
1608 OpStatus::BackendFault,
1609 None,
1610 timestamp_ns,
1611 );
1612 let audit_seq = audit.append(record);
1613 Err(ClockOpError::Backend { error, audit_seq })
1614 }
1615 }
1616 }
1617
1618 pub fn clock_query_rate(
1619 &mut self,
1620 policy: &AccessPolicy,
1621 audit: &mut AuditRing,
1622 session: u64,
1623 resource: ResourceId,
1624 hz_in: u64,
1625 ) -> Result<ClockQueryRateOutcome, ClockOpError> {
1626 let timestamp_ns = self.clock.now_ns();
1627 if let Err(denial) = policy.check_clock_query_rate(resource) {
1628 let record = op_record(
1629 session,
1630 resource,
1631 "clock_query_rate",
1632 0,
1633 Decision::Denied(denial),
1634 OpStatus::Rejected,
1635 None,
1636 timestamp_ns,
1637 );
1638 let audit_seq = audit.append(record);
1639 return Err(ClockOpError::Denied { denial, audit_seq });
1640 }
1641 let Some(backend) = self.backends.get_mut(&resource) else {
1642 let record = op_record(
1643 session,
1644 resource,
1645 "clock_query_rate",
1646 0,
1647 Decision::Allowed,
1648 OpStatus::BackendFault,
1649 None,
1650 timestamp_ns,
1651 );
1652 let audit_seq = audit.append(record);
1653 return Err(ClockOpError::Backend { error: BackendError::Fault, audit_seq });
1654 };
1655 match backend.clock_query_rate(hz_in) {
1656 Ok(hz_out) => {
1657 let record = op_record(
1658 session,
1659 resource,
1660 "clock_query_rate",
1661 0,
1662 Decision::Allowed,
1663 OpStatus::Ok,
1664 Some((hz_out & 0xffffffff) as u32),
1665 timestamp_ns,
1666 );
1667 let audit_seq = audit.append(record);
1668 Ok(ClockQueryRateOutcome { hz_out, audit_seq, timestamp_ns })
1669 }
1670 Err(error) => {
1671 let record = op_record(
1672 session,
1673 resource,
1674 "clock_query_rate",
1675 0,
1676 Decision::Allowed,
1677 OpStatus::BackendFault,
1678 None,
1679 timestamp_ns,
1680 );
1681 let audit_seq = audit.append(record);
1682 Err(ClockOpError::Backend { error, audit_seq })
1683 }
1684 }
1685 }
1686
1687 pub fn clock_get_rate(
1688 &mut self,
1689 policy: &AccessPolicy,
1690 audit: &mut AuditRing,
1691 session: u64,
1692 resource: ResourceId,
1693 ) -> Result<ClockGetRateOutcome, ClockOpError> {
1694 let timestamp_ns = self.clock.now_ns();
1695 if let Err(denial) = policy.check_clock_get_rate(resource) {
1696 let record = op_record(
1697 session,
1698 resource,
1699 "clock_get_rate",
1700 0,
1701 Decision::Denied(denial),
1702 OpStatus::Rejected,
1703 None,
1704 timestamp_ns,
1705 );
1706 let audit_seq = audit.append(record);
1707 return Err(ClockOpError::Denied { denial, audit_seq });
1708 }
1709 let Some(backend) = self.backends.get_mut(&resource) else {
1710 let record = op_record(
1711 session,
1712 resource,
1713 "clock_get_rate",
1714 0,
1715 Decision::Allowed,
1716 OpStatus::BackendFault,
1717 None,
1718 timestamp_ns,
1719 );
1720 let audit_seq = audit.append(record);
1721 return Err(ClockOpError::Backend { error: BackendError::Fault, audit_seq });
1722 };
1723 match backend.clock_get_rate() {
1724 Ok(hz) => {
1725 let record = op_record(
1726 session,
1727 resource,
1728 "clock_get_rate",
1729 0,
1730 Decision::Allowed,
1731 OpStatus::Ok,
1732 Some((hz & 0xffffffff) as u32),
1733 timestamp_ns,
1734 );
1735 let audit_seq = audit.append(record);
1736 Ok(ClockGetRateOutcome { hz, audit_seq, timestamp_ns })
1737 }
1738 Err(error) => {
1739 let record = op_record(
1740 session,
1741 resource,
1742 "clock_get_rate",
1743 0,
1744 Decision::Allowed,
1745 OpStatus::BackendFault,
1746 None,
1747 timestamp_ns,
1748 );
1749 let audit_seq = audit.append(record);
1750 Err(ClockOpError::Backend { error, audit_seq })
1751 }
1752 }
1753 }
1754
1755 pub fn reset_assert(
1756 &mut self,
1757 policy: &AccessPolicy,
1758 audit: &mut AuditRing,
1759 session: u64,
1760 resource: ResourceId,
1761 ) -> Result<ResetAssertOutcome, ResetOpError> {
1762 let timestamp_ns = self.clock.now_ns();
1763 if let Err(denial) = policy.check_reset_assert(resource) {
1764 let record = op_record(
1765 session,
1766 resource,
1767 "reset_assert",
1768 0,
1769 Decision::Denied(denial),
1770 OpStatus::Rejected,
1771 None,
1772 timestamp_ns,
1773 );
1774 let audit_seq = audit.append(record);
1775 return Err(ResetOpError::Denied { denial, audit_seq });
1776 }
1777 let Some(backend) = self.backends.get_mut(&resource) else {
1778 let record = op_record(
1779 session,
1780 resource,
1781 "reset_assert",
1782 0,
1783 Decision::Allowed,
1784 OpStatus::BackendFault,
1785 None,
1786 timestamp_ns,
1787 );
1788 let audit_seq = audit.append(record);
1789 return Err(ResetOpError::Backend { error: BackendError::Fault, audit_seq });
1790 };
1791 match backend.reset_assert() {
1792 Ok(()) => {
1793 let record = op_record(
1794 session,
1795 resource,
1796 "reset_assert",
1797 0,
1798 Decision::Allowed,
1799 OpStatus::Ok,
1800 None,
1801 timestamp_ns,
1802 );
1803 let audit_seq = audit.append(record);
1804 Ok(ResetAssertOutcome { audit_seq, timestamp_ns })
1805 }
1806 Err(error) => {
1807 let record = op_record(
1808 session,
1809 resource,
1810 "reset_assert",
1811 0,
1812 Decision::Allowed,
1813 OpStatus::BackendFault,
1814 None,
1815 timestamp_ns,
1816 );
1817 let audit_seq = audit.append(record);
1818 Err(ResetOpError::Backend { error, audit_seq })
1819 }
1820 }
1821 }
1822
1823 pub fn reset_deassert(
1824 &mut self,
1825 policy: &AccessPolicy,
1826 audit: &mut AuditRing,
1827 session: u64,
1828 resource: ResourceId,
1829 ) -> Result<ResetDeassertOutcome, ResetOpError> {
1830 let timestamp_ns = self.clock.now_ns();
1831 if let Err(denial) = policy.check_reset_deassert(resource) {
1832 let record = op_record(
1833 session,
1834 resource,
1835 "reset_deassert",
1836 0,
1837 Decision::Denied(denial),
1838 OpStatus::Rejected,
1839 None,
1840 timestamp_ns,
1841 );
1842 let audit_seq = audit.append(record);
1843 return Err(ResetOpError::Denied { denial, audit_seq });
1844 }
1845 let Some(backend) = self.backends.get_mut(&resource) else {
1846 let record = op_record(
1847 session,
1848 resource,
1849 "reset_deassert",
1850 0,
1851 Decision::Allowed,
1852 OpStatus::BackendFault,
1853 None,
1854 timestamp_ns,
1855 );
1856 let audit_seq = audit.append(record);
1857 return Err(ResetOpError::Backend { error: BackendError::Fault, audit_seq });
1858 };
1859 match backend.reset_deassert() {
1860 Ok(()) => {
1861 let record = op_record(
1862 session,
1863 resource,
1864 "reset_deassert",
1865 0,
1866 Decision::Allowed,
1867 OpStatus::Ok,
1868 None,
1869 timestamp_ns,
1870 );
1871 let audit_seq = audit.append(record);
1872 Ok(ResetDeassertOutcome { audit_seq, timestamp_ns })
1873 }
1874 Err(error) => {
1875 let record = op_record(
1876 session,
1877 resource,
1878 "reset_deassert",
1879 0,
1880 Decision::Allowed,
1881 OpStatus::BackendFault,
1882 None,
1883 timestamp_ns,
1884 );
1885 let audit_seq = audit.append(record);
1886 Err(ResetOpError::Backend { error, audit_seq })
1887 }
1888 }
1889 }
1890
1891 pub fn reset_toggle(
1892 &mut self,
1893 policy: &AccessPolicy,
1894 audit: &mut AuditRing,
1895 session: u64,
1896 resource: ResourceId,
1897 ) -> Result<ResetToggleOutcome, ResetOpError> {
1898 let timestamp_ns = self.clock.now_ns();
1899 if let Err(denial) = policy.check_reset_toggle(resource) {
1900 let record = op_record(
1901 session,
1902 resource,
1903 "reset_toggle",
1904 0,
1905 Decision::Denied(denial),
1906 OpStatus::Rejected,
1907 None,
1908 timestamp_ns,
1909 );
1910 let audit_seq = audit.append(record);
1911 return Err(ResetOpError::Denied { denial, audit_seq });
1912 }
1913 let Some(backend) = self.backends.get_mut(&resource) else {
1914 let record = op_record(
1915 session,
1916 resource,
1917 "reset_toggle",
1918 0,
1919 Decision::Allowed,
1920 OpStatus::BackendFault,
1921 None,
1922 timestamp_ns,
1923 );
1924 let audit_seq = audit.append(record);
1925 return Err(ResetOpError::Backend { error: BackendError::Fault, audit_seq });
1926 };
1927 match backend.reset_toggle() {
1928 Ok(()) => {
1929 let record = op_record(
1930 session,
1931 resource,
1932 "reset_toggle",
1933 0,
1934 Decision::Allowed,
1935 OpStatus::Ok,
1936 None,
1937 timestamp_ns,
1938 );
1939 let audit_seq = audit.append(record);
1940 Ok(ResetToggleOutcome { audit_seq, timestamp_ns })
1941 }
1942 Err(error) => {
1943 let record = op_record(
1944 session,
1945 resource,
1946 "reset_toggle",
1947 0,
1948 Decision::Allowed,
1949 OpStatus::BackendFault,
1950 None,
1951 timestamp_ns,
1952 );
1953 let audit_seq = audit.append(record);
1954 Err(ResetOpError::Backend { error, audit_seq })
1955 }
1956 }
1957 }
1958
1959 pub fn reset_status(
1960 &mut self,
1961 policy: &AccessPolicy,
1962 audit: &mut AuditRing,
1963 session: u64,
1964 resource: ResourceId,
1965 ) -> Result<ResetStatusOutcome, ResetOpError> {
1966 let timestamp_ns = self.clock.now_ns();
1967 if let Err(denial) = policy.check_reset_status(resource) {
1968 let record = op_record(
1969 session,
1970 resource,
1971 "reset_status",
1972 0,
1973 Decision::Denied(denial),
1974 OpStatus::Rejected,
1975 None,
1976 timestamp_ns,
1977 );
1978 let audit_seq = audit.append(record);
1979 return Err(ResetOpError::Denied { denial, audit_seq });
1980 }
1981 let Some(backend) = self.backends.get_mut(&resource) else {
1982 let record = op_record(
1983 session,
1984 resource,
1985 "reset_status",
1986 0,
1987 Decision::Allowed,
1988 OpStatus::BackendFault,
1989 None,
1990 timestamp_ns,
1991 );
1992 let audit_seq = audit.append(record);
1993 return Err(ResetOpError::Backend { error: BackendError::Fault, audit_seq });
1994 };
1995 match backend.reset_status() {
1996 Ok(asserted) => {
1997 let record = op_record(
1998 session,
1999 resource,
2000 "reset_status",
2001 0,
2002 Decision::Allowed,
2003 OpStatus::Ok,
2004 Some(asserted as u32),
2005 timestamp_ns,
2006 );
2007 let audit_seq = audit.append(record);
2008 Ok(ResetStatusOutcome { asserted, audit_seq, timestamp_ns })
2009 }
2010 Err(error) => {
2011 let record = op_record(
2012 session,
2013 resource,
2014 "reset_status",
2015 0,
2016 Decision::Allowed,
2017 OpStatus::BackendFault,
2018 None,
2019 timestamp_ns,
2020 );
2021 let audit_seq = audit.append(record);
2022 Err(ResetOpError::Backend { error, audit_seq })
2023 }
2024 }
2025 }
2026
2027 pub fn serial_read(
2028 &mut self,
2029 policy: &AccessPolicy,
2030 audit: &mut AuditRing,
2031 session: u64,
2032 resource: ResourceId,
2033 ) -> Result<SerialReadOutcome, SerialOpError> {
2034 let timestamp_ns = self.clock.now_ns();
2035 if let Err(denial) = policy.check_serial_read(resource) {
2036 let record = op_record(
2037 session,
2038 resource,
2039 "serial_read",
2040 0,
2041 Decision::Denied(denial),
2042 OpStatus::Rejected,
2043 None,
2044 timestamp_ns,
2045 );
2046 let audit_seq = audit.append(record);
2047 return Err(SerialOpError::Denied { denial, audit_seq });
2048 }
2049 let Some(backend) = self.backends.get_mut(&resource) else {
2050 let record = op_record(
2051 session,
2052 resource,
2053 "serial_read",
2054 0,
2055 Decision::Allowed,
2056 OpStatus::BackendFault,
2057 None,
2058 timestamp_ns,
2059 );
2060 let audit_seq = audit.append(record);
2061 return Err(SerialOpError::Backend { error: BackendError::Fault, audit_seq });
2062 };
2063 match backend.serial_read() {
2064 Ok(data) => {
2065 let record = op_record(
2066 session,
2067 resource,
2068 "serial_read",
2069 0,
2070 Decision::Allowed,
2071 OpStatus::Ok,
2072 Some(data.len() as u32),
2073 timestamp_ns,
2074 );
2075 let audit_seq = audit.append(record);
2076 Ok(SerialReadOutcome { data, audit_seq, timestamp_ns })
2077 }
2078 Err(error) => {
2079 let record = op_record(
2080 session,
2081 resource,
2082 "serial_read",
2083 0,
2084 Decision::Allowed,
2085 OpStatus::BackendFault,
2086 None,
2087 timestamp_ns,
2088 );
2089 let audit_seq = audit.append(record);
2090 Err(SerialOpError::Backend { error, audit_seq })
2091 }
2092 }
2093 }
2094
2095 pub fn serial_write(
2096 &mut self,
2097 policy: &AccessPolicy,
2098 audit: &mut AuditRing,
2099 session: u64,
2100 resource: ResourceId,
2101 data: &[u8],
2102 ) -> Result<SerialWriteOutcome, SerialOpError> {
2103 let timestamp_ns = self.clock.now_ns();
2104 if let Err(denial) = policy.check_serial_write(resource, data.len()) {
2105 let record = op_record(
2106 session,
2107 resource,
2108 "serial_write",
2109 0,
2110 Decision::Denied(denial),
2111 OpStatus::Rejected,
2112 None,
2113 timestamp_ns,
2114 );
2115 let audit_seq = audit.append(record);
2116 return Err(SerialOpError::Denied { denial, audit_seq });
2117 }
2118 let Some(backend) = self.backends.get_mut(&resource) else {
2119 let record = op_record(
2120 session,
2121 resource,
2122 "serial_write",
2123 0,
2124 Decision::Allowed,
2125 OpStatus::BackendFault,
2126 None,
2127 timestamp_ns,
2128 );
2129 let audit_seq = audit.append(record);
2130 return Err(SerialOpError::Backend { error: BackendError::Fault, audit_seq });
2131 };
2132 match backend.serial_write(data) {
2133 Ok(()) => {
2134 let record = op_record(
2135 session,
2136 resource,
2137 "serial_write",
2138 0,
2139 Decision::Allowed,
2140 OpStatus::Ok,
2141 Some(data.len() as u32),
2142 timestamp_ns,
2143 );
2144 let audit_seq = audit.append(record);
2145 Ok(SerialWriteOutcome { audit_seq, timestamp_ns })
2146 }
2147 Err(error) => {
2148 let record = op_record(
2149 session,
2150 resource,
2151 "serial_write",
2152 0,
2153 Decision::Allowed,
2154 OpStatus::BackendFault,
2155 None,
2156 timestamp_ns,
2157 );
2158 let audit_seq = audit.append(record);
2159 Err(SerialOpError::Backend { error, audit_seq })
2160 }
2161 }
2162 }
2163}
2164
2165impl<B: ResourceBackend, C: Timer> Executor<B, C> {
2166 pub async fn poll32_with_abort(
2168 &mut self,
2169 policy: &AccessPolicy,
2170 audit: &mut AuditRing,
2171 session: u64,
2172 resource: ResourceId,
2173 offset: u64,
2174 expected: u32,
2175 mask: u32,
2176 interval_ns: i64,
2177 timeout_ns: i64,
2178 abort: Option<&std::sync::atomic::AtomicBool>,
2179 ) -> Result<PollOutcome, PollError> {
2180 self.poll32_internal(
2181 policy,
2182 audit,
2183 session,
2184 resource,
2185 offset,
2186 expected,
2187 mask,
2188 interval_ns,
2189 timeout_ns,
2190 None,
2191 abort,
2192 )
2193 .await
2194 }
2195
2196 pub async fn poll32(
2198 &mut self,
2199 policy: &AccessPolicy,
2200 audit: &mut AuditRing,
2201 session: u64,
2202 resource: ResourceId,
2203 offset: u64,
2204 expected: u32,
2205 mask: u32,
2206 interval_ns: i64,
2207 timeout_ns: i64,
2208 ) -> Result<PollOutcome, PollError> {
2209 self.poll32_with_abort(
2210 policy,
2211 audit,
2212 session,
2213 resource,
2214 offset,
2215 expected,
2216 mask,
2217 interval_ns,
2218 timeout_ns,
2219 None,
2220 )
2221 .await
2222 }
2223
2224 pub async fn poll32_internal(
2226 &mut self,
2227 policy: &AccessPolicy,
2228 audit: &mut AuditRing,
2229 session: u64,
2230 resource: ResourceId,
2231 offset: u64,
2232 expected: u32,
2233 mask: u32,
2234 interval_ns: i64,
2235 timeout_ns: i64,
2236 item_index: Option<u32>,
2237 abort: Option<&std::sync::atomic::AtomicBool>,
2238 ) -> Result<PollOutcome, PollError> {
2239 let timestamp_ns = self.clock.now_ns();
2240 if let Some(token) = abort {
2241 if token.load(std::sync::atomic::Ordering::Relaxed) {
2242 let mut record = op_record(
2243 session,
2244 resource,
2245 "poll32",
2246 offset,
2247 Decision::Denied(Denial::NotAccepting),
2248 OpStatus::Rejected,
2249 None,
2250 timestamp_ns,
2251 );
2252 record.item_index = item_index;
2253 let audit_seq = audit.append(record);
2254 return Err(PollError::Denied { denial: Denial::NotAccepting, audit_seq });
2255 }
2256 }
2257 if let Err(denial) = policy.check_poll32(resource, offset, mask, interval_ns, timeout_ns) {
2258 let mut record = op_record(
2259 session,
2260 resource,
2261 "poll32",
2262 offset,
2263 Decision::Denied(denial),
2264 OpStatus::Rejected,
2265 None,
2266 timestamp_ns,
2267 );
2268 record.item_index = item_index;
2269 let audit_seq = audit.append(record);
2270 return Err(PollError::Denied { denial, audit_seq });
2271 }
2272 if timeout_ns > self.limits.max_delay_ns {
2273 let mut record = op_record(
2274 session,
2275 resource,
2276 "poll32",
2277 offset,
2278 Decision::Denied(Denial::LimitExceeded),
2279 OpStatus::Rejected,
2280 None,
2281 timestamp_ns,
2282 );
2283 record.item_index = item_index;
2284 let audit_seq = audit.append(record);
2285 return Err(PollError::Denied { denial: Denial::LimitExceeded, audit_seq });
2286 }
2287
2288 let start_ns = self.clock.now_ns();
2289 let deadline_ns = start_ns.saturating_add(timeout_ns);
2290 let mut last_val: Option<u32> = None;
2291 loop {
2292 if let Some(token) = abort {
2293 if token.load(std::sync::atomic::Ordering::Relaxed) {
2294 let mut record = op_record(
2295 session,
2296 resource,
2297 "poll32",
2298 offset,
2299 Decision::Denied(Denial::NotAccepting),
2300 OpStatus::Rejected,
2301 last_val,
2302 self.clock.now_ns(),
2303 );
2304 record.item_index = item_index;
2305 let audit_seq = audit.append(record);
2306 return Err(PollError::Denied { denial: Denial::NotAccepting, audit_seq });
2307 }
2308 }
2309 match self
2310 .poll32_read_step(audit, session, resource, offset, expected, mask, item_index)?
2311 {
2312 Ok(outcome) => return Ok(outcome),
2313 Err(val) => {
2314 last_val = Some(val);
2315 let now = self.clock.now_ns();
2316 if now >= deadline_ns {
2317 return Err(self.poll32_timeout(
2318 audit, session, resource, offset, last_val, item_index,
2319 ));
2320 }
2321 let sleep_ns = interval_ns.min(deadline_ns - now);
2322 if sleep_ns > 0 {
2323 self.clock.sleep(sleep_ns).await;
2324 }
2325 if let Some(token) = abort {
2326 if token.load(std::sync::atomic::Ordering::Relaxed) {
2327 let mut record = op_record(
2328 session,
2329 resource,
2330 "poll32",
2331 offset,
2332 Decision::Denied(Denial::NotAccepting),
2333 OpStatus::Rejected,
2334 last_val,
2335 self.clock.now_ns(),
2336 );
2337 record.item_index = item_index;
2338 let audit_seq = audit.append(record);
2339 return Err(PollError::Denied {
2340 denial: Denial::NotAccepting,
2341 audit_seq,
2342 });
2343 }
2344 }
2345 if self.clock.now_ns() >= deadline_ns {
2346 match self.poll32_read_step(
2347 audit, session, resource, offset, expected, mask, item_index,
2348 )? {
2349 Ok(outcome) => return Ok(outcome),
2350 Err(final_val) => {
2351 return Err(self.poll32_timeout(
2352 audit,
2353 session,
2354 resource,
2355 offset,
2356 Some(final_val),
2357 item_index,
2358 ));
2359 }
2360 }
2361 }
2362 }
2363 }
2364 }
2365 }
2366
2367 pub async fn execute_sequence(
2369 &mut self,
2370 policy: &AccessPolicy,
2371 audit: &mut AuditRing,
2372 session: u64,
2373 items: &[SequenceItem],
2374 ) -> Result<SequenceOutcome, SequenceError> {
2375 self.execute_sequence_with_abort(policy, audit, session, items, None).await
2376 }
2377
2378 pub async fn execute_sequence_with_abort(
2380 &mut self,
2381 policy: &AccessPolicy,
2382 audit: &mut AuditRing,
2383 session: u64,
2384 items: &[SequenceItem],
2385 abort: Option<&std::sync::atomic::AtomicBool>,
2386 ) -> Result<SequenceOutcome, SequenceError> {
2387 if items.len() > self.limits.max_sequence_items {
2388 let timestamp_ns = self.clock.now_ns();
2389 let record = AuditRecord {
2390 session: Some(session),
2391 resource: None,
2392 operation: "sequence",
2393 offset: None,
2394 decision: Decision::Denied(Denial::LimitExceeded),
2395 status: OpStatus::Rejected,
2396 value: None,
2397 timestamp_ns,
2398 run_id: None,
2399 item_index: None,
2400 };
2401 let audit_seq = audit.append(record);
2402 return Err(SequenceError::TooManyItems {
2403 max: self.limits.max_sequence_items,
2404 audit_seq,
2405 });
2406 }
2407 if let Err((index, denial)) = self.prevalidate_sequence(policy, items) {
2408 let timestamp_ns = self.clock.now_ns();
2409 let record = AuditRecord {
2410 session: Some(session),
2411 resource: None,
2412 operation: "sequence",
2413 offset: None,
2414 decision: Decision::Denied(denial),
2415 status: OpStatus::Rejected,
2416 value: None,
2417 timestamp_ns,
2418 run_id: None,
2419 item_index: Some(index as u32),
2420 };
2421 let audit_seq = audit.append(record);
2422 return Err(SequenceError::Rejected { index, denial, audit_seq });
2423 }
2424 if let Some(token) = abort {
2425 if token.load(std::sync::atomic::Ordering::Relaxed) {
2426 let timestamp_ns = self.clock.now_ns();
2427 let record = AuditRecord {
2428 session: Some(session),
2429 resource: None,
2430 operation: "sequence",
2431 offset: None,
2432 decision: Decision::Denied(Denial::NotAccepting),
2433 status: OpStatus::Rejected,
2434 value: None,
2435 timestamp_ns,
2436 run_id: None,
2437 item_index: None,
2438 };
2439 let audit_seq = audit.append(record);
2440 return Err(SequenceError::Rejected {
2441 index: 0,
2442 denial: Denial::NotAccepting,
2443 audit_seq,
2444 });
2445 }
2446 }
2447
2448 let mut results = Vec::with_capacity(items.len());
2449 let mut complete = true;
2450 let start_ts = self.clock.now_ns();
2451
2452 for (index, item) in items.iter().enumerate() {
2453 let idx = index as u32;
2454 let now = self.clock.now_ns();
2455
2456 if let Some(token) = abort {
2457 if token.load(std::sync::atomic::Ordering::Relaxed) {
2458 let record = AuditRecord {
2459 session: Some(session),
2460 resource: None,
2461 operation: "sequence",
2462 offset: None,
2463 decision: Decision::Denied(Denial::NotAccepting),
2464 status: OpStatus::Rejected,
2465 value: None,
2466 timestamp_ns: now,
2467 run_id: None,
2468 item_index: Some(idx),
2469 };
2470 audit.append(record);
2471 results.push(SequenceItemResult {
2472 index: idx,
2473 ok: false,
2474 outcome: SequenceItemOutcome::Error(Denial::NotAccepting),
2475 });
2476 complete = false;
2477 break;
2478 }
2479 }
2480
2481 if now.saturating_sub(start_ts) > self.limits.max_sequence_duration_ns {
2482 let record = AuditRecord {
2483 session: Some(session),
2484 resource: None,
2485 operation: "sequence",
2486 offset: None,
2487 decision: Decision::Denied(Denial::Timeout),
2488 status: OpStatus::Rejected,
2489 value: None,
2490 timestamp_ns: now,
2491 run_id: None,
2492 item_index: Some(idx),
2493 };
2494 audit.append(record);
2495 results.push(SequenceItemResult {
2496 index: idx,
2497 ok: false,
2498 outcome: SequenceItemOutcome::Error(Denial::Timeout),
2499 });
2500 complete = false;
2501 break;
2502 }
2503
2504 match item {
2505 SequenceItem::Read32 { resource, offset } => {
2506 match self.read32_internal(
2507 policy,
2508 audit,
2509 session,
2510 *resource,
2511 *offset,
2512 Some(idx),
2513 ) {
2514 Ok(outcome) => {
2515 results.push(SequenceItemResult {
2516 index: idx,
2517 ok: true,
2518 outcome: SequenceItemOutcome::Read32(outcome),
2519 });
2520 }
2521 Err(ReadError::Denied { denial, .. }) => {
2522 results.push(SequenceItemResult {
2523 index: idx,
2524 ok: false,
2525 outcome: SequenceItemOutcome::Error(denial),
2526 });
2527 complete = false;
2528 break;
2529 }
2530 Err(ReadError::Backend { error, .. }) => {
2531 results.push(SequenceItemResult {
2532 index: idx,
2533 ok: false,
2534 outcome: SequenceItemOutcome::Backend(error),
2535 });
2536 complete = false;
2537 break;
2538 }
2539 }
2540 }
2541 SequenceItem::Write32 {
2542 resource,
2543 offset,
2544 value,
2545 write_mask,
2546 precondition,
2547 readback,
2548 } => {
2549 match self.write32_internal(
2550 policy,
2551 audit,
2552 session,
2553 *resource,
2554 *offset,
2555 *value,
2556 *write_mask,
2557 *precondition,
2558 *readback,
2559 Some(idx),
2560 ) {
2561 Ok(outcome) => {
2562 results.push(SequenceItemResult {
2563 index: idx,
2564 ok: true,
2565 outcome: SequenceItemOutcome::Write32(outcome),
2566 });
2567 }
2568 Err(WriteError::Denied { denial, .. }) => {
2569 results.push(SequenceItemResult {
2570 index: idx,
2571 ok: false,
2572 outcome: SequenceItemOutcome::Error(denial),
2573 });
2574 complete = false;
2575 break;
2576 }
2577 Err(WriteError::Backend { error, .. }) => {
2578 results.push(SequenceItemResult {
2579 index: idx,
2580 ok: false,
2581 outcome: SequenceItemOutcome::Backend(error),
2582 });
2583 complete = false;
2584 break;
2585 }
2586 }
2587 }
2588 SequenceItem::Poll32 {
2589 resource,
2590 offset,
2591 expected,
2592 mask,
2593 interval_ns,
2594 timeout_ns,
2595 } => {
2596 match self
2597 .poll32_internal(
2598 policy,
2599 audit,
2600 session,
2601 *resource,
2602 *offset,
2603 *expected,
2604 *mask,
2605 *interval_ns,
2606 *timeout_ns,
2607 Some(idx),
2608 abort,
2609 )
2610 .await
2611 {
2612 Ok(outcome) => {
2613 results.push(SequenceItemResult {
2614 index: idx,
2615 ok: true,
2616 outcome: SequenceItemOutcome::Poll32(outcome),
2617 });
2618 }
2619 Err(PollError::Denied { denial, .. }) => {
2620 results.push(SequenceItemResult {
2621 index: idx,
2622 ok: false,
2623 outcome: SequenceItemOutcome::Error(denial),
2624 });
2625 complete = false;
2626 break;
2627 }
2628 Err(PollError::Backend { error, .. }) => {
2629 results.push(SequenceItemResult {
2630 index: idx,
2631 ok: false,
2632 outcome: SequenceItemOutcome::Backend(error),
2633 });
2634 complete = false;
2635 break;
2636 }
2637 }
2638 }
2639 SequenceItem::DelayNs(delay_ns) => {
2640 let ts = self.clock.now_ns();
2641 if *delay_ns > 0 {
2642 self.clock.sleep(*delay_ns).await;
2643 }
2644 let record = AuditRecord {
2645 session: Some(session),
2646 resource: None,
2647 operation: "delay",
2648 offset: None,
2649 decision: Decision::Allowed,
2650 status: OpStatus::Ok,
2651 value: None,
2652 timestamp_ns: ts,
2653 run_id: None,
2654 item_index: Some(idx),
2655 };
2656 audit.append(record);
2657 results.push(SequenceItemResult {
2658 index: idx,
2659 ok: true,
2660 outcome: SequenceItemOutcome::DelayNs,
2661 });
2662 }
2663 SequenceItem::Barrier => {
2664 let ts = self.clock.now_ns();
2665 self.barrier();
2666 let record = AuditRecord {
2667 session: Some(session),
2668 resource: None,
2669 operation: "barrier",
2670 offset: None,
2671 decision: Decision::Allowed,
2672 status: OpStatus::Ok,
2673 value: None,
2674 timestamp_ns: ts,
2675 run_id: None,
2676 item_index: Some(idx),
2677 };
2678 audit.append(record);
2679 results.push(SequenceItemResult {
2680 index: idx,
2681 ok: true,
2682 outcome: SequenceItemOutcome::Barrier,
2683 });
2684 }
2685 SequenceItem::GpioRead { resource } => {
2686 match self.gpio_read_internal(policy, audit, session, *resource, Some(idx)) {
2687 Ok(outcome) => {
2688 results.push(SequenceItemResult {
2689 index: idx,
2690 ok: true,
2691 outcome: SequenceItemOutcome::GpioRead(outcome),
2692 });
2693 }
2694 Err(GpioReadError::Denied { denial, .. }) => {
2695 results.push(SequenceItemResult {
2696 index: idx,
2697 ok: false,
2698 outcome: SequenceItemOutcome::Error(denial),
2699 });
2700 complete = false;
2701 break;
2702 }
2703 Err(GpioReadError::Backend { error, .. }) => {
2704 results.push(SequenceItemResult {
2705 index: idx,
2706 ok: false,
2707 outcome: SequenceItemOutcome::Backend(error),
2708 });
2709 complete = false;
2710 break;
2711 }
2712 }
2713 }
2714 SequenceItem::GpioWrite { resource, value } => {
2715 match self.gpio_write_internal(
2716 policy,
2717 audit,
2718 session,
2719 *resource,
2720 *value,
2721 Some(idx),
2722 ) {
2723 Ok(outcome) => {
2724 results.push(SequenceItemResult {
2725 index: idx,
2726 ok: true,
2727 outcome: SequenceItemOutcome::GpioWrite(outcome),
2728 });
2729 }
2730 Err(GpioWriteError::Denied { denial, .. }) => {
2731 results.push(SequenceItemResult {
2732 index: idx,
2733 ok: false,
2734 outcome: SequenceItemOutcome::Error(denial),
2735 });
2736 complete = false;
2737 break;
2738 }
2739 Err(GpioWriteError::Backend { error, .. }) => {
2740 results.push(SequenceItemResult {
2741 index: idx,
2742 ok: false,
2743 outcome: SequenceItemOutcome::Backend(error),
2744 });
2745 complete = false;
2746 break;
2747 }
2748 }
2749 }
2750 SequenceItem::I2cTransfer { resource, write_data, read_length } => {
2751 match self.i2c_transfer_internal(
2752 policy,
2753 audit,
2754 session,
2755 *resource,
2756 write_data,
2757 *read_length,
2758 Some(idx),
2759 ) {
2760 Ok(outcome) => {
2761 results.push(SequenceItemResult {
2762 index: idx,
2763 ok: true,
2764 outcome: SequenceItemOutcome::I2cTransfer(outcome),
2765 });
2766 }
2767 Err(I2cTransferError::Denied { denial, .. }) => {
2768 results.push(SequenceItemResult {
2769 index: idx,
2770 ok: false,
2771 outcome: SequenceItemOutcome::Error(denial),
2772 });
2773 complete = false;
2774 break;
2775 }
2776 Err(I2cTransferError::Backend { error, .. }) => {
2777 results.push(SequenceItemResult {
2778 index: idx,
2779 ok: false,
2780 outcome: SequenceItemOutcome::Backend(error),
2781 });
2782 complete = false;
2783 break;
2784 }
2785 }
2786 }
2787 SequenceItem::SpiTransmit { resource, tx_data } => {
2788 match self.spi_transmit_internal(
2789 policy,
2790 audit,
2791 session,
2792 *resource,
2793 tx_data,
2794 Some(idx),
2795 ) {
2796 Ok(outcome) => {
2797 results.push(SequenceItemResult {
2798 index: idx,
2799 ok: true,
2800 outcome: SequenceItemOutcome::SpiTransmit(outcome),
2801 });
2802 }
2803 Err(SpiTransmitError::Denied { denial, .. }) => {
2804 results.push(SequenceItemResult {
2805 index: idx,
2806 ok: false,
2807 outcome: SequenceItemOutcome::Error(denial),
2808 });
2809 complete = false;
2810 break;
2811 }
2812 Err(SpiTransmitError::Backend { error, .. }) => {
2813 results.push(SequenceItemResult {
2814 index: idx,
2815 ok: false,
2816 outcome: SequenceItemOutcome::Backend(error),
2817 });
2818 complete = false;
2819 break;
2820 }
2821 }
2822 }
2823 }
2824 }
2825 Ok(SequenceOutcome { results, complete })
2826 }
2827}
2828
2829fn op_record(
2830 session: u64,
2831 resource: ResourceId,
2832 operation: &'static str,
2833 offset: u64,
2834 decision: Decision,
2835 status: OpStatus,
2836 value: Option<u32>,
2837 timestamp_ns: i64,
2838) -> AuditRecord {
2839 AuditRecord {
2840 session: Some(session),
2841 resource: Some(resource),
2842 operation,
2843 offset: Some(offset),
2844 decision,
2845 status,
2846 value,
2847 timestamp_ns,
2848 run_id: None,
2849 item_index: None,
2850 }
2851}
2852
2853#[cfg(test)]
2854mod tests {
2855 use super::*;
2856 use crate::access_policy::{
2857 AccessRule, MmioResource, ResourceCeiling, SessionMode, WIDTH32, WritableRegister,
2858 };
2859 use crate::hardware_backend::{FakeClock, FakeMmio};
2860
2861 const CTRL: ResourceId = 1;
2862 const SESSION: u64 = 7;
2863
2864 fn rule(offset: u64, class: AccessClass) -> AccessRule {
2865 AccessRule { resource: CTRL, offset, width: WIDTH32, class }
2866 }
2867
2868 fn make_executor(
2869 rules: &[AccessRule],
2870 ) -> (Executor<FakeMmio, FakeClock>, AccessPolicy, AuditRing) {
2871 let resources =
2872 BTreeMap::from([(CTRL, MmioResource::mmio("ctrl".to_string(), 0x100, 0x100))]);
2873 let ceiling = BTreeMap::from([(
2874 CTRL,
2875 ResourceCeiling {
2876 hard_denied: vec![],
2877 allow_unknown_reads: true,
2878 allow_poll: false,
2879 writable_registers: vec![],
2880 protocol: None,
2881 allow_interrupt: false,
2882 },
2883 )]);
2884 let policy =
2885 AccessPolicy::new(SessionMode::ReadOnly, resources, ceiling, rules.iter().copied())
2886 .unwrap();
2887 let mut mmio = FakeMmio::new();
2888 mmio.set(0x3c, 0xdead_beef);
2889 mmio.set(0x40, 0x1234_5678);
2890 mmio.fail_at(0x44);
2891 let backends = BTreeMap::from([(CTRL, mmio)]);
2892 let clock = FakeClock::new(1000, 10);
2893 let executor = Executor::new(
2894 backends,
2895 clock,
2896 ExecLimits {
2897 max_snapshot_items: 4,
2898 max_sequence_items: 4,
2899 max_delay_ns: 5_000_000_000,
2900 max_sequence_duration_ns: 1_000_000_000,
2901 },
2902 );
2903 (executor, policy, AuditRing::new(32))
2904 }
2905
2906 fn make_mutating_executor(
2907 writable_registers: Vec<WritableRegister>,
2908 rules: &[AccessRule],
2909 ) -> (Executor<FakeMmio, FakeClock>, AccessPolicy, AuditRing) {
2910 let resources =
2911 BTreeMap::from([(CTRL, MmioResource::mmio("ctrl".to_string(), 0x100, 0x100))]);
2912 let ceiling = BTreeMap::from([(
2913 CTRL,
2914 ResourceCeiling {
2915 hard_denied: vec![],
2916 allow_unknown_reads: true,
2917 allow_poll: true,
2918 writable_registers,
2919 protocol: None,
2920 allow_interrupt: false,
2921 },
2922 )]);
2923 let policy =
2924 AccessPolicy::new(SessionMode::Mutating, resources, ceiling, rules.iter().copied())
2925 .unwrap();
2926 let mut mmio = FakeMmio::new();
2927 mmio.set(0x3c, 0xdead_beef);
2928 mmio.set(0x40, 0x1234_5678);
2929 mmio.fail_at(0x44);
2930 let backends = BTreeMap::from([(CTRL, mmio)]);
2931 let clock = FakeClock::new(1000, 10);
2932 let executor = Executor::new(
2933 backends,
2934 clock,
2935 ExecLimits {
2936 max_snapshot_items: 4,
2937 max_sequence_items: 8,
2938 max_delay_ns: 5_000_000_000,
2939 max_sequence_duration_ns: 1_000_000_000,
2940 },
2941 );
2942 (executor, policy, AuditRing::new(32))
2943 }
2944
2945 fn accesses(executor: &Executor<FakeMmio, FakeClock>) -> &[u64] {
2946 &executor.backend(CTRL).unwrap().accesses
2947 }
2948
2949 fn write_accesses(executor: &Executor<FakeMmio, FakeClock>) -> &[(u64, u32)] {
2950 &executor.backend(CTRL).unwrap().write_accesses
2951 }
2952
2953 fn barriers(executor: &Executor<FakeMmio, FakeClock>) -> usize {
2954 executor.backend(CTRL).unwrap().barriers
2955 }
2956
2957 #[test]
2958 fn read_allowed_returns_value_and_audits() {
2959 let (mut executor, policy, mut audit) = make_executor(&[rule(0x3c, AccessClass::ReadOnce)]);
2960 let outcome = executor.read32(&policy, &mut audit, SESSION, CTRL, 0x3c).unwrap();
2961 assert_eq!(outcome.value, 0xdead_beef);
2962 assert_eq!(outcome.timestamp_ns, 1000);
2963 assert_eq!(accesses(&executor), &[0x3c]);
2964
2965 let entry = &audit.read(outcome.audit_seq, 1).entries[0];
2966 assert_eq!(entry.record.decision, Decision::Allowed);
2967 assert_eq!(entry.record.status, OpStatus::Ok);
2968 assert_eq!(entry.record.value, Some(0xdead_beef));
2969 assert_eq!(entry.record.session, Some(SESSION));
2970 }
2971
2972 #[test]
2973 fn denied_read_touches_no_hardware_but_is_audited() {
2974 let (mut executor, policy, mut audit) = make_executor(&[rule(0x3c, AccessClass::ReadOnce)]);
2975 let error = executor.read32(&policy, &mut audit, SESSION, CTRL, 0x40).unwrap_err();
2976 let ReadError::Denied { denial, audit_seq } = error else {
2977 panic!("expected denial, got {error:?}");
2978 };
2979 assert_eq!(denial, Denial::NotInAllowlist);
2980 assert!(accesses(&executor).is_empty());
2981
2982 let entry = &audit.read(audit_seq, 1).entries[0];
2983 assert_eq!(entry.record.decision, Decision::Denied(Denial::NotInAllowlist));
2984 assert_eq!(entry.record.status, OpStatus::Rejected);
2985 assert_eq!(entry.record.value, None);
2986 }
2987
2988 #[test]
2989 fn read_backend_fault_is_audited() {
2990 let (mut executor, policy, mut audit) = make_executor(&[rule(0x44, AccessClass::ReadOnce)]);
2991 let error = executor.read32(&policy, &mut audit, SESSION, CTRL, 0x44).unwrap_err();
2992 let ReadError::Backend { error, audit_seq } = error else {
2993 panic!("expected backend error, got {error:?}");
2994 };
2995 assert_eq!(error, BackendError::Fault);
2996
2997 let entry = &audit.read(audit_seq, 1).entries[0];
2998 assert_eq!(entry.record.decision, Decision::Allowed);
2999 assert_eq!(entry.record.status, OpStatus::BackendFault);
3000 }
3001
3002 #[test]
3003 fn snapshot_reads_in_order_with_distinct_timestamps() {
3004 let (mut executor, policy, mut audit) =
3005 make_executor(&[rule(0x3c, AccessClass::Snapshot), rule(0x40, AccessClass::Snapshot)]);
3006 let items = [
3007 SnapshotItem { resource: CTRL, offset: 0x3c },
3008 SnapshotItem { resource: CTRL, offset: 0x40 },
3009 ];
3010 let outcome = executor.snapshot(&policy, &mut audit, SESSION, &items).unwrap();
3011 assert!(outcome.complete);
3012 assert_eq!(outcome.results.len(), 2);
3013 assert_eq!(outcome.results[0].value, Ok(0xdead_beef));
3014 assert_eq!(outcome.results[1].value, Ok(0x1234_5678));
3015 assert!(outcome.results[0].timestamp_ns < outcome.results[1].timestamp_ns);
3016 assert!(outcome.results[0].audit_seq < outcome.results[1].audit_seq);
3017 assert_eq!(accesses(&executor), &[0x3c, 0x40]);
3018 }
3019
3020 #[test]
3021 fn snapshot_prevalidation_blocks_every_read() {
3022 let (mut executor, policy, mut audit) = make_executor(&[rule(0x3c, AccessClass::Snapshot)]);
3023 let items = [
3024 SnapshotItem { resource: CTRL, offset: 0x3c },
3025 SnapshotItem { resource: CTRL, offset: 0x48 },
3026 ];
3027 let error = executor.snapshot(&policy, &mut audit, SESSION, &items).unwrap_err();
3028 let SnapshotError::Rejected { index, denial, .. } = error else {
3029 panic!("expected rejection, got {error:?}");
3030 };
3031 assert_eq!(index, 1);
3032 assert_eq!(denial, Denial::NotInAllowlist);
3033 assert!(accesses(&executor).is_empty());
3034 }
3035
3036 #[test]
3037 fn read_once_grant_does_not_authorize_snapshot() {
3038 let (mut executor, policy, mut audit) = make_executor(&[rule(0x3c, AccessClass::ReadOnce)]);
3039 let items = [SnapshotItem { resource: CTRL, offset: 0x3c }];
3040 let error = executor.snapshot(&policy, &mut audit, SESSION, &items).unwrap_err();
3041 let SnapshotError::Rejected { index, denial, .. } = error else {
3042 panic!("expected rejection, got {error:?}");
3043 };
3044 assert_eq!(index, 0);
3045 assert_eq!(denial, Denial::NotInAllowlist);
3046 assert!(accesses(&executor).is_empty());
3047 }
3048
3049 #[test]
3050 fn snapshot_stops_at_first_backend_fault() {
3051 let (mut executor, policy, mut audit) = make_executor(&[
3052 rule(0x3c, AccessClass::Snapshot),
3053 rule(0x44, AccessClass::Snapshot),
3054 rule(0x40, AccessClass::Snapshot),
3055 ]);
3056 let items = [
3057 SnapshotItem { resource: CTRL, offset: 0x3c },
3058 SnapshotItem { resource: CTRL, offset: 0x44 },
3059 SnapshotItem { resource: CTRL, offset: 0x40 },
3060 ];
3061 let outcome = executor.snapshot(&policy, &mut audit, SESSION, &items).unwrap();
3062 assert!(!outcome.complete);
3063 assert_eq!(outcome.results.len(), 2);
3064 assert_eq!(outcome.results[0].value, Ok(0xdead_beef));
3065 assert_eq!(outcome.results[1].value, Err(BackendError::Fault));
3066 assert_eq!(accesses(&executor), &[0x3c, 0x44]);
3068
3069 let entry = &audit.read(outcome.results[1].audit_seq, 1).entries[0];
3070 assert_eq!(entry.record.status, OpStatus::BackendFault);
3071 }
3072
3073 #[test]
3074 fn snapshot_item_limit_enforced() {
3075 let (mut executor, policy, mut audit) = make_executor(&[rule(0x3c, AccessClass::Snapshot)]);
3076 let items = [SnapshotItem { resource: CTRL, offset: 0x3c }; 5];
3077 let error = executor.snapshot(&policy, &mut audit, SESSION, &items).unwrap_err();
3078 let SnapshotError::TooManyItems { max, audit_seq } = error else {
3079 panic!("expected limit rejection, got {error:?}");
3080 };
3081 assert_eq!(max, 4);
3082 assert!(accesses(&executor).is_empty());
3083
3084 let entry = &audit.read(audit_seq, 1).entries[0];
3085 assert_eq!(entry.record.decision, Decision::Denied(Denial::LimitExceeded));
3086 }
3087
3088 #[test]
3089 fn missing_backend_is_an_audited_fault() {
3090 let resources =
3091 BTreeMap::from([(CTRL, MmioResource::mmio("ctrl".to_string(), 0x100, 0x100))]);
3092 let ceiling = BTreeMap::from([(
3093 CTRL,
3094 ResourceCeiling {
3095 hard_denied: vec![],
3096 allow_unknown_reads: true,
3097 allow_poll: false,
3098 writable_registers: vec![],
3099 protocol: None,
3100 allow_interrupt: false,
3101 },
3102 )]);
3103 let policy = AccessPolicy::new(
3104 SessionMode::ReadOnly,
3105 resources,
3106 ceiling,
3107 [rule(0x3c, AccessClass::ReadOnce)],
3108 )
3109 .unwrap();
3110 let clock = FakeClock::new(0, 1);
3111 let mut executor = Executor::<FakeMmio, _>::new(
3112 BTreeMap::new(),
3113 clock,
3114 ExecLimits {
3115 max_snapshot_items: 4,
3116 max_sequence_items: 4,
3117 max_delay_ns: 5_000_000_000,
3118 max_sequence_duration_ns: 1_000_000_000,
3119 },
3120 );
3121 let mut audit = AuditRing::new(4);
3122 let error = executor.read32(&policy, &mut audit, SESSION, CTRL, 0x3c).unwrap_err();
3123 let ReadError::Backend { error, audit_seq } = error else {
3124 panic!("expected backend error, got {error:?}");
3125 };
3126 assert_eq!(error, BackendError::Fault);
3127 let entry = &audit.read(audit_seq, 1).entries[0];
3128 assert_eq!(entry.record.status, OpStatus::BackendFault);
3129 }
3130
3131 #[test]
3132 fn write_full_mask_writes_directly_and_applies_barrier() {
3133 let reg = WritableRegister {
3134 offset: 0x40,
3135 width: WIDTH32,
3136 allow_mask: 0xFFFF_FFFF,
3137 allow_rmw: false,
3138 require_precondition: false,
3139 precondition_mask: 0,
3140 readback: false,
3141 };
3142 let (mut executor, policy, mut audit) =
3143 make_mutating_executor(vec![reg], &[rule(0x40, AccessClass::Write)]);
3144 let outcome = executor
3145 .write32(
3146 &policy,
3147 &mut audit,
3148 SESSION,
3149 CTRL,
3150 0x40,
3151 0xcafe_babe,
3152 0xFFFF_FFFF,
3153 None,
3154 false,
3155 )
3156 .unwrap();
3157 assert_eq!(outcome.readback_value, 0);
3158 assert_eq!(write_accesses(&executor), &[(0x40, 0xcafe_babe)]);
3159 assert_eq!(barriers(&executor), 1);
3160 assert!(accesses(&executor).is_empty()); let entry = &audit.read(outcome.audit_seq, 1).entries[0];
3163 assert_eq!(entry.record.decision, Decision::Allowed);
3164 assert_eq!(entry.record.status, OpStatus::Ok);
3165 assert_eq!(entry.record.value, Some(0xcafe_babe));
3166 }
3167
3168 #[test]
3169 fn write_rmw_reads_before_and_modifies_bits() {
3170 let reg = WritableRegister {
3171 offset: 0x40,
3172 width: WIDTH32,
3173 allow_mask: 0x00FF_0000,
3174 allow_rmw: true,
3175 require_precondition: false,
3176 precondition_mask: 0,
3177 readback: false,
3178 };
3179 let (mut executor, policy, mut audit) =
3180 make_mutating_executor(vec![reg], &[rule(0x40, AccessClass::Write)]);
3181 let outcome = executor
3183 .write32(
3184 &policy,
3185 &mut audit,
3186 SESSION,
3187 CTRL,
3188 0x40,
3189 0x00ab_0000,
3190 0x00FF_0000,
3191 None,
3192 false,
3193 )
3194 .unwrap();
3195 assert_eq!(outcome.readback_value, 0);
3196 assert_eq!(accesses(&executor), &[0x40]); assert_eq!(write_accesses(&executor), &[(0x40, 0x12ab_5678)]);
3198 assert_eq!(barriers(&executor), 1);
3199
3200 let entries = &audit.read(0, 5).entries;
3201 assert_eq!(entries.len(), 2);
3202 assert_eq!(entries[0].record.operation, "write32_before_read");
3203 assert_eq!(entries[0].record.value, Some(0x1234_5678));
3204 assert_eq!(entries[1].record.operation, "write32");
3205 assert_eq!(entries[1].record.value, Some(0x12ab_5678));
3206 }
3207
3208 #[test]
3209 fn write_precondition_satisfied_allows_write() {
3210 let reg = WritableRegister {
3211 offset: 0x40,
3212 width: WIDTH32,
3213 allow_mask: 0xFFFF_FFFF,
3214 allow_rmw: true,
3215 require_precondition: true,
3216 precondition_mask: 0xFFFF_0000,
3217 readback: false,
3218 };
3219 let pre = WritePrecondition { expected: 0x1234_0000, mask: 0xFFFF_0000 };
3220 let (mut executor, policy, mut audit) =
3221 make_mutating_executor(vec![reg], &[rule(0x40, AccessClass::Write)]);
3222 let outcome = executor
3223 .write32(
3224 &policy,
3225 &mut audit,
3226 SESSION,
3227 CTRL,
3228 0x40,
3229 0xcafe_babe,
3230 0xFFFF_FFFF,
3231 Some(pre),
3232 false,
3233 )
3234 .unwrap();
3235 assert_eq!(write_accesses(&executor), &[(0x40, 0xcafe_babe)]);
3236 assert_eq!(outcome.readback_value, 0);
3237 }
3238
3239 #[test]
3240 fn write_precondition_failed_rejects_and_prevents_write() {
3241 let reg = WritableRegister {
3242 offset: 0x40,
3243 width: WIDTH32,
3244 allow_mask: 0xFFFF_FFFF,
3245 allow_rmw: true,
3246 require_precondition: true,
3247 precondition_mask: 0xFFFF_0000,
3248 readback: false,
3249 };
3250 let pre = WritePrecondition { expected: 0x9999_0000, mask: 0xFFFF_0000 };
3251 let (mut executor, policy, mut audit) =
3252 make_mutating_executor(vec![reg], &[rule(0x40, AccessClass::Write)]);
3253 let error = executor
3254 .write32(
3255 &policy,
3256 &mut audit,
3257 SESSION,
3258 CTRL,
3259 0x40,
3260 0xcafe_babe,
3261 0xFFFF_FFFF,
3262 Some(pre),
3263 false,
3264 )
3265 .unwrap_err();
3266 let WriteError::Denied { denial, audit_seq } = error else {
3267 panic!("expected denial, got {error:?}");
3268 };
3269 assert_eq!(denial, Denial::PreconditionFailed);
3270 assert!(write_accesses(&executor).is_empty()); let entry = &audit.read(audit_seq, 1).entries[0];
3273 assert_eq!(entry.record.decision, Decision::Denied(Denial::PreconditionFailed));
3274 assert_eq!(entry.record.status, OpStatus::Rejected);
3275 assert_eq!(entry.record.value, Some(0x1234_5678)); }
3277
3278 #[test]
3279 fn write_readback_audited_and_returned() {
3280 let reg = WritableRegister {
3281 offset: 0x40,
3282 width: WIDTH32,
3283 allow_mask: 0xFFFF_FFFF,
3284 allow_rmw: false,
3285 require_precondition: false,
3286 precondition_mask: 0,
3287 readback: true,
3288 };
3289 let (mut executor, policy, mut audit) =
3290 make_mutating_executor(vec![reg], &[rule(0x40, AccessClass::Write)]);
3291 let outcome = executor
3292 .write32(
3293 &policy,
3294 &mut audit,
3295 SESSION,
3296 CTRL,
3297 0x40,
3298 0xcafe_babe,
3299 0xFFFF_FFFF,
3300 None,
3301 false,
3302 )
3303 .unwrap();
3304 assert_eq!(outcome.readback_value, 0xcafe_babe);
3305
3306 let entries = &audit.read(0, 5).entries;
3307 assert_eq!(entries.len(), 2);
3308 assert_eq!(entries[0].record.operation, "write32");
3309 assert_eq!(entries[1].record.operation, "write32_readback");
3310 assert_eq!(entries[1].record.value, Some(0xcafe_babe));
3311 }
3312
3313 #[test]
3314 fn write_denied_in_read_only_session() {
3315 let (mut executor, policy, mut audit) = make_executor(&[rule(0x40, AccessClass::ReadOnce)]);
3316 let error = executor
3317 .write32(
3318 &policy,
3319 &mut audit,
3320 SESSION,
3321 CTRL,
3322 0x40,
3323 0xcafe_babe,
3324 0xFFFF_FFFF,
3325 None,
3326 false,
3327 )
3328 .unwrap_err();
3329 let WriteError::Denied { denial, .. } = error else {
3330 panic!("expected denial, got {error:?}");
3331 };
3332 assert_eq!(denial, Denial::ReadOnlySession);
3333 assert!(write_accesses(&executor).is_empty());
3334 }
3335
3336 #[test]
3337 fn poll32_matches_immediately_without_sleep() {
3338 let (mut executor, policy, mut audit) =
3339 make_mutating_executor(vec![], &[rule(0x3c, AccessClass::Poll)]);
3340 let outcome = futures::executor::block_on(executor.poll32(
3341 &policy,
3342 &mut audit,
3343 SESSION,
3344 CTRL,
3345 0x3c,
3346 0xdead_beef,
3347 0xFFFF_FFFF,
3348 100,
3349 1000,
3350 ))
3351 .unwrap();
3352 assert_eq!(outcome.value, 0xdead_beef);
3353 assert_eq!(executor.clock.sleeps.len(), 0);
3354 assert_eq!(accesses(&executor), &[0x3c]);
3355 }
3356
3357 #[test]
3358 fn poll32_timeout_audited_and_records_sleeps() {
3359 let (mut executor, policy, mut audit) =
3360 make_mutating_executor(vec![], &[rule(0x3c, AccessClass::Poll)]);
3361 let error = futures::executor::block_on(executor.poll32(
3362 &policy,
3363 &mut audit,
3364 SESSION,
3365 CTRL,
3366 0x3c,
3367 0x9999_9999,
3368 0xFFFF_FFFF,
3369 100,
3370 300,
3371 ))
3372 .unwrap_err();
3373 let PollError::Denied { denial, audit_seq } = error else {
3374 panic!("expected timeout denial, got {error:?}");
3375 };
3376 assert_eq!(denial, Denial::Timeout);
3377 assert!(executor.clock.sleeps.len() > 0);
3378
3379 let entry = &audit.read(audit_seq, 1).entries[0];
3380 assert_eq!(entry.record.operation, "poll32");
3381 assert_eq!(entry.record.decision, Decision::Denied(Denial::Timeout));
3382 assert_eq!(entry.record.status, OpStatus::Rejected);
3383 assert_eq!(entry.record.value, Some(0xdead_beef));
3384 }
3385
3386 #[test]
3387 fn sequence_prevalidation_blocks_all_hardware_access() {
3388 let reg = WritableRegister {
3389 offset: 0x40,
3390 width: WIDTH32,
3391 allow_mask: 0xFFFF_FFFF,
3392 allow_rmw: false,
3393 require_precondition: false,
3394 precondition_mask: 0,
3395 readback: false,
3396 };
3397 let (mut executor, policy, mut audit) = make_mutating_executor(
3398 vec![reg],
3399 &[rule(0x3c, AccessClass::Sequence), rule(0x40, AccessClass::Sequence)],
3400 );
3401 let items = [
3402 SequenceItem::Read32 { resource: CTRL, offset: 0x3c },
3403 SequenceItem::Write32 {
3404 resource: CTRL,
3405 offset: 0x50, value: 123,
3407 write_mask: 0xFFFF_FFFF,
3408 precondition: None,
3409 readback: false,
3410 },
3411 ];
3412 let error = futures::executor::block_on(
3413 executor.execute_sequence(&policy, &mut audit, SESSION, &items),
3414 )
3415 .unwrap_err();
3416 let SequenceError::Rejected { index, denial, .. } = error else {
3417 panic!("expected rejection, got {error:?}");
3418 };
3419 assert_eq!(index, 1);
3420 assert_eq!(denial, Denial::WriteNotPermitted);
3421 assert!(accesses(&executor).is_empty());
3423 assert!(write_accesses(&executor).is_empty());
3424 }
3425
3426 #[test]
3427 fn sequence_full_execution_succeeds_in_order() {
3428 let reg = WritableRegister {
3429 offset: 0x40,
3430 width: WIDTH32,
3431 allow_mask: 0xFFFF_FFFF,
3432 allow_rmw: false,
3433 require_precondition: false,
3434 precondition_mask: 0,
3435 readback: false,
3436 };
3437 let (mut executor, policy, mut audit) = make_mutating_executor(
3438 vec![reg],
3439 &[rule(0x3c, AccessClass::Sequence), rule(0x40, AccessClass::Sequence)],
3440 );
3441 let items = [
3442 SequenceItem::Read32 { resource: CTRL, offset: 0x3c },
3443 SequenceItem::Write32 {
3444 resource: CTRL,
3445 offset: 0x40,
3446 value: 0x9999_8888,
3447 write_mask: 0xFFFF_FFFF,
3448 precondition: None,
3449 readback: false,
3450 },
3451 SequenceItem::DelayNs(200),
3452 SequenceItem::Barrier,
3453 SequenceItem::Poll32 {
3454 resource: CTRL,
3455 offset: 0x40,
3456 expected: 0x9999_8888,
3457 mask: 0xFFFF_FFFF,
3458 interval_ns: 50,
3459 timeout_ns: 200,
3460 },
3461 ];
3462 let outcome = futures::executor::block_on(
3463 executor.execute_sequence(&policy, &mut audit, SESSION, &items),
3464 )
3465 .unwrap();
3466 assert!(outcome.complete);
3467 assert_eq!(outcome.results.len(), 5);
3468 assert!(outcome.results.iter().all(|r| r.ok));
3469 assert_eq!(write_accesses(&executor), &[(0x40, 0x9999_8888)]);
3470 assert_eq!(barriers(&executor), 2); let entries = &audit.read(0, 10).entries;
3473 assert_eq!(entries.len(), 5);
3474 for (i, entry) in entries.iter().enumerate() {
3475 assert_eq!(entry.record.item_index, Some(i as u32));
3476 }
3477 }
3478
3479 #[test]
3480 fn sequence_stops_at_first_runtime_failure() {
3481 let reg = WritableRegister {
3482 offset: 0x40,
3483 width: WIDTH32,
3484 allow_mask: 0xFFFF_FFFF,
3485 allow_rmw: true,
3486 require_precondition: true,
3487 precondition_mask: 0xFFFF_FFFF,
3488 readback: false,
3489 };
3490 let (mut executor, policy, mut audit) = make_mutating_executor(
3491 vec![reg],
3492 &[rule(0x3c, AccessClass::Sequence), rule(0x40, AccessClass::Sequence)],
3493 );
3494 let items = [
3495 SequenceItem::Read32 { resource: CTRL, offset: 0x3c },
3496 SequenceItem::Write32 {
3497 resource: CTRL,
3498 offset: 0x40,
3499 value: 0x9999_8888,
3500 write_mask: 0xFFFF_FFFF,
3501 precondition: Some(WritePrecondition { expected: 0x0000_0000, mask: 0xFFFF_FFFF }), readback: false,
3503 },
3504 SequenceItem::Read32 { resource: CTRL, offset: 0x3c }, ];
3506 let outcome = futures::executor::block_on(
3507 executor.execute_sequence(&policy, &mut audit, SESSION, &items),
3508 )
3509 .unwrap();
3510 assert!(!outcome.complete);
3511 assert_eq!(outcome.results.len(), 2);
3512 assert!(outcome.results[0].ok);
3513 assert!(!outcome.results[1].ok);
3514 assert_eq!(accesses(&executor), &[0x3c, 0x40]); assert!(write_accesses(&executor).is_empty());
3517 }
3518
3519 #[test]
3520 fn sequence_stops_on_duration_timeout() {
3521 let (mut executor, policy, mut audit) =
3522 make_mutating_executor(vec![], &[rule(0x3c, AccessClass::Sequence)]);
3523 executor.limits.max_sequence_duration_ns = 500;
3524 let items = [
3525 SequenceItem::Read32 { resource: CTRL, offset: 0x3c },
3526 SequenceItem::DelayNs(600),
3527 SequenceItem::Read32 { resource: CTRL, offset: 0x3c },
3528 ];
3529 let outcome = futures::executor::block_on(
3530 executor.execute_sequence(&policy, &mut audit, SESSION, &items),
3531 )
3532 .unwrap();
3533 assert!(!outcome.complete);
3534 assert_eq!(outcome.results.len(), 3);
3535 assert!(outcome.results[0].ok);
3536 assert!(outcome.results[1].ok);
3537 assert!(!outcome.results[2].ok);
3538 assert_eq!(outcome.results[2].outcome, SequenceItemOutcome::Error(Denial::Timeout));
3539 }
3540
3541 #[test]
3542 fn sequence_stops_on_abort() {
3543 let (mut executor, policy, mut audit) =
3544 make_mutating_executor(vec![], &[rule(0x3c, AccessClass::Sequence)]);
3545 let abort = std::sync::atomic::AtomicBool::new(true);
3546 let items = [SequenceItem::Read32 { resource: CTRL, offset: 0x3c }];
3547 let error = futures::executor::block_on(executor.execute_sequence_with_abort(
3548 &policy,
3549 &mut audit,
3550 SESSION,
3551 &items,
3552 Some(&abort),
3553 ))
3554 .unwrap_err();
3555 let SequenceError::Rejected { denial, .. } = error else {
3556 panic!("expected rejected error, got {error:?}");
3557 };
3558 assert_eq!(denial, Denial::NotAccepting);
3559 }
3560
3561 #[test]
3562 fn poll32_aborts_when_token_set() {
3563 let (mut executor, policy, mut audit) =
3564 make_mutating_executor(vec![], &[rule(0x3c, AccessClass::Poll)]);
3565 let abort = std::sync::atomic::AtomicBool::new(true);
3566 let error = futures::executor::block_on(executor.poll32_with_abort(
3567 &policy,
3568 &mut audit,
3569 SESSION,
3570 CTRL,
3571 0x3c,
3572 0xdead_beef,
3573 0xFFFF_FFFF,
3574 100,
3575 1000,
3576 Some(&abort),
3577 ))
3578 .unwrap_err();
3579 let PollError::Denied { denial, .. } = error else {
3580 panic!("expected denial, got {error:?}");
3581 };
3582 assert_eq!(denial, Denial::NotAccepting);
3583 }
3584
3585 #[test]
3586 fn protocol_gpio_standalone_read_and_write() {
3587 use crate::access_policy::ProtocolCeiling;
3588 let gpio_id = 2;
3589 let resources = BTreeMap::from([(gpio_id, MmioResource::gpio("gpio_pin".to_string()))]);
3590 let ceiling = BTreeMap::from([(
3591 gpio_id,
3592 ResourceCeiling {
3593 hard_denied: vec![],
3594 allow_unknown_reads: true,
3595 allow_poll: false,
3596 writable_registers: vec![],
3597 protocol: Some(ProtocolCeiling::default_for(
3598 crate::access_policy::ResourceKind::Gpio,
3599 )),
3600 allow_interrupt: false,
3601 },
3602 )]);
3603 let policy_ro = AccessPolicy::new(
3604 SessionMode::ReadOnly,
3605 resources.clone(),
3606 ceiling.clone(),
3607 [AccessRule { resource: gpio_id, offset: 0, width: 0, class: AccessClass::Protocol }],
3608 )
3609 .unwrap();
3610 let policy_rw = AccessPolicy::new(
3611 SessionMode::Mutating,
3612 resources,
3613 ceiling,
3614 [AccessRule { resource: gpio_id, offset: 0, width: 0, class: AccessClass::Protocol }],
3615 )
3616 .unwrap();
3617
3618 let fake_gpio = crate::protocol_resource_adapter::FakeGpio::new(true);
3619 let backends = BTreeMap::from([(gpio_id, fake_gpio)]);
3620 let clock = FakeClock::new(1000, 10);
3621 let mut executor = Executor::new(
3622 backends,
3623 clock,
3624 ExecLimits {
3625 max_snapshot_items: 4,
3626 max_sequence_items: 4,
3627 max_delay_ns: 5_000_000_000,
3628 max_sequence_duration_ns: 1_000_000_000,
3629 },
3630 );
3631 let mut audit = AuditRing::new(32);
3632
3633 let read_outcome = executor.gpio_read(&policy_ro, &mut audit, SESSION, gpio_id).unwrap();
3635 assert_eq!(read_outcome.value, true);
3636
3637 let write_err =
3639 executor.gpio_write(&policy_ro, &mut audit, SESSION, gpio_id, false).unwrap_err();
3640 assert_eq!(
3641 write_err,
3642 GpioWriteError::Denied { denial: Denial::ReadOnlySession, audit_seq: 1 }
3643 );
3644
3645 let write_outcome =
3647 executor.gpio_write(&policy_rw, &mut audit, SESSION, gpio_id, false).unwrap();
3648 assert_eq!(write_outcome.audit_seq, 2);
3649
3650 let read_outcome2 = executor.gpio_read(&policy_rw, &mut audit, SESSION, gpio_id).unwrap();
3652 assert_eq!(read_outcome2.value, false);
3653 }
3654
3655 #[test]
3656 fn heterogeneous_sequence_executes_mmio_and_protocols() {
3657 use crate::access_policy::ProtocolCeiling;
3658 let mmio_id = 1;
3659 let gpio_id = 2;
3660 let i2c_id = 3;
3661 let spi_id = 4;
3662
3663 let resources = BTreeMap::from([
3664 (mmio_id, MmioResource::mmio("mmio".to_string(), 0x100, 0x100)),
3665 (gpio_id, MmioResource::gpio("gpio".to_string())),
3666 (i2c_id, MmioResource::i2c("i2c".to_string())),
3667 (spi_id, MmioResource::spi("spi".to_string())),
3668 ]);
3669 let ceiling = BTreeMap::from([
3670 (
3671 mmio_id,
3672 ResourceCeiling {
3673 hard_denied: vec![],
3674 allow_unknown_reads: true,
3675 allow_poll: false,
3676 writable_registers: vec![],
3677 protocol: None,
3678 allow_interrupt: false,
3679 },
3680 ),
3681 (
3682 gpio_id,
3683 ResourceCeiling {
3684 hard_denied: vec![],
3685 allow_unknown_reads: false,
3686 allow_poll: false,
3687 writable_registers: vec![],
3688 protocol: Some(ProtocolCeiling::default_for(
3689 crate::access_policy::ResourceKind::Gpio,
3690 )),
3691 allow_interrupt: false,
3692 },
3693 ),
3694 (
3695 i2c_id,
3696 ResourceCeiling {
3697 hard_denied: vec![],
3698 allow_unknown_reads: false,
3699 allow_poll: false,
3700 writable_registers: vec![],
3701 protocol: Some(ProtocolCeiling::default_for(
3702 crate::access_policy::ResourceKind::I2c,
3703 )),
3704 allow_interrupt: false,
3705 },
3706 ),
3707 (
3708 spi_id,
3709 ResourceCeiling {
3710 hard_denied: vec![],
3711 allow_unknown_reads: false,
3712 allow_poll: false,
3713 writable_registers: vec![],
3714 protocol: Some(ProtocolCeiling::default_for(
3715 crate::access_policy::ResourceKind::Spi,
3716 )),
3717 allow_interrupt: false,
3718 },
3719 ),
3720 ]);
3721 let rules = [
3722 AccessRule {
3723 resource: mmio_id,
3724 offset: 0x10,
3725 width: WIDTH32,
3726 class: AccessClass::Sequence,
3727 },
3728 AccessRule { resource: gpio_id, offset: 0, width: 0, class: AccessClass::Sequence },
3729 AccessRule { resource: i2c_id, offset: 0, width: 0, class: AccessClass::Sequence },
3730 AccessRule { resource: spi_id, offset: 0, width: 0, class: AccessClass::Sequence },
3731 ];
3732 let policy = AccessPolicy::new(SessionMode::Mutating, resources, ceiling, rules).unwrap();
3733
3734 let mut mmio = FakeMmio::new();
3735 mmio.set(0x10, 0xaabb_ccdd);
3736
3737 let gpio = crate::protocol_resource_adapter::FakeGpio::new(false);
3738
3739 let mut i2c = crate::protocol_resource_adapter::FakeI2c::new();
3740 i2c.set_read_response(vec![0x11, 0x22]);
3741
3742 let mut spi = crate::protocol_resource_adapter::FakeSpi::new();
3743 spi.set_rx_response(vec![0x33, 0x44]);
3744
3745 use crate::protocol_resource_adapter::DeviceBackend;
3746 let backends: BTreeMap<
3747 ResourceId,
3748 DeviceBackend<
3749 FakeMmio,
3750 crate::protocol_resource_adapter::FakeGpio,
3751 crate::protocol_resource_adapter::FakeI2c,
3752 crate::protocol_resource_adapter::FakeSpi,
3753 >,
3754 > = BTreeMap::from([
3755 (mmio_id, DeviceBackend::new_mmio(mmio)),
3756 (gpio_id, DeviceBackend::new_gpio(gpio)),
3757 (i2c_id, DeviceBackend::new_i2c(i2c)),
3758 (spi_id, DeviceBackend::new_spi(spi)),
3759 ]);
3760
3761 let clock = FakeClock::new(1000, 10);
3762 let mut executor = Executor::new(
3763 backends,
3764 clock,
3765 ExecLimits {
3766 max_snapshot_items: 4,
3767 max_sequence_items: 10,
3768 max_delay_ns: 5_000_000_000,
3769 max_sequence_duration_ns: 1_000_000_000,
3770 },
3771 );
3772 let mut audit = AuditRing::new(32);
3773
3774 let sequence = [
3775 SequenceItem::Read32 { resource: mmio_id, offset: 0x10 },
3776 SequenceItem::GpioWrite { resource: gpio_id, value: true },
3777 SequenceItem::DelayNs(100),
3778 SequenceItem::I2cTransfer { resource: i2c_id, write_data: vec![0x55], read_length: 2 },
3779 SequenceItem::SpiTransmit { resource: spi_id, tx_data: vec![0xaa, 0xbb] },
3780 SequenceItem::GpioRead { resource: gpio_id },
3781 SequenceItem::Barrier,
3782 ];
3783
3784 let outcome = futures::executor::block_on(
3785 executor.execute_sequence(&policy, &mut audit, SESSION, &sequence),
3786 )
3787 .unwrap();
3788
3789 assert!(outcome.complete);
3790 assert_eq!(outcome.results.len(), 7);
3791 assert!(outcome.results.iter().all(|r| r.ok));
3792
3793 match &outcome.results[0].outcome {
3794 SequenceItemOutcome::Read32(r) => assert_eq!(r.value, 0xaabb_ccdd),
3795 other => panic!("expected Read32, got {other:?}"),
3796 }
3797 assert!(matches!(outcome.results[1].outcome, SequenceItemOutcome::GpioWrite(_)));
3798 assert_eq!(outcome.results[2].outcome, SequenceItemOutcome::DelayNs);
3799 match &outcome.results[3].outcome {
3800 SequenceItemOutcome::I2cTransfer(t) => assert_eq!(t.read_data, vec![0x11, 0x22]),
3801 other => panic!("expected I2cTransfer, got {other:?}"),
3802 }
3803 match &outcome.results[4].outcome {
3804 SequenceItemOutcome::SpiTransmit(t) => assert_eq!(t.rx_data, vec![0x33, 0x44]),
3805 other => panic!("expected SpiTransmit, got {other:?}"),
3806 }
3807 match &outcome.results[5].outcome {
3808 SequenceItemOutcome::GpioRead(r) => assert_eq!(r.value, true),
3809 other => panic!("expected GpioRead, got {other:?}"),
3810 }
3811 assert_eq!(outcome.results[6].outcome, SequenceItemOutcome::Barrier);
3812
3813 let entries = &audit.read(0, 10).entries;
3814 assert_eq!(entries.len(), 7);
3815 for (i, entry) in entries.iter().enumerate() {
3816 assert_eq!(entry.record.item_index, Some(i as u32));
3817 }
3818 }
3819}