1extern crate alloc;
40use alloc::vec::Vec;
41
42use crate::header::RtpsHeader;
43use crate::header_extension::{ChecksumValue, HeTimestamp, HeaderExtension};
44use crate::parameter_list::ParameterList;
45use crate::wire_types::{GuidPrefix, Locator, ProtocolVersion, VendorId};
46
47#[derive(Debug, Clone, PartialEq, Eq)]
50pub struct ReceiverState {
51 pub source_version: ProtocolVersion,
53 pub source_vendor_id: VendorId,
55 pub source_guid_prefix: GuidPrefix,
57 pub dest_guid_prefix: GuidPrefix,
59 pub have_timestamp: bool,
61 pub timestamp: HeTimestamp,
63 pub message_length: Option<u32>,
65 pub message_checksum: ChecksumValue,
67 pub parameters: Option<ParameterList>,
69 pub unicast_reply_locator_list: Vec<Locator>,
72 pub multicast_reply_locator_list: Vec<Locator>,
75 pub clock_skew_detected: bool,
79}
80
81impl ReceiverState {
82 #[must_use]
85 pub fn new(dest_guid_prefix: GuidPrefix) -> Self {
86 Self {
87 source_version: ProtocolVersion::V2_5,
88 source_vendor_id: VendorId([0, 0]),
89 source_guid_prefix: GuidPrefix::from_bytes([0; 12]),
90 dest_guid_prefix,
91 have_timestamp: false,
92 timestamp: HeTimestamp::default(),
93 message_length: None,
94 message_checksum: ChecksumValue::None,
95 parameters: None,
96 unicast_reply_locator_list: Vec::new(),
97 multicast_reply_locator_list: Vec::new(),
98 clock_skew_detected: false,
99 }
100 }
101
102 pub fn init_from_header(&mut self, header: &RtpsHeader) {
104 self.source_version = header.protocol_version;
105 self.source_vendor_id = header.vendor_id;
106 self.source_guid_prefix = header.guid_prefix;
107 self.unicast_reply_locator_list.clear();
109 self.multicast_reply_locator_list.clear();
110 self.have_timestamp = false;
111 }
112
113 pub fn apply_info_source(
120 &mut self,
121 version: ProtocolVersion,
122 vendor_id: VendorId,
123 guid_prefix: GuidPrefix,
124 ) {
125 self.source_version = version;
126 self.source_vendor_id = vendor_id;
127 self.source_guid_prefix = guid_prefix;
128 self.have_timestamp = false;
129 self.unicast_reply_locator_list.clear();
130 self.multicast_reply_locator_list.clear();
131 }
132
133 pub fn apply_info_timestamp(&mut self, ts: HeTimestamp, invalidate: bool) {
136 if invalidate {
137 self.have_timestamp = false;
138 } else {
139 self.have_timestamp = true;
140 self.timestamp = ts;
141 }
142 }
143
144 pub fn apply_info_reply(&mut self, unicast: Vec<Locator>, multicast: Option<Vec<Locator>>) {
147 self.unicast_reply_locator_list = unicast;
148 if let Some(m) = multicast {
149 self.multicast_reply_locator_list = m;
150 }
151 }
152
153 pub fn apply_header_extension(&mut self, he: &HeaderExtension) {
157 if let Some(len) = he.message_length {
158 self.message_length = Some(len);
159 }
160 if let Some(ts) = he.timestamp {
161 self.have_timestamp = true;
162 self.timestamp = ts;
163 }
164 if !matches!(he.checksum, ChecksumValue::None) {
165 self.message_checksum = he.checksum.clone();
166 }
167 if let Some(pl) = &he.parameters {
168 self.parameters = Some(pl.clone());
169 }
170 }
171
172 pub fn note_clock_skew(&mut self, now_seconds: i32, threshold_seconds: u32) {
176 if !self.have_timestamp {
177 return;
178 }
179 let diff = (now_seconds as i64).saturating_sub(self.timestamp.seconds as i64);
180 if diff.unsigned_abs() > u64::from(threshold_seconds) {
181 self.clock_skew_detected = true;
182 }
183 }
184}
185
186#[cfg(test)]
187mod tests {
188 #![allow(clippy::expect_used, clippy::unwrap_used)]
189 use super::*;
190 use crate::header_extension::ChecksumValue;
191 use alloc::vec;
192
193 fn dummy_prefix(byte: u8) -> GuidPrefix {
194 GuidPrefix::from_bytes([byte; 12])
195 }
196
197 #[test]
198 fn new_state_has_default_fields() {
199 let st = ReceiverState::new(dummy_prefix(7));
200 assert!(!st.have_timestamp);
201 assert_eq!(st.dest_guid_prefix, dummy_prefix(7));
202 assert!(matches!(st.message_checksum, ChecksumValue::None));
203 assert!(st.message_length.is_none());
204 assert!(!st.clock_skew_detected);
205 }
206
207 #[test]
208 fn init_from_header_overrides_source_fields() {
209 let mut st = ReceiverState::new(dummy_prefix(0));
210 let h = RtpsHeader::new(VendorId::ZERODDS, dummy_prefix(0xAB));
211 st.init_from_header(&h);
212 assert_eq!(st.source_vendor_id, VendorId::ZERODDS);
213 assert_eq!(st.source_guid_prefix, dummy_prefix(0xAB));
214 }
215
216 #[test]
217 fn apply_info_source_resets_reply_locators_and_timestamp() {
218 let mut st = ReceiverState::new(dummy_prefix(0));
219 st.have_timestamp = true;
220 st.unicast_reply_locator_list.push(Locator::INVALID);
221 st.apply_info_source(
222 ProtocolVersion { major: 2, minor: 5 },
223 VendorId([0x42, 0x42]),
224 dummy_prefix(0x99),
225 );
226 assert_eq!(st.source_version, ProtocolVersion { major: 2, minor: 5 });
227 assert_eq!(st.source_vendor_id, VendorId([0x42, 0x42]));
228 assert_eq!(st.source_guid_prefix, dummy_prefix(0x99));
229 assert!(!st.have_timestamp);
230 assert!(st.unicast_reply_locator_list.is_empty());
231 }
232
233 #[test]
234 fn apply_info_timestamp_sets_value() {
235 let mut st = ReceiverState::new(dummy_prefix(0));
236 st.apply_info_timestamp(
237 HeTimestamp {
238 seconds: 100,
239 fraction: 200,
240 },
241 false,
242 );
243 assert!(st.have_timestamp);
244 assert_eq!(st.timestamp.seconds, 100);
245 assert_eq!(st.timestamp.fraction, 200);
246 }
247
248 #[test]
249 fn apply_info_timestamp_with_invalidate_clears() {
250 let mut st = ReceiverState::new(dummy_prefix(0));
251 st.have_timestamp = true;
252 st.apply_info_timestamp(HeTimestamp::default(), true);
253 assert!(!st.have_timestamp);
254 }
255
256 #[test]
257 fn apply_info_reply_sets_locators() {
258 let mut st = ReceiverState::new(dummy_prefix(0));
259 let uni = vec![Locator::INVALID];
260 let multi = vec![Locator::INVALID, Locator::INVALID];
261 st.apply_info_reply(uni.clone(), Some(multi.clone()));
262 assert_eq!(st.unicast_reply_locator_list, uni);
263 assert_eq!(st.multicast_reply_locator_list, multi);
264 }
265
266 #[test]
267 fn apply_header_extension_updates_fields() {
268 let mut st = ReceiverState::new(dummy_prefix(0));
269 let he = HeaderExtension {
270 little_endian: true,
271 message_length: Some(99),
272 timestamp: Some(HeTimestamp {
273 seconds: 1,
274 fraction: 2,
275 }),
276 checksum: ChecksumValue::Crc32c(0xCAFE),
277 ..HeaderExtension::default()
278 };
279 st.apply_header_extension(&he);
280 assert_eq!(st.message_length, Some(99));
281 assert!(st.have_timestamp);
282 assert_eq!(st.timestamp.seconds, 1);
283 assert!(matches!(st.message_checksum, ChecksumValue::Crc32c(0xCAFE)));
284 }
285
286 #[test]
287 fn apply_header_extension_with_parameters_sets_pl() {
288 let mut st = ReceiverState::new(dummy_prefix(0));
289 let pl = ParameterList::new();
290 let he = HeaderExtension {
291 little_endian: true,
292 parameters: Some(pl.clone()),
293 ..HeaderExtension::default()
294 };
295 st.apply_header_extension(&he);
296 assert_eq!(st.parameters, Some(pl));
297 }
298
299 #[test]
300 fn note_clock_skew_skipped_without_timestamp() {
301 let mut st = ReceiverState::new(dummy_prefix(0));
302 st.note_clock_skew(1_000_000, 5);
303 assert!(!st.clock_skew_detected);
304 }
305
306 #[test]
307 fn note_clock_skew_within_threshold_does_not_flag() {
308 let mut st = ReceiverState::new(dummy_prefix(0));
309 st.have_timestamp = true;
310 st.timestamp = HeTimestamp {
311 seconds: 100,
312 fraction: 0,
313 };
314 st.note_clock_skew(102, 5); assert!(!st.clock_skew_detected);
316 }
317
318 #[test]
319 fn note_clock_skew_above_threshold_flags() {
320 let mut st = ReceiverState::new(dummy_prefix(0));
321 st.have_timestamp = true;
322 st.timestamp = HeTimestamp {
323 seconds: 100,
324 fraction: 0,
325 };
326 st.note_clock_skew(200, 5); assert!(st.clock_skew_detected);
328 }
329}