umsh_ulcp_runtime/
transport_policy.rs1#[derive(Clone, Copy, Debug, PartialEq, Eq)]
4pub enum Transport {
5 Usb,
6 Ble,
7}
8
9#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10pub struct SessionArbitration {
11 active: Option<Transport>,
12 generation: u32,
13}
14
15impl SessionArbitration {
16 pub const fn new(generation: u32) -> Self {
17 Self {
18 active: None,
19 generation,
20 }
21 }
22
23 pub fn attach(&mut self, transport: Transport) {
26 self.generation = self.generation.wrapping_add(1);
27 self.active = Some(transport);
28 }
29
30 pub fn detach(&mut self, transport: Transport) -> bool {
33 if self.active == Some(transport) {
34 self.active = None;
35 true
36 } else {
37 false
38 }
39 }
40
41 #[cfg(test)]
42 pub const fn active(&self) -> Option<Transport> {
43 self.active
44 }
45
46 pub const fn generation(&self) -> u32 {
47 self.generation
48 }
49
50 pub fn accepts_frame(&self, transport: Transport) -> bool {
51 self.active == Some(transport)
52 }
53
54 pub const fn destination(&self) -> Option<(Transport, u32)> {
55 match self.active {
56 Some(transport) => Some((transport, self.generation)),
57 None => None,
58 }
59 }
60
61 #[cfg(test)]
62 pub fn is_current(&self, generation: u32) -> bool {
63 self.generation == generation
64 }
65
66 pub fn advertising_allowed(&self) -> bool {
67 self.active != Some(Transport::Usb)
68 }
69}
70
71pub struct GenerationChecked<I, L> {
75 items: I,
76 load_generation: L,
77 expected: u32,
78 stale: bool,
79}
80
81impl<I, L> GenerationChecked<I, L> {
82 pub const fn stale(&self) -> bool {
83 self.stale
84 }
85}
86
87impl<I, L> Iterator for GenerationChecked<I, L>
88where
89 I: Iterator,
90 L: FnMut() -> u32,
91{
92 type Item = I::Item;
93
94 fn next(&mut self) -> Option<Self::Item> {
95 if (self.load_generation)() != self.expected {
96 self.stale = true;
97 return None;
98 }
99 self.items.next()
100 }
101}
102
103pub fn generation_checked<I, L>(
104 items: I,
105 expected: u32,
106 load_generation: L,
107) -> GenerationChecked<I::IntoIter, L>
108where
109 I: IntoIterator,
110 L: FnMut() -> u32,
111{
112 GenerationChecked {
113 items: items.into_iter(),
114 load_generation,
115 expected,
116 stale: false,
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123 use core::cell::Cell;
124
125 #[test]
126 fn attachment_displaces_previous_transport_and_invalidates_frames() {
127 let mut state = SessionArbitration::new(10);
128 state.attach(Transport::Ble);
129 let ble_generation = state.generation();
130 assert!(state.accepts_frame(Transport::Ble));
131 assert!(state.advertising_allowed());
132
133 state.attach(Transport::Usb);
134 assert_eq!(state.active(), Some(Transport::Usb));
135 assert!(!state.accepts_frame(Transport::Ble));
136 assert!(state.accepts_frame(Transport::Usb));
137 assert!(!state.is_current(ble_generation));
138 assert!(!state.advertising_allowed());
139 }
140
141 #[test]
142 fn late_displaced_detach_does_not_clear_replacement() {
143 let mut state = SessionArbitration::new(0);
144 state.attach(Transport::Ble);
145 state.attach(Transport::Usb);
146 assert!(!state.detach(Transport::Ble));
147 assert_eq!(state.active(), Some(Transport::Usb));
148 assert!(state.detach(Transport::Usb));
149 assert_eq!(state.active(), None);
150 assert!(state.advertising_allowed());
151 }
152
153 #[test]
154 fn no_session_has_no_output_destination_or_accepted_frames() {
155 let state = SessionArbitration::new(7);
156 assert_eq!(state.destination(), None);
157 assert!(!state.accepts_frame(Transport::Usb));
158 assert!(!state.accepts_frame(Transport::Ble));
159 assert!(state.advertising_allowed());
160 }
161
162 #[test]
163 fn generation_wrap_still_invalidates_previous_session() {
164 let mut state = SessionArbitration::new(u32::MAX);
165 assert!(!state.is_current(0));
166 state.attach(Transport::Ble);
167 assert_eq!(state.destination(), Some((Transport::Ble, 0)));
168 assert!(state.is_current(0));
169 state.attach(Transport::Usb);
170 assert_eq!(state.generation(), 1);
171 assert!(!state.is_current(0));
172 }
173
174 #[test]
175 fn generation_checked_stops_between_transport_writes() {
176 let generation = Cell::new(4);
177 let mut writes = generation_checked(["one", "two", "three"], 4, || generation.get());
178 assert_eq!(writes.next(), Some("one"));
179
180 generation.set(5);
183 assert_eq!(writes.next(), None);
184 assert!(writes.stale());
185 }
186
187 #[test]
188 fn generation_checked_yields_every_write_for_current_session() {
189 let writes: heapless::Vec<_, 4> = generation_checked(0..4, 9, || 9).collect();
190 assert_eq!(writes.as_slice(), &[0, 1, 2, 3]);
191 }
192}