Skip to main content

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    /// Padding
152    padding: u8,
153}
154
155impl Default for PduHeader {
156    fn default() -> Self {
157        Self {
158            protocol_version: ProtocolVersion::IEEE1278_1_2012,
159            exercise_id: 1,
160            pdu_type: PduType::default(),
161            protocol_family: ProtocolFamily::default(),
162            timestamp: Self::calculate_dis_timestamp(),
163            length: 0,
164            status_record: PduStatusRecord::default(),
165            padding: 0,
166        }
167    }
168}
169
170impl GenericHeader for PduHeader {
171    fn pdu_type(&self) -> PduType {
172        self.pdu_type
173    }
174
175    fn set_pdu_type(&mut self, value: PduType) {
176        self.pdu_type = value;
177    }
178
179    fn protocol_family(&self) -> ProtocolFamily {
180        self.protocol_family
181    }
182
183    fn set_protocol_family(&mut self, value: ProtocolFamily) {
184        self.protocol_family = value;
185    }
186
187    fn length(&self) -> u16 {
188        self.length
189    }
190
191    fn set_length(&mut self, value: u16) {
192        self.length = value;
193    }
194
195    fn serialize(&self, buf: &mut BytesMut) {
196        buf.put_u8(self.protocol_version as u8);
197        buf.put_u8(self.exercise_id);
198        buf.put_u8(self.pdu_type as u8);
199        buf.put_u8(self.protocol_family as u8);
200        buf.put_u32(self.timestamp);
201        buf.put_u16(self.length);
202        buf.put_u8(self.status_record.to_u8());
203        buf.put_u8(self.padding);
204    }
205
206    fn deserialize<B: Buf>(buf: &mut B) -> Self {
207        Self {
208            protocol_version: ProtocolVersion::deserialize(buf),
209            exercise_id: buf.get_u8(),
210            pdu_type: PduType::deserialize(buf),
211            protocol_family: ProtocolFamily::deserialize(buf),
212            timestamp: buf.get_u32(),
213            length: buf.get_u16(),
214            status_record: PduStatusRecord::from_u8(buf.get_u8()),
215            padding: buf.get_u8(),
216        }
217    }
218}
219
220impl PduHeader {
221    #[must_use]
222    pub fn new(
223        pdu_type: PduType,
224        protocol_family: ProtocolFamily,
225        exercise_id: u8,
226        length: u16,
227    ) -> Self {
228        Self {
229            protocol_version: ProtocolVersion::IEEE1278_1_2012,
230            exercise_id,
231            pdu_type,
232            protocol_family,
233            timestamp: Self::calculate_dis_timestamp(),
234            length,
235            status_record: PduStatusRecord::default(),
236            padding: 0,
237        }
238    }
239
240    /// Gets the current time in terms of IEEE-1278.1 DIS time units
241    #[must_use]
242    #[allow(
243        clippy::cast_precision_loss,
244        clippy::cast_possible_truncation,
245        clippy::cast_sign_loss
246    )]
247    pub fn calculate_dis_timestamp() -> u32 {
248        let minute_curr = u64::from((Utc::now().minute() * 60) * 1_000_000);
249        let second_curr = u64::from(Utc::now().second() * 1_000_000);
250        let nanosecond_curr = u64::from(Utc::now().nanosecond() / 1000);
251        let dis_time = (second_curr + minute_curr + nanosecond_curr) as f32 / 1.68;
252        dis_time as u32
253    }
254}
255
256impl FieldSerialize for PduHeader {
257    fn serialize_field(&self, buf: &mut BytesMut) {
258        self.serialize(buf);
259    }
260}
261
262impl FieldDeserialize for PduHeader {
263    fn deserialize_field<B: Buf>(buf: &mut B) -> Self {
264        Self::deserialize(buf)
265    }
266}
267
268impl FieldLen for PduHeader {
269    fn field_len(&self) -> usize {
270        Self::LENGTH
271    }
272}
273
274impl SerializedLength for PduHeader {
275    const LENGTH: usize = 12;
276}