detcore/
record_or_replay.rs1use reverie::Error;
10use reverie::Guest;
11use reverie::Subscription;
12use reverie::Tool;
13use reverie::syscalls::Syscall;
14use serde::Deserialize;
15use serde::Serialize;
16
17use crate::config::Config;
18use crate::tool_global::GlobalState;
19
20pub trait RecordOrReplay: Tool<GlobalState = GlobalState> {}
22
23impl<T> RecordOrReplay for T where T: Tool<GlobalState = GlobalState> {}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub struct ChosenBinaryInputRefusal;
31
32impl std::fmt::Display for ChosenBinaryInputRefusal {
33 fn fmt(&self, output: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34 output.write_str(
35 "HERMIT_CHOSEN_BINARY_INPUT_PROVENANCE: Chosen guest IDs require reserved-input provenance, which the binary record/replay stream does not carry; the guest has not started. Use Namespace guest IDs for binary record/replay, or use plain Chosen execution and its qualified JSON schedule recording. Unknown custom Tool compositions must declare their actual plain stream role; Config flags cannot override a recorder or replayer.",
36 )
37 }
38}
39
40impl std::error::Error for ChosenBinaryInputRefusal {}
41
42pub fn require_chosen_input_stream(
46 config: &Config,
47 role: reverie::ToolStreamRole,
48) -> Result<(), ChosenBinaryInputRefusal> {
49 if matches!(
50 config.guest_ids,
51 detcore_model::pid::GuestIds::Chosen { .. }
52 ) && (role != reverie::ToolStreamRole::Plain
53 || config.recordreplay_modes
54 || config.replaying)
55 {
56 return Err(ChosenBinaryInputRefusal);
57 }
58 Ok(())
59}
60
61pub fn admit_chosen_input_stream(config: &Config, role: reverie::ToolStreamRole) {
65 if let Err(refusal) = require_chosen_input_stream(config, role) {
66 crate::scheduler::exit_on_scheduler_refusal(&refusal);
67 }
68}
69
70pub(crate) fn injects_only<T>() -> bool {
94 std::mem::size_of::<T>() == 0 && std::any::type_name::<T>() == std::any::type_name::<NoopTool>()
95}
96
97#[derive(Debug, Default, Serialize, Deserialize)]
99pub struct NoopTool;
100
101#[reverie::tool]
102impl Tool for NoopTool {
103 type GlobalState = GlobalState;
104 type ThreadState = ();
105
106 fn stream_role() -> reverie::ToolStreamRole {
107 reverie::ToolStreamRole::Plain
108 }
109
110 fn native_mm_ownership(_cfg: &Config) -> reverie::NativeMmOwnership {
111 reverie::NativeMmOwnership::ManagedRuntimeOnly
114 }
115
116 fn subscriptions(_cfg: &Config) -> Subscription {
117 Subscription::none()
120 }
121
122 async fn handle_syscall_event<T: Guest<Self>>(
123 &self,
124 guest: &mut T,
125 call: Syscall,
126 ) -> Result<i64, Error> {
127 Ok(guest.inject(call).await?)
130 }
131}
132#[cfg(test)]
133mod mapping_capture_tests;
134
135#[cfg(test)]
136mod input_stream_tests {
137 use super::*;
138
139 #[test]
140 fn chosen_binary_refusal_uses_the_typed_stream_even_with_false_mode_flags() {
141 let config = Config {
142 guest_ids: detcore_model::pid::GuestIds::Chosen {
143 base: 5000,
144 refuse_host_ids: true,
145 },
146 recordreplay_modes: false,
147 replaying: false,
148 ..Config::default()
149 };
150 assert_eq!(NoopTool::stream_role(), reverie::ToolStreamRole::Plain);
151 require_chosen_input_stream(&config, NoopTool::stream_role()).unwrap();
152 for role in [
153 reverie::ToolStreamRole::BinaryRecord,
154 reverie::ToolStreamRole::BinaryReplay,
155 reverie::ToolStreamRole::Unknown,
156 ] {
157 let refusal = require_chosen_input_stream(&config, role).unwrap_err();
158 assert!(
159 refusal
160 .to_string()
161 .starts_with("HERMIT_CHOSEN_BINARY_INPUT_PROVENANCE:")
162 );
163 }
164 for (recordreplay_modes, replaying) in [(true, false), (false, true), (true, true)] {
165 let config = Config {
166 recordreplay_modes,
167 replaying,
168 ..config.clone()
169 };
170 assert!(require_chosen_input_stream(&config, reverie::ToolStreamRole::Plain).is_err());
171 }
172 }
173
174 #[test]
175 fn namespace_stream_admission_and_subscription_queries_remain_unconditional() {
176 for role in [
177 reverie::ToolStreamRole::BinaryRecord,
178 reverie::ToolStreamRole::BinaryReplay,
179 reverie::ToolStreamRole::Unknown,
180 reverie::ToolStreamRole::Plain,
181 ] {
182 for (recordreplay_modes, replaying) in
183 [(false, false), (true, false), (false, true), (true, true)]
184 {
185 require_chosen_input_stream(
186 &Config {
187 recordreplay_modes,
188 replaying,
189 ..Config::default()
190 },
191 role,
192 )
193 .unwrap();
194 }
195 }
196 let config = Config {
199 guest_ids: detcore_model::pid::GuestIds::Chosen {
200 base: 5000,
201 refuse_host_ids: true,
202 },
203 recordreplay_modes: true,
204 ..Config::default()
205 };
206 let _ = <crate::Detcore as Tool>::subscriptions(&config);
207 }
208}