1use std::collections::{BTreeMap, BTreeSet};
4
5use serde::Serialize;
6use tracing::{Level, event};
7
8use super::alarm::{Alarm, AlarmDetails, AlarmKind, AlarmMaskability, Alarmer, BugActivity};
9use super::changelog::ChangeIndex;
10use super::diagnostics::DiagnosticFetcher;
11use crate::diagnostics::CurrentInstructionDiagnostics;
12
13#[cfg(test)]
14use base::Unsigned6Bit;
15
16#[derive(Debug, Serialize)]
18pub struct AlarmStatus {
19 pub name: String,
20 pub maskable: bool,
21 pub masked: bool,
22 pub active: bool,
23 pub message: String,
26}
27
28#[derive(Debug, Default)]
34pub struct AlarmUnit {
35 panic_on_unmasked_alarm: bool,
36 masked: BTreeSet<AlarmKind>,
37 active: BTreeMap<AlarmKind, Alarm>,
38 changes: ChangeIndex<AlarmKind>,
39}
40
41impl AlarmUnit {
42 pub fn new() -> AlarmUnit {
43 AlarmUnit::default()
44 }
45
46 fn status_for_alarm_kind(&self, kind: &AlarmKind) -> AlarmStatus {
47 let maybe_firing_alarm: Option<&Alarm> = self.active.get(kind);
48 AlarmStatus {
49 name: kind.to_string(),
50 maskable: matches!(kind.maskable(), AlarmMaskability::Maskable),
51 masked: self.masked.contains(kind),
52 active: maybe_firing_alarm.is_some(),
53 message: match maybe_firing_alarm {
54 Some(a) => a.to_string(),
55 None => String::new(),
56 },
57 }
58 }
59
60 pub fn get_alarm_statuses(&self) -> Vec<AlarmStatus> {
61 AlarmKind::all_alarm_kinds()
62 .iter()
63 .map(|kind| self.status_for_alarm_kind(kind))
64 .collect()
65 }
66
67 pub fn drain_alarm_changes(&mut self) -> BTreeMap<AlarmKind, AlarmStatus> {
68 self.changes
69 .drain()
70 .into_iter()
71 .map(|kind| (kind, self.status_for_alarm_kind(&kind)))
72 .collect()
73 }
74
75 pub fn get_status_of_alarm(&self, name: &str) -> Option<AlarmStatus> {
76 AlarmKind::try_from(name)
77 .map(|k| self.status_for_alarm_kind(&k))
78 .ok()
79 }
80
81 pub fn new_with_panic(panic: bool) -> AlarmUnit {
82 AlarmUnit {
83 panic_on_unmasked_alarm: panic,
84 ..AlarmUnit::new()
85 }
86 }
87
88 pub fn mask(
89 &mut self,
90 kind: AlarmKind,
91 diags: &CurrentInstructionDiagnostics,
92 ) -> Result<(), Alarm> {
93 match kind.maskable() {
94 AlarmMaskability::Unmaskable => {
95 let bug = Alarm {
96 sequence: None,
97 details: AlarmDetails::BUGAL {
98 activity: BugActivity::AlarmHandling,
99 diagnostics: diags.clone(),
100 message: format!("attempt to mask unmaskable alarm {kind}"),
101 },
102 };
103 Err(self.always_fire(bug, diags))
104 }
105 AlarmMaskability::Maskable => {
106 self.changes.add(kind);
107 self.masked.insert(kind);
108 Ok(())
109 }
110 }
111 }
112
113 pub fn unmask(&mut self, kind: AlarmKind) {
114 if self.masked.remove(&kind) {
115 self.changes.add(kind);
116 }
117 }
118
119 fn is_masked(&self, alarm_instance: &Alarm) -> bool {
120 let kind = alarm_instance.kind();
121 match kind.maskable() {
122 AlarmMaskability::Unmaskable => false,
123 AlarmMaskability::Maskable => {
124 self.masked.contains(&kind)
127 }
128 }
129 }
130
131 fn maybe_panic(&self, alarm_instance: &Alarm) {
132 if self.panic_on_unmasked_alarm {
133 event!(Level::ERROR, "panicing with alarm {}", alarm_instance);
137 panic!(
138 "unmasked alarm and panic_on_unmasked_alarm={}: {}",
139 self.panic_on_unmasked_alarm, alarm_instance
140 );
141 }
142 }
143
144 pub fn clear_all_alarms(&mut self) {
145 event!(Level::INFO, "clearing all alarms");
146 self.active.clear();
147 }
148
149 pub fn unmasked_alarm_active(&self) -> bool {
150 self.active.keys().any(|kind| match kind.maskable() {
151 AlarmMaskability::Unmaskable => {
152 assert!(!self.masked.contains(kind));
153 true
154 }
155 AlarmMaskability::Maskable => !self.masked.contains(kind),
156 })
157 }
158
159 fn set_active(&mut self, alarm_instance: Alarm) -> Result<(), Alarm> {
160 let kind: AlarmKind = alarm_instance.kind();
161 self.changes.add(kind);
162 if self.is_masked(&alarm_instance) {
163 self.active.insert(kind, alarm_instance);
164 Ok(())
165 } else {
166 self.active.insert(kind, alarm_instance.clone());
167 self.maybe_panic(&alarm_instance);
168 Err(alarm_instance)
169 }
170 }
171}
172
173impl Alarmer for AlarmUnit {
174 fn fire_if_not_masked<F: DiagnosticFetcher>(
175 &mut self,
176 alarm_instance: Alarm,
177 _get_diags: F,
178 ) -> Result<(), Alarm> {
179 self.changes.add(alarm_instance.kind());
180 self.set_active(alarm_instance)
181 }
182
183 fn always_fire<F: DiagnosticFetcher>(
184 &mut self,
185 alarm_instance: Alarm,
186 get_diagnostics: F,
187 ) -> Alarm {
188 let kind = alarm_instance.kind();
189 self.changes.add(kind);
190 let sequence = alarm_instance.sequence;
191 match self.set_active(alarm_instance) {
192 Err(a) => a,
193 Ok(()) => {
194 let bug = Alarm {
195 sequence,
196 details: AlarmDetails::BUGAL {
197 activity: BugActivity::AlarmHandling,
198 diagnostics: get_diagnostics.diagnostics(),
199 message: format!(
200 "alarm {kind} is masked, but the caller assumed it could not be"
201 ),
202 },
203 };
204 match self.set_active(bug) {
205 Err(a) => a,
206 Ok(()) => unreachable!("Alarm BUGAL was unexpectedly masked"),
207 }
208 }
209 }
210 }
211}
212
213#[test]
214fn unmaskable_alarms_are_not_maskable() {
215 use base::prelude::{Address, Instruction};
216
217 let mut alarm_unit = AlarmUnit::new_with_panic(false);
218 assert!(!alarm_unit.unmasked_alarm_active());
219 let diagnostics = CurrentInstructionDiagnostics {
220 current_instruction: Instruction::invalid(),
221 instruction_address: Address::ZERO,
222 };
223 assert!(
225 alarm_unit
226 .mask(AlarmKind::ROUNDTUITAL, &diagnostics)
227 .is_err()
228 );
229 assert!(matches!(
231 alarm_unit.fire_if_not_masked(
232 Alarm {
233 sequence: Some(Unsigned6Bit::ZERO),
234 details: AlarmDetails::ROUNDTUITAL {
235 explanation: "The ROUNDTUITAL alarm is not maskable!".to_string(),
236 bug_report_url: "https://github.com/TX-2/TX-2-simulator/issues/144",
237 },
238 },
239 &diagnostics
240 ),
241 Err(Alarm {
242 sequence: Some(_),
243 details: AlarmDetails::ROUNDTUITAL { .. },
244 })
245 ));
246 assert!(alarm_unit.unmasked_alarm_active());
249}
250
251#[test]
252fn maskable_alarms_are_not_masked_by_default() {
253 use base::prelude::{Address, Instruction};
254
255 let mut alarm_unit = AlarmUnit::new_with_panic(false);
256 assert!(!alarm_unit.unmasked_alarm_active());
257 let diagnostics = CurrentInstructionDiagnostics {
258 current_instruction: Instruction::invalid(),
259 instruction_address: Address::ZERO,
260 };
261 let the_alarm = Alarm {
263 sequence: Some(Unsigned6Bit::ZERO),
264 details: AlarmDetails::PSAL(22, "some PPSAL alarm".to_string()),
265 };
266 assert!(matches!(
268 alarm_unit.fire_if_not_masked(the_alarm, &diagnostics),
269 Err(Alarm {
270 sequence: _,
271 details: AlarmDetails::PSAL(22, _),
272 },)
273 ));
274 assert!(alarm_unit.unmasked_alarm_active());
277}