1use core::marker::PhantomData;
2
3use crate::{AccessPolicy, PersistTrigger, ShadowError, policy::PersistPolicy, table::ShadowTable};
4use bitmaps::{Bits, BitsImpl};
5
6pub struct HostView<'a, const TS: usize, const BS: usize, const BC: usize, AP, PP, PT, PK>
11where
12 BitsImpl<BC>: Bits,
13 AP: AccessPolicy,
14 PP: PersistPolicy<PK>,
15 PT: PersistTrigger<PK>,
16{
17 pub(crate) table: &'a mut ShadowTable<TS, BS, BC>,
18 pub(crate) access_policy: &'a AP,
19 pub(crate) persist_policy: &'a PP,
20 pub(crate) persist_trigger: &'a mut PT,
21 _phantom: PhantomData<PK>,
22}
23
24impl<'a, const TS: usize, const BS: usize, const BC: usize, AP, PP, PT, PK>
25 HostView<'a, TS, BS, BC, AP, PP, PT, PK>
26where
27 BitsImpl<BC>: Bits,
28 AP: AccessPolicy,
29 PP: PersistPolicy<PK>,
30 PT: PersistTrigger<PK>,
31{
32 pub(crate) fn new(
33 table: &'a mut ShadowTable<TS, BS, BC>,
34 access_policy: &'a AP,
35 persist_policy: &'a PP,
36 persist_trigger: &'a mut PT,
37 ) -> Self {
38 Self {
39 table,
40 access_policy,
41 persist_policy,
42 persist_trigger,
43 _phantom: PhantomData,
44 }
45 }
46
47 pub fn read_range(&self, addr: u16, out: &mut [u8]) -> Result<(), ShadowError> {
51 if !self.access_policy.can_read(addr, out.len()) {
52 return Err(ShadowError::Denied);
53 }
54 self.table.read_range(addr, out)
55 }
56
57 pub fn write_range(&mut self, addr: u16, data: &[u8]) -> Result<(), ShadowError> {
61 self.write_range_no_persist(addr, data)?;
62
63 let should_persist =
64 self.persist_policy
65 .push_persist_keys_for_range(addr, data.len(), |key| {
66 self.persist_trigger.push_key(key)
67 });
68
69 if should_persist {
70 self.persist_trigger.request_persist();
71 }
72
73 Ok(())
74 }
75
76 pub(crate) fn write_range_no_persist(
77 &mut self,
78 addr: u16,
79 data: &[u8],
80 ) -> Result<(), ShadowError> {
81 if !self.access_policy.can_write(addr, data.len()) {
82 return Err(ShadowError::Denied);
83 }
84
85 self.table.write_range(addr, data)?;
86 self.table.mark_dirty(addr, data.len())?;
87
88 Ok(())
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95 use crate::persist::NoPersist;
96 use crate::policy::{AllowAllPolicy, NoPersistPolicy};
97 use crate::test_support::{DenyAllPolicy, ReadOnlyBelow32, TestTable};
98
99 #[test]
100 fn host_write_marks_dirty() {
101 let mut table = TestTable::new();
102 let policy = AllowAllPolicy::default();
103 let persist_policy = NoPersistPolicy::default();
104 let mut trigger = NoPersist;
105
106 {
107 let mut view = HostView::new(&mut table, &policy, &persist_policy, &mut trigger);
108 view.write_range(0, &[0x01, 0x02, 0x03, 0x04]).unwrap();
109 }
110
111 assert!(table.is_dirty(0, 4).unwrap());
112 }
113
114 #[test]
115 fn host_write_spanning_blocks_marks_all_dirty() {
116 let mut table = TestTable::new();
117 let policy = AllowAllPolicy::default();
118 let persist_policy = NoPersistPolicy::default();
119 let mut trigger = NoPersist;
120
121 {
122 let mut view = HostView::new(&mut table, &policy, &persist_policy, &mut trigger);
123 view.write_range(8, &[0xAA; 16]).unwrap();
125 }
126
127 assert!(table.is_dirty(0, 16).unwrap()); assert!(table.is_dirty(16, 16).unwrap()); }
131
132 #[test]
133 fn host_read_does_not_mark_dirty() {
134 let mut table = TestTable::new();
135 let policy = AllowAllPolicy::default();
136 let persist_policy = NoPersistPolicy::default();
137 let mut trigger = NoPersist;
138
139 let view = HostView::new(&mut table, &policy, &persist_policy, &mut trigger);
140
141 let mut buf = [0u8; 4];
142 view.read_range(0, &mut buf).unwrap();
143
144 assert!(!table.any_dirty());
145 }
146
147 #[test]
148 fn read_denied_returns_error() {
149 let mut table = TestTable::new();
150 let policy = DenyAllPolicy;
151 let persist_policy = NoPersistPolicy::default();
152 let mut trigger = NoPersist;
153
154 let view = HostView::new(&mut table, &policy, &persist_policy, &mut trigger);
155 let mut buf = [0u8; 4];
156
157 assert_eq!(view.read_range(0, &mut buf), Err(ShadowError::Denied));
158 }
159
160 #[test]
161 fn write_denied_returns_error() {
162 let mut table = TestTable::new();
163 let policy = DenyAllPolicy;
164 let persist_policy = NoPersistPolicy::default();
165 let mut trigger = NoPersist;
166
167 let mut view = HostView::new(&mut table, &policy, &persist_policy, &mut trigger);
168
169 assert_eq!(view.write_range(0, &[0x01, 0x02]), Err(ShadowError::Denied));
170 }
171
172 #[test]
173 fn denied_write_does_not_modify_state() {
174 let mut table = TestTable::new();
175 table.write_range(0, &[0xFF; 4]).unwrap();
176
177 let policy = DenyAllPolicy;
178 let persist_policy = NoPersistPolicy::default();
179 let mut trigger = NoPersist;
180
181 {
182 let mut view = HostView::new(&mut table, &policy, &persist_policy, &mut trigger);
183 let _ = view.write_range(0, &[0x00; 4]); }
185
186 let mut buf = [0u8; 4];
188 table.read_range(0, &mut buf).unwrap();
189 assert_eq!(buf, [0xFF; 4]);
190
191 assert!(!table.any_dirty());
193 }
194
195 #[test]
196 fn denied_read_does_not_leak_data() {
197 let mut table = TestTable::new();
198 table.write_range(0, &[0xAA; 4]).unwrap();
199
200 let policy = DenyAllPolicy;
201 let persist_policy = NoPersistPolicy::default();
202 let mut trigger = NoPersist;
203
204 let view = HostView::new(&mut table, &policy, &persist_policy, &mut trigger);
205
206 let mut buf = [0x00u8; 4];
207 let _ = view.read_range(0, &mut buf); assert_eq!(buf, [0x00; 4]);
211 }
212
213 #[test]
214 fn partial_policy_allows_permitted_ranges() {
215 let mut table = TestTable::new();
216 let policy = ReadOnlyBelow32;
217 let persist_policy = NoPersistPolicy::default();
218 let mut trigger = NoPersist;
219
220 let mut view = HostView::new(&mut table, &policy, &persist_policy, &mut trigger);
221
222 let mut buf = [0u8; 4];
224 assert!(view.read_range(0, &mut buf).is_ok());
225 assert!(view.read_range(32, &mut buf).is_ok());
226
227 assert_eq!(view.write_range(0, &[0x01; 4]), Err(ShadowError::Denied));
229
230 assert!(view.write_range(32, &[0x01; 4]).is_ok());
232 }
233}