open_dis_rust/common/
pdu_header.rs

1//     open-dis-rust - Rust implementation of the IEEE 1278.1-2012 Distributed Interactive
2//                     Simulation (DIS) application protocol
3//     Copyright (C) 2025 Cameron Howell
4//
5//     Licensed under the BSD 2-Clause License
6
7#![allow(clippy::must_use_candidate)]
8
9use bytes::{Buf, BufMut, BytesMut};
10use chrono::{Timelike, Utc};
11use modular_bitfield::prelude::*;
12
13use crate::{
14    common::{
15        GenericHeader, SerializedLength,
16        enums::{
17            ActiveInterrogationIndicator, CoupledExtensionIndicator, DetonationTypeIndicator,
18            FireTypeIndicator, IntercomAttachedIndicator, LVCIndicator, PduStatusIFFSimulationMode,
19            PduType, ProtocolFamily, ProtocolVersion, RadioAttachedIndicator,
20            TransferredEntityIndicator,
21        },
22    },
23    pdu_macro::{FieldDeserialize, FieldLen, FieldSerialize},
24};
25
26#[bitfield(bits = 8)]
27#[derive(Copy, Clone, Debug, PartialEq, Eq)]
28pub struct PduStatusRecord {
29    pub tei: TransferredEntityIndicator,
30    pub lvc: LVCIndicator,
31    pub cei: CoupledExtensionIndicator,
32    pub bit4_5: B2,
33    #[skip]
34    __reserved: B2,
35}
36
37impl PduStatusRecord {
38    #[must_use]
39    pub const fn new_zero() -> Self {
40        Self::new()
41    }
42
43    #[must_use]
44    pub fn get_dti(&self) -> DetonationTypeIndicator {
45        DetonationTypeIndicator::from(self.bit4_5())
46    }
47
48    pub fn set_dti(&mut self, dti: DetonationTypeIndicator) {
49        self.set_bit4_5(dti.into());
50    }
51
52    #[must_use]
53    pub fn get_rai(&self) -> RadioAttachedIndicator {
54        RadioAttachedIndicator::from(self.bit4_5())
55    }
56
57    pub fn set_rai(&mut self, rai: RadioAttachedIndicator) {
58        self.set_bit4_5(rai.into());
59    }
60
61    #[must_use]
62    pub fn get_iai(&self) -> IntercomAttachedIndicator {
63        IntercomAttachedIndicator::from(self.bit4_5())
64    }
65
66    pub fn set_iai(&mut self, iai: IntercomAttachedIndicator) {
67        self.set_bit4_5(iai.into());
68    }
69
70    #[must_use]
71    pub fn get_fti(&self) -> FireTypeIndicator {
72        let bit4 = self.bit4_5() & 0b01;
73        if bit4 == 0 {
74            FireTypeIndicator::Munition
75        } else {
76            FireTypeIndicator::Expendable
77        }
78    }
79
80    pub fn set_fti(&mut self, fti: FireTypeIndicator) {
81        let mut v = self.bit4_5();
82        v = (v & 0b10) | (fti as u8 & 0x01);
83        self.set_bit4_5(v);
84    }
85
86    #[must_use]
87    pub fn get_ism(&self) -> PduStatusIFFSimulationMode {
88        let bit4 = self.bit4_5() & 0b01;
89        if bit4 == 0 {
90            PduStatusIFFSimulationMode::Regeneration
91        } else {
92            PduStatusIFFSimulationMode::Interactive
93        }
94    }
95
96    pub fn set_ism(&mut self, ism: PduStatusIFFSimulationMode) {
97        let mut v = self.bit4_5();
98        v = (v & 0b10) | (ism as u8 & 0x01);
99        self.set_bit4_5(v);
100    }
101
102    #[must_use]
103    pub fn get_aii(&self) -> ActiveInterrogationIndicator {
104        let bit5 = (self.bit4_5() >> 1) & 0b01;
105        if bit5 == 0 {
106            ActiveInterrogationIndicator::NotActive
107        } else {
108            ActiveInterrogationIndicator::Active
109        }
110    }
111
112    pub fn set_aii(&mut self, aii: ActiveInterrogationIndicator) {
113        let mut v = self.bit4_5();
114        v = (v & 0b01) | ((aii as u8) << 1);
115        self.set_bit4_5(v);
116    }
117
118    #[must_use]
119    pub const fn to_u8(&self) -> u8 {
120        self.into_bytes()[0]
121    }
122
123    #[must_use]
124    pub const fn from_u8(b: u8) -> Self {
125        Self::from_bytes([b])
126    }
127}
128
129impl Default for PduStatusRecord {
130    fn default() -> Self {
131        Self::new_zero()
132    }
133}
134
135#[derive(Copy, Clone, Debug, PartialEq, Eq)]
136pub struct PduHeader {
137    /// The version of the protocol
138    pub protocol_version: ProtocolVersion,
139    /// Exercise ID
140    pub exercise_id: u8,
141    /// Type of PDU, unique for each PDU class
142    pub pdu_type: PduType,
143    /// Value that refers to the protocol family
144    pub protocol_family: ProtocolFamily,
145    /// Timestamp value
146    pub timestamp: u32,
147    /// Length, in bytes, of the PDU
148    pub length: u16,
149    /// PDU status record
150    pub status_record: PduStatusRecord,
151}
152
153impl Default for PduHeader {
154    fn default() -> Self {
155        Self {
156            protocol_version: ProtocolVersion::IEEE1278_1_2012,
157            exercise_id: 1,
158            pdu_type: PduType::default(),
159            protocol_family: ProtocolFamily::default(),
160            timestamp: Self::calculate_dis_timestamp(),
161            length: 0,
162            status_record: PduStatusRecord::default(),
163        }
164    }
165}
166
167impl GenericHeader for PduHeader {
168    fn pdu_type(&self) -> PduType {
169        self.pdu_type
170    }
171
172    fn set_pdu_type(&mut self, value: PduType) {
173        self.pdu_type = value;
174    }
175
176    fn protocol_family(&self) -> ProtocolFamily {
177        self.protocol_family
178    }
179
180    fn set_protocol_family(&mut self, value: ProtocolFamily) {
181        self.protocol_family = value;
182    }
183
184    fn length(&self) -> u16 {
185        self.length
186    }
187
188    fn set_length(&mut self, value: u16) {
189        self.length = value;
190    }
191
192    fn serialize(&self, buf: &mut BytesMut) {
193        buf.put_u8(self.protocol_version as u8);
194        buf.put_u8(self.exercise_id);
195        buf.put_u8(self.pdu_type as u8);
196        buf.put_u8(self.protocol_family as u8);
197        buf.put_u32(self.timestamp);
198        buf.put_u16(self.length);
199        buf.put_u8(self.status_record.to_u8());
200    }
201
202    fn deserialize<B: Buf>(buf: &mut B) -> Self {
203        Self {
204            protocol_version: ProtocolVersion::deserialize(buf),
205            exercise_id: buf.get_u8(),
206            pdu_type: PduType::deserialize(buf),
207            protocol_family: ProtocolFamily::deserialize(buf),
208            timestamp: buf.get_u32(),
209            length: buf.get_u16(),
210            status_record: PduStatusRecord::from_u8(buf.get_u8()),
211        }
212    }
213}
214
215impl PduHeader {
216    #[must_use]
217    pub fn new(
218        pdu_type: PduType,
219        protocol_family: ProtocolFamily,
220        exercise_id: u8,
221        length: u16,
222    ) -> Self {
223        Self {
224            protocol_version: ProtocolVersion::IEEE1278_1_2012,
225            exercise_id,
226            pdu_type,
227            protocol_family,
228            timestamp: Self::calculate_dis_timestamp(),
229            length,
230            status_record: PduStatusRecord::default(),
231        }
232    }
233
234    /// Gets the current time in terms of IEEE-1278.1 DIS time units
235    #[must_use]
236    #[allow(
237        clippy::cast_precision_loss,
238        clippy::cast_possible_truncation,
239        clippy::cast_sign_loss
240    )]
241    pub fn calculate_dis_timestamp() -> u32 {
242        let minute_curr = u64::from((Utc::now().minute() * 60) * 1_000_000);
243        let second_curr = u64::from(Utc::now().second() * 1_000_000);
244        let nanosecond_curr = u64::from(Utc::now().nanosecond() / 1000);
245        let dis_time = (second_curr + minute_curr + nanosecond_curr) as f32 / 1.68;
246        dis_time as u32
247    }
248}
249
250impl FieldSerialize for PduHeader {
251    fn serialize_field(&self, buf: &mut BytesMut) {
252        self.serialize(buf);
253    }
254}
255
256impl FieldDeserialize for PduHeader {
257    fn deserialize_field<B: Buf>(buf: &mut B) -> Self {
258        Self::deserialize(buf)
259    }
260}
261
262impl FieldLen for PduHeader {
263    fn field_len(&self) -> usize {
264        Self::LENGTH
265    }
266}
267
268impl SerializedLength for PduHeader {
269    const LENGTH: usize = 12;
270}