Skip to main content

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