dis_rs/common/aggregate_state/
model.rs

1use crate::aggregate_state::builder::AggregateStateBuilder;
2use crate::common::{BodyInfo, Interaction};
3use crate::constants::{EIGHT_OCTETS, FOUR_OCTETS, THIRTY_TWO_OCTETS, TWO_OCTETS};
4use crate::entity_state::model::EntityAppearance;
5use crate::enumerations::{
6    AggregateStateAggregateKind, AggregateStateAggregateState, AggregateStateFormation,
7    AggregateStateSpecific, AggregateStateSubcategory, Country, EntityMarkingCharacterSet, ForceId,
8    PduType, PlatformDomain,
9};
10use crate::model::{
11    length_padded_to_num, EntityId, EntityType, Location, Orientation, PduBody, VariableDatum,
12    VectorF32, BASE_VARIABLE_DATUM_LENGTH,
13};
14use crate::DisError;
15#[cfg(feature = "serde")]
16use serde::{Deserialize, Serialize};
17use std::fmt::{Display, Formatter};
18use std::str::FromStr;
19
20pub(crate) const BASE_AGGREGATE_STATE_BODY_LENGTH: u16 = 124;
21
22/// 5.9.2.2 Aggregate State PDU
23///
24/// 7.8.2 Aggregate State PDU
25#[derive(Clone, Debug, Default, PartialEq)]
26#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
27pub struct AggregateState {
28    pub aggregate_id: EntityId,
29    pub force_id: ForceId,
30    pub aggregate_state: AggregateStateAggregateState,
31    pub aggregate_type: AggregateType,
32    pub formation: AggregateStateFormation,
33    pub aggregate_marking: AggregateMarking,
34    pub dimensions: VectorF32,
35    pub orientation: Orientation,
36    pub center_of_mass: Location,
37    pub velocity: VectorF32,
38    pub aggregates: Vec<EntityId>,
39    pub entities: Vec<EntityId>,
40    pub silent_aggregate_systems: Vec<SilentAggregateSystem>,
41    pub silent_entity_systems: Vec<SilentEntitySystem>,
42    pub variable_datums: Vec<VariableDatum>,
43}
44
45impl AggregateState {
46    #[must_use]
47    pub fn builder() -> AggregateStateBuilder {
48        AggregateStateBuilder::new()
49    }
50
51    #[must_use]
52    pub fn into_builder(self) -> AggregateStateBuilder {
53        AggregateStateBuilder::new_from_body(self)
54    }
55
56    #[must_use]
57    pub fn into_pdu_body(self) -> PduBody {
58        PduBody::AggregateState(self)
59    }
60}
61
62/// Calculate the intermediate length and padding of an `AggregateState` PDU.
63///
64/// Returns a tuple consisting of the intermediate length including the padding,
65/// and the length of the padding, in octets.
66pub(crate) fn aggregate_state_intermediate_length_padding(
67    aggregates: &[EntityId],
68    entities: &[EntityId],
69) -> (u16, u16) {
70    let intermediate_length = BASE_AGGREGATE_STATE_BODY_LENGTH
71        + aggregates.iter().map(crate::model::EntityId::record_length ).sum::<u16>() // number of aggregate ids
72        + entities.iter().map(crate::model::EntityId::record_length ).sum::<u16>(); // number of entity ids
73    let padding_length = intermediate_length % (FOUR_OCTETS as u16); // padding to 32-bits (4 octets) boundary
74    (intermediate_length + padding_length, padding_length)
75}
76
77impl BodyInfo for AggregateState {
78    fn body_length(&self) -> u16 {
79        let (intermediate_length, _padding_length) =
80            aggregate_state_intermediate_length_padding(&self.aggregates, &self.entities);
81        intermediate_length
82            // number of silent aggregate systems
83            + self.silent_aggregate_systems.iter().map(SilentAggregateSystem::record_length ).sum::<u16>()
84            // number of silent entity systems
85            + self.silent_entity_systems.iter().map(SilentEntitySystem::record_length ).sum::<u16>()
86            // number of variable datum records
87            + (self.variable_datums.iter().map(|datum| {
88                let padded_record = length_padded_to_num(
89                    BASE_VARIABLE_DATUM_LENGTH as usize + datum.datum_value.len(),
90                    EIGHT_OCTETS);
91                padded_record.record_length as u16
92            } ).sum::<u16>())
93    }
94
95    fn body_type(&self) -> PduType {
96        PduType::AggregateState
97    }
98}
99
100impl Interaction for AggregateState {
101    fn originator(&self) -> Option<&EntityId> {
102        None
103    }
104
105    fn receiver(&self) -> Option<&EntityId> {
106        None
107    }
108}
109
110/// 6.2.4 Aggregate Marking record
111#[derive(Clone, Debug, Default, PartialEq)]
112#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
113pub struct AggregateMarking {
114    pub marking_character_set: EntityMarkingCharacterSet,
115    pub marking_string: String, // 31 byte String
116}
117
118impl AggregateMarking {
119    #[must_use]
120    pub fn new(marking: String, character_set: EntityMarkingCharacterSet) -> Self {
121        Self {
122            marking_character_set: character_set,
123            marking_string: marking,
124        }
125    }
126
127    pub fn new_ascii<S: Into<String>>(marking: S) -> Self {
128        AggregateMarking::new(marking.into(), EntityMarkingCharacterSet::ASCII)
129    }
130
131    #[allow(clippy::return_self_not_must_use)]
132    pub fn with_marking<S: Into<String>>(mut self, marking: S) -> Self {
133        self.marking_string = marking.into();
134        self
135    }
136
137    #[must_use]
138    pub fn record_length(&self) -> u16 {
139        THIRTY_TWO_OCTETS as u16
140    }
141}
142
143impl Display for AggregateMarking {
144    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
145        f.write_str(self.marking_string.as_str())
146    }
147}
148
149impl FromStr for AggregateMarking {
150    type Err = DisError;
151
152    fn from_str(s: &str) -> Result<Self, Self::Err> {
153        if s.len() <= 31 {
154            Ok(Self {
155                marking_character_set: EntityMarkingCharacterSet::ASCII,
156                marking_string: s.to_string(),
157            })
158        } else {
159            Err(DisError::ParseError(format!(
160                "String is too long for AggregateMarking. Found {}, max 31 allowed.",
161                s.len()
162            )))
163        }
164    }
165}
166
167/// 6.2.5 Aggregate Type record
168#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
169#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
170pub struct AggregateType {
171    pub aggregate_kind: AggregateStateAggregateKind,
172    pub domain: PlatformDomain,
173    pub country: Country,
174    pub category: u8,
175    pub subcategory: AggregateStateSubcategory,
176    pub specific: AggregateStateSpecific,
177    pub extra: u8,
178}
179
180impl AggregateType {
181    #[must_use]
182    pub fn with_aggregate_kind(mut self, aggregate_kind: AggregateStateAggregateKind) -> Self {
183        self.aggregate_kind = aggregate_kind;
184        self
185    }
186
187    #[must_use]
188    pub fn with_domain(mut self, domain: PlatformDomain) -> Self {
189        self.domain = domain;
190        self
191    }
192
193    #[must_use]
194    pub fn with_country(mut self, country: Country) -> Self {
195        self.country = country;
196        self
197    }
198
199    #[must_use]
200    pub fn with_category(mut self, category: u8) -> Self {
201        self.category = category;
202        self
203    }
204
205    #[must_use]
206    pub fn with_subcategory(mut self, subcategory: AggregateStateSubcategory) -> Self {
207        self.subcategory = subcategory;
208        self
209    }
210
211    #[must_use]
212    pub fn with_specific(mut self, specific: AggregateStateSpecific) -> Self {
213        self.specific = specific;
214        self
215    }
216
217    #[must_use]
218    pub fn with_extra(mut self, extra: u8) -> Self {
219        self.extra = extra;
220        self
221    }
222
223    #[must_use]
224    pub fn record_length(&self) -> u16 {
225        EIGHT_OCTETS as u16
226    }
227}
228
229impl Display for AggregateType {
230    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
231        write!(
232            f,
233            "{}:{}:{}:{}:{}:{}:{}",
234            u8::from(self.aggregate_kind),
235            u8::from(self.domain),
236            u16::from(self.country),
237            self.category,
238            u8::from(self.subcategory),
239            u8::from(self.specific),
240            self.extra
241        )
242    }
243}
244
245#[allow(clippy::get_first)]
246impl FromStr for AggregateType {
247    type Err = DisError;
248
249    fn from_str(s: &str) -> Result<Self, Self::Err> {
250        const NUM_DIGITS: usize = 7;
251        let ss = s.split(':').collect::<Vec<&str>>();
252        if ss.len() != NUM_DIGITS {
253            return Err(DisError::ParseError(format!(
254                "AggregateType string pattern does contain not precisely {NUM_DIGITS} digits"
255            )));
256        }
257        Ok(Self {
258            aggregate_kind: ss
259                .get(0)
260                .expect("Impossible - checked for correct number of digits")
261                .parse::<u8>()
262                .map_err(|_| DisError::ParseError("Invalid kind digit".to_string()))?
263                .into(),
264            domain: ss
265                .get(1)
266                .expect("Impossible - checked for correct number of digits")
267                .parse::<u8>()
268                .map_err(|_| DisError::ParseError("Invalid domain digit".to_string()))?
269                .into(),
270            country: ss
271                .get(2)
272                .expect("Impossible - checked for correct number of digits")
273                .parse::<u16>()
274                .map_err(|_| DisError::ParseError("Invalid country digit".to_string()))?
275                .into(),
276            category: ss
277                .get(3)
278                .expect("Impossible - checked for correct number of digits")
279                .parse::<u8>()
280                .map_err(|_| DisError::ParseError("Invalid category digit".to_string()))?,
281            subcategory: ss
282                .get(4)
283                .expect("Impossible - checked for correct number of digits")
284                .parse::<u8>()
285                .map_err(|_| DisError::ParseError("Invalid subcategory digit".to_string()))?
286                .into(),
287            specific: ss
288                .get(5)
289                .expect("Impossible - checked for correct number of digits")
290                .parse::<u8>()
291                .map_err(|_| DisError::ParseError("Invalid specific digit".to_string()))?
292                .into(),
293            extra: ss
294                .get(6)
295                .expect("Impossible - checked for correct number of digits")
296                .parse::<u8>()
297                .map_err(|_| DisError::ParseError("Invalid extra digit".to_string()))?,
298        })
299    }
300}
301
302impl TryFrom<&str> for AggregateType {
303    type Error = DisError;
304
305    fn try_from(value: &str) -> Result<Self, Self::Error> {
306        AggregateType::from_str(value)
307    }
308}
309
310impl TryFrom<String> for AggregateType {
311    type Error = DisError;
312
313    fn try_from(value: String) -> Result<Self, Self::Error> {
314        TryFrom::<&str>::try_from(&value)
315    }
316}
317
318/// Custom record for `SilentAggregateSystem`
319#[derive(Clone, Debug, Default, PartialEq)]
320#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
321pub struct SilentAggregateSystem {
322    pub number_of_aggregates: u16,
323    pub aggregate_type: AggregateType,
324}
325
326impl SilentAggregateSystem {
327    #[must_use]
328    pub fn with_number_of_aggregates(mut self, number_of_aggregates: u16) -> Self {
329        self.number_of_aggregates = number_of_aggregates;
330        self
331    }
332
333    #[must_use]
334    pub fn with_aggregate_type(mut self, aggregate_type: AggregateType) -> Self {
335        self.aggregate_type = aggregate_type;
336        self
337    }
338
339    #[must_use]
340    pub fn record_length(&self) -> u16 {
341        FOUR_OCTETS as u16 + self.aggregate_type.record_length()
342    }
343}
344
345/// 6.2.79 Silent Entity System record
346#[derive(Clone, Debug, Default, PartialEq)]
347#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
348pub struct SilentEntitySystem {
349    pub number_of_entities: u16,
350    pub entity_type: EntityType,
351    pub appearances: Vec<EntityAppearance>,
352}
353
354impl SilentEntitySystem {
355    #[must_use]
356    pub fn with_number_of_entities(mut self, number_of_entities: u16) -> Self {
357        self.number_of_entities = number_of_entities;
358        self
359    }
360
361    #[must_use]
362    pub fn with_entity_type(mut self, entity_type: EntityType) -> Self {
363        self.entity_type = entity_type;
364        self
365    }
366
367    #[must_use]
368    pub fn with_appearance(mut self, appearance: EntityAppearance) -> Self {
369        self.appearances.push(appearance);
370        self
371    }
372
373    #[must_use]
374    pub fn with_appearances(mut self, appearances: Vec<EntityAppearance>) -> Self {
375        self.appearances = appearances;
376        self
377    }
378
379    #[must_use]
380    pub fn record_length(&self) -> u16 {
381        TWO_OCTETS as u16
382            + self.entity_type.record_length()
383            + self
384                .appearances
385                .iter()
386                .map(crate::entity_state::model::EntityAppearance::record_length)
387                .sum::<u16>()
388    }
389}