dis_rs/common/
model.rs

1use crate::acknowledge_r::model::AcknowledgeR;
2use crate::action_request_r::model::ActionRequestR;
3use crate::action_response_r::model::ActionResponseR;
4use crate::aggregate_state::model::AggregateState;
5use crate::comment_r::model::CommentR;
6use crate::common::acknowledge::model::Acknowledge;
7use crate::common::action_request::model::ActionRequest;
8use crate::common::action_response::model::ActionResponse;
9use crate::common::attribute::model::Attribute;
10use crate::common::collision::model::Collision;
11use crate::common::collision_elastic::model::CollisionElastic;
12use crate::common::comment::model::Comment;
13use crate::common::create_entity::model::CreateEntity;
14use crate::common::data::model::Data;
15use crate::common::data_query::model::DataQuery;
16use crate::common::designator::model::Designator;
17use crate::common::detonation::model::Detonation;
18use crate::common::electromagnetic_emission::model::ElectromagneticEmission;
19use crate::common::entity_state::model::EntityState;
20use crate::common::entity_state_update::model::EntityStateUpdate;
21use crate::common::event_report::model::EventReport;
22use crate::common::fire::model::Fire;
23use crate::common::iff::model::Iff;
24use crate::common::other::model::Other;
25use crate::common::receiver::model::Receiver;
26use crate::common::remove_entity::model::RemoveEntity;
27use crate::common::set_data::model::SetData;
28use crate::common::signal::model::Signal;
29use crate::common::start_resume::model::StartResume;
30use crate::common::stop_freeze::model::StopFreeze;
31use crate::common::transmitter::model::Transmitter;
32use crate::common::{BodyInfo, Interaction};
33use crate::constants::{
34    EIGHT_OCTETS, FIFTEEN_OCTETS, LEAST_SIGNIFICANT_BIT, NANOSECONDS_PER_TIME_UNIT, NO_REMAINDER,
35    PDU_HEADER_LEN_BYTES, SIX_OCTETS,
36};
37use crate::create_entity_r::model::CreateEntityR;
38use crate::data_query_r::model::DataQueryR;
39use crate::data_r::model::DataR;
40use crate::enumerations::{
41    ArticulatedPartsTypeClass, ArticulatedPartsTypeMetric, AttachedPartDetachedIndicator,
42    AttachedParts, ChangeIndicator, EntityAssociationAssociationStatus,
43    EntityAssociationGroupMemberType, EntityAssociationPhysicalAssociationType,
44    EntityAssociationPhysicalConnectionType, SeparationPreEntityIndicator,
45    SeparationReasonForSeparation, StationName,
46};
47use crate::enumerations::{
48    Country, EntityKind, ExplosiveMaterialCategories, MunitionDescriptorFuse,
49    MunitionDescriptorWarhead, PduType, PlatformDomain, ProtocolFamily, ProtocolVersion,
50    VariableRecordType,
51};
52use crate::event_report_r::model::EventReportR;
53use crate::fixed_parameters::{NO_APPLIC, NO_ENTITY, NO_SITE};
54use crate::is_group_of::model::IsGroupOf;
55use crate::is_part_of::model::IsPartOf;
56use crate::record_query_r::model::RecordQueryR;
57use crate::record_r::model::RecordR;
58use crate::remove_entity_r::model::RemoveEntityR;
59use crate::repair_complete::model::RepairComplete;
60use crate::repair_response::model::RepairResponse;
61use crate::resupply_cancel::model::ResupplyCancel;
62use crate::resupply_offer::model::ResupplyOffer;
63use crate::resupply_received::model::ResupplyReceived;
64use crate::sees::model::SEES;
65use crate::service_request::model::ServiceRequest;
66use crate::set_data_r::model::SetDataR;
67use crate::set_record_r::model::SetRecordR;
68use crate::start_resume_r::model::StartResumeR;
69use crate::stop_freeze_r::model::StopFreezeR;
70use crate::transfer_ownership::model::TransferOwnership;
71use crate::underwater_acoustic::model::UnderwaterAcoustic;
72use crate::DisError;
73#[cfg(feature = "serde")]
74use serde::{Deserialize, Serialize};
75use std::fmt::Display;
76use std::str::FromStr;
77
78pub use crate::v7::model::PduStatus;
79
80#[derive(Clone, Debug, PartialEq)]
81#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
82pub struct Pdu {
83    pub header: PduHeader,
84    pub body: PduBody,
85}
86
87impl Pdu {
88    pub fn finalize_from_parts(
89        header: PduHeader,
90        body: PduBody,
91        time_stamp: impl Into<TimeStamp>,
92    ) -> Self {
93        let time_stamp: TimeStamp = time_stamp.into();
94        Self {
95            header: header
96                .with_pdu_type(body.body_type())
97                .with_time_stamp(time_stamp.raw_timestamp)
98                .with_length(body.body_length()),
99            body,
100        }
101    }
102
103    #[must_use]
104    pub fn pdu_length(&self) -> u16 {
105        PDU_HEADER_LEN_BYTES + self.body.body_length()
106    }
107}
108
109impl Interaction for Pdu {
110    fn originator(&self) -> Option<&EntityId> {
111        self.body.originator()
112    }
113
114    fn receiver(&self) -> Option<&EntityId> {
115        self.body.receiver()
116    }
117}
118
119/// 6.2.66 PDU Header record
120#[derive(Copy, Clone, Debug, PartialEq)]
121#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
122pub struct PduHeader {
123    pub protocol_version: ProtocolVersion,
124    pub exercise_id: u8,
125    pub pdu_type: PduType,
126    pub protocol_family: ProtocolFamily,
127    pub time_stamp: u32,
128    pub pdu_length: u16,
129    pub pdu_status: Option<PduStatus>,
130    pub padding: u16,
131}
132
133impl PduHeader {
134    #[must_use]
135    pub fn new(protocol_version: ProtocolVersion, exercise_id: u8, pdu_type: PduType) -> Self {
136        let protocol_family = pdu_type.into();
137        Self {
138            protocol_version,
139            exercise_id,
140            pdu_type,
141            protocol_family,
142            time_stamp: 0u32,
143            pdu_length: 0u16,
144            pdu_status: None,
145            padding: 0u16,
146        }
147    }
148
149    #[must_use]
150    pub fn new_v6(exercise_id: u8, pdu_type: PduType) -> Self {
151        PduHeader::new(ProtocolVersion::IEEE1278_1A1998, exercise_id, pdu_type)
152    }
153
154    #[must_use]
155    pub fn new_v7(exercise_id: u8, pdu_type: PduType) -> Self {
156        PduHeader::new(ProtocolVersion::IEEE1278_12012, exercise_id, pdu_type)
157    }
158
159    #[must_use]
160    pub fn with_pdu_type(mut self, pdu_type: PduType) -> Self {
161        self.protocol_family = pdu_type.into();
162        self.pdu_type = pdu_type;
163        self
164    }
165
166    #[allow(clippy::return_self_not_must_use)]
167    pub fn with_time_stamp(mut self, time_stamp: impl Into<u32>) -> Self {
168        let time_stamp: u32 = time_stamp.into();
169        self.time_stamp = time_stamp;
170        self
171    }
172
173    #[must_use]
174    pub fn with_length(mut self, body_length: u16) -> Self {
175        self.pdu_length = PDU_HEADER_LEN_BYTES + body_length;
176        self
177    }
178
179    #[must_use]
180    pub fn with_pdu_status(mut self, pdu_status: PduStatus) -> Self {
181        self.pdu_status = Some(pdu_status);
182        self
183    }
184}
185
186#[derive(Clone, Debug, PartialEq)]
187#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
188#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
189#[cfg_attr(feature = "serde", serde(tag = "type"))]
190pub enum PduBody {
191    Other(Other),
192    EntityState(EntityState),
193    Fire(Fire),
194    Detonation(Detonation),
195    Collision(Collision),
196    ServiceRequest(ServiceRequest),
197    ResupplyOffer(ResupplyOffer),
198    ResupplyReceived(ResupplyReceived),
199    ResupplyCancel(ResupplyCancel),
200    RepairComplete(RepairComplete),
201    RepairResponse(RepairResponse),
202    CreateEntity(CreateEntity),
203    RemoveEntity(RemoveEntity),
204    StartResume(StartResume),
205    StopFreeze(StopFreeze),
206    Acknowledge(Acknowledge),
207    ActionRequest(ActionRequest),
208    ActionResponse(ActionResponse),
209    DataQuery(DataQuery),
210    SetData(SetData),
211    Data(Data),
212    EventReport(EventReport),
213    Comment(Comment),
214    ElectromagneticEmission(ElectromagneticEmission),
215    Designator(Designator),
216    Transmitter(Transmitter),
217    Signal(Signal),
218    Receiver(Receiver),
219    IFF(Iff),
220    UnderwaterAcoustic(UnderwaterAcoustic),
221    SupplementalEmissionEntityState(SEES),
222    IntercomSignal,
223    IntercomControl,
224    AggregateState(AggregateState),
225    IsGroupOf(IsGroupOf),
226    TransferOwnership(TransferOwnership),
227    IsPartOf(IsPartOf),
228    MinefieldState,
229    MinefieldQuery,
230    MinefieldData,
231    MinefieldResponseNACK,
232    EnvironmentalProcess,
233    GriddedData,
234    PointObjectState,
235    LinearObjectState,
236    ArealObjectState,
237    TSPI,
238    Appearance,
239    ArticulatedParts,
240    LEFire,
241    LEDetonation,
242    CreateEntityR(CreateEntityR),
243    RemoveEntityR(RemoveEntityR),
244    StartResumeR(StartResumeR),
245    StopFreezeR(StopFreezeR),
246    AcknowledgeR(AcknowledgeR),
247    ActionRequestR(ActionRequestR),
248    ActionResponseR(ActionResponseR),
249    DataQueryR(DataQueryR),
250    SetDataR(SetDataR),
251    DataR(DataR),
252    EventReportR(EventReportR),
253    CommentR(CommentR),
254    RecordR(RecordR),
255    SetRecordR(SetRecordR),
256    RecordQueryR(RecordQueryR),
257    CollisionElastic(CollisionElastic),
258    EntityStateUpdate(EntityStateUpdate),
259    DirectedEnergyFire,
260    EntityDamageStatus,
261    InformationOperationsAction,
262    InformationOperationsReport,
263    Attribute(Attribute),
264}
265
266impl BodyInfo for PduBody {
267    #[allow(clippy::match_same_arms)]
268    fn body_length(&self) -> u16 {
269        match self {
270            PduBody::Other(body) => body.body_length(),
271            PduBody::EntityState(body) => body.body_length(),
272            PduBody::Fire(body) => body.body_length(),
273            PduBody::Detonation(body) => body.body_length(),
274            PduBody::Collision(body) => body.body_length(),
275            PduBody::ServiceRequest(body) => body.body_length(),
276            PduBody::ResupplyOffer(body) => body.body_length(),
277            PduBody::ResupplyReceived(body) => body.body_length(),
278            PduBody::ResupplyCancel(body) => body.body_length(),
279            PduBody::RepairComplete(body) => body.body_length(),
280            PduBody::RepairResponse(body) => body.body_length(),
281            PduBody::CreateEntity(body) => body.body_length(),
282            PduBody::RemoveEntity(body) => body.body_length(),
283            PduBody::StartResume(body) => body.body_length(),
284            PduBody::StopFreeze(body) => body.body_length(),
285            PduBody::Acknowledge(body) => body.body_length(),
286            PduBody::ActionRequest(body) => body.body_length(),
287            PduBody::ActionResponse(body) => body.body_length(),
288            PduBody::DataQuery(body) => body.body_length(),
289            PduBody::SetData(body) => body.body_length(),
290            PduBody::Data(body) => body.body_length(),
291            PduBody::EventReport(body) => body.body_length(),
292            PduBody::Comment(body) => body.body_length(),
293            PduBody::ElectromagneticEmission(body) => body.body_length(),
294            PduBody::Designator(body) => body.body_length(),
295            PduBody::Transmitter(body) => body.body_length(),
296            PduBody::Signal(body) => body.body_length(),
297            PduBody::Receiver(body) => body.body_length(),
298            PduBody::IFF(body) => body.body_length(),
299            PduBody::UnderwaterAcoustic(body) => body.body_length(),
300            PduBody::SupplementalEmissionEntityState(body) => body.body_length(),
301            PduBody::IntercomSignal => 0,
302            PduBody::IntercomControl => 0,
303            PduBody::AggregateState(body) => body.body_length(),
304            PduBody::IsGroupOf(body) => body.body_length(),
305            PduBody::TransferOwnership(body) => body.body_length(),
306            PduBody::IsPartOf(body) => body.body_length(),
307            PduBody::MinefieldState => 0,
308            PduBody::MinefieldQuery => 0,
309            PduBody::MinefieldData => 0,
310            PduBody::MinefieldResponseNACK => 0,
311            PduBody::EnvironmentalProcess => 0,
312            PduBody::GriddedData => 0,
313            PduBody::PointObjectState => 0,
314            PduBody::LinearObjectState => 0,
315            PduBody::ArealObjectState => 0,
316            PduBody::TSPI => 0,
317            PduBody::Appearance => 0,
318            PduBody::ArticulatedParts => 0,
319            PduBody::LEFire => 0,
320            PduBody::LEDetonation => 0,
321            PduBody::CreateEntityR(body) => body.body_length(),
322            PduBody::RemoveEntityR(body) => body.body_length(),
323            PduBody::StartResumeR(body) => body.body_length(),
324            PduBody::StopFreezeR(body) => body.body_length(),
325            PduBody::AcknowledgeR(body) => body.body_length(),
326            PduBody::ActionRequestR(body) => body.body_length(),
327            PduBody::ActionResponseR(body) => body.body_length(),
328            PduBody::DataQueryR(body) => body.body_length(),
329            PduBody::SetDataR(body) => body.body_length(),
330            PduBody::DataR(body) => body.body_length(),
331            PduBody::EventReportR(body) => body.body_length(),
332            PduBody::CommentR(body) => body.body_length(),
333            PduBody::RecordR(body) => body.body_length(),
334            PduBody::SetRecordR(body) => body.body_length(),
335            PduBody::RecordQueryR(body) => body.body_length(),
336            PduBody::CollisionElastic(body) => body.body_length(),
337            PduBody::EntityStateUpdate(body) => body.body_length(),
338            PduBody::DirectedEnergyFire => 0,
339            PduBody::EntityDamageStatus => 0,
340            PduBody::InformationOperationsAction => 0,
341            PduBody::InformationOperationsReport => 0,
342            PduBody::Attribute(body) => body.body_length(),
343        }
344    }
345
346    fn body_type(&self) -> PduType {
347        match self {
348            PduBody::Other(body) => body.body_type(),
349            PduBody::EntityState(body) => body.body_type(),
350            PduBody::Fire(body) => body.body_type(),
351            PduBody::Detonation(body) => body.body_type(),
352            PduBody::Collision(body) => body.body_type(),
353            PduBody::ServiceRequest(body) => body.body_type(),
354            PduBody::ResupplyOffer(body) => body.body_type(),
355            PduBody::ResupplyReceived(body) => body.body_type(),
356            PduBody::ResupplyCancel(body) => body.body_type(),
357            PduBody::RepairComplete(body) => body.body_type(),
358            PduBody::RepairResponse(body) => body.body_type(),
359            PduBody::CreateEntity(body) => body.body_type(),
360            PduBody::RemoveEntity(body) => body.body_type(),
361            PduBody::StartResume(body) => body.body_type(),
362            PduBody::StopFreeze(body) => body.body_type(),
363            PduBody::Acknowledge(body) => body.body_type(),
364            PduBody::ActionRequest(body) => body.body_type(),
365            PduBody::ActionResponse(body) => body.body_type(),
366            PduBody::DataQuery(body) => body.body_type(),
367            PduBody::SetData(body) => body.body_type(),
368            PduBody::Data(body) => body.body_type(),
369            PduBody::EventReport(body) => body.body_type(),
370            PduBody::Comment(body) => body.body_type(),
371            PduBody::ElectromagneticEmission(body) => body.body_type(),
372            PduBody::Designator(body) => body.body_type(),
373            PduBody::Transmitter(body) => body.body_type(),
374            PduBody::Signal(body) => body.body_type(),
375            PduBody::Receiver(body) => body.body_type(),
376            PduBody::IFF(body) => body.body_type(),
377            PduBody::UnderwaterAcoustic(body) => body.body_type(),
378            PduBody::SupplementalEmissionEntityState(body) => body.body_type(),
379            PduBody::IntercomSignal => PduType::IntercomSignal,
380            PduBody::IntercomControl => PduType::IntercomControl,
381            PduBody::AggregateState(body) => body.body_type(),
382            PduBody::IsGroupOf(body) => body.body_type(),
383            PduBody::TransferOwnership(body) => body.body_type(),
384            PduBody::IsPartOf(body) => body.body_type(),
385            PduBody::MinefieldState => PduType::MinefieldState,
386            PduBody::MinefieldQuery => PduType::MinefieldQuery,
387            PduBody::MinefieldData => PduType::MinefieldData,
388            PduBody::MinefieldResponseNACK => PduType::MinefieldResponseNACK,
389            PduBody::EnvironmentalProcess => PduType::EnvironmentalProcess,
390            PduBody::GriddedData => PduType::GriddedData,
391            PduBody::PointObjectState => PduType::PointObjectState,
392            PduBody::LinearObjectState => PduType::LinearObjectState,
393            PduBody::ArealObjectState => PduType::ArealObjectState,
394            PduBody::TSPI => PduType::TSPI,
395            PduBody::Appearance => PduType::Appearance,
396            PduBody::ArticulatedParts => PduType::ArticulatedParts,
397            PduBody::LEFire => PduType::LEFire,
398            PduBody::LEDetonation => PduType::LEDetonation,
399            PduBody::CreateEntityR(body) => body.body_type(),
400            PduBody::RemoveEntityR(body) => body.body_type(),
401            PduBody::StartResumeR(body) => body.body_type(),
402            PduBody::StopFreezeR(body) => body.body_type(),
403            PduBody::AcknowledgeR(body) => body.body_type(),
404            PduBody::ActionRequestR(body) => body.body_type(),
405            PduBody::ActionResponseR(body) => body.body_type(),
406            PduBody::DataQueryR(body) => body.body_type(),
407            PduBody::SetDataR(body) => body.body_type(),
408            PduBody::DataR(body) => body.body_type(),
409            PduBody::EventReportR(body) => body.body_type(),
410            PduBody::CommentR(body) => body.body_type(),
411            PduBody::RecordR(body) => body.body_type(),
412            PduBody::SetRecordR(body) => body.body_type(),
413            PduBody::RecordQueryR(body) => body.body_type(),
414            PduBody::CollisionElastic(body) => body.body_type(),
415            PduBody::EntityStateUpdate(body) => body.body_type(),
416            PduBody::DirectedEnergyFire => PduType::DirectedEnergyFire,
417            PduBody::EntityDamageStatus => PduType::EntityDamageStatus,
418            PduBody::InformationOperationsAction => PduType::InformationOperationsAction,
419            PduBody::InformationOperationsReport => PduType::InformationOperationsReport,
420            PduBody::Attribute(body) => body.body_type(),
421        }
422    }
423}
424
425impl Interaction for PduBody {
426    #[allow(clippy::match_same_arms)]
427    fn originator(&self) -> Option<&EntityId> {
428        match self {
429            PduBody::Other(body) => body.originator(),
430            PduBody::EntityState(body) => body.originator(),
431            PduBody::Fire(body) => body.originator(),
432            PduBody::Detonation(body) => body.originator(),
433            PduBody::Collision(body) => body.originator(),
434            PduBody::ServiceRequest(body) => body.originator(),
435            PduBody::ResupplyOffer(body) => body.originator(),
436            PduBody::ResupplyReceived(body) => body.originator(),
437            PduBody::ResupplyCancel(body) => body.originator(),
438            PduBody::RepairComplete(body) => body.originator(),
439            PduBody::RepairResponse(body) => body.originator(),
440            PduBody::CreateEntity(body) => body.originator(),
441            PduBody::RemoveEntity(body) => body.originator(),
442            PduBody::StartResume(body) => body.originator(),
443            PduBody::StopFreeze(body) => body.originator(),
444            PduBody::Acknowledge(body) => body.originator(),
445            PduBody::ActionRequest(body) => body.originator(),
446            PduBody::ActionResponse(body) => body.originator(),
447            PduBody::DataQuery(body) => body.originator(),
448            PduBody::SetData(body) => body.originator(),
449            PduBody::Data(body) => body.originator(),
450            PduBody::EventReport(body) => body.originator(),
451            PduBody::Comment(body) => body.originator(),
452            PduBody::ElectromagneticEmission(body) => body.originator(),
453            PduBody::Designator(body) => body.originator(),
454            PduBody::Transmitter(body) => body.originator(),
455            PduBody::Signal(body) => body.originator(),
456            PduBody::Receiver(body) => body.originator(),
457            PduBody::IFF(body) => body.originator(),
458            PduBody::UnderwaterAcoustic(body) => body.originator(),
459            PduBody::SupplementalEmissionEntityState(body) => body.originator(),
460            PduBody::IntercomSignal => None,
461            PduBody::IntercomControl => None,
462            PduBody::AggregateState(body) => body.originator(),
463            PduBody::IsGroupOf(body) => body.originator(),
464            PduBody::TransferOwnership(body) => body.originator(),
465            PduBody::IsPartOf(body) => body.originator(),
466            PduBody::MinefieldState => None,
467            PduBody::MinefieldQuery => None,
468            PduBody::MinefieldData => None,
469            PduBody::MinefieldResponseNACK => None,
470            PduBody::EnvironmentalProcess => None,
471            PduBody::GriddedData => None,
472            PduBody::PointObjectState => None,
473            PduBody::LinearObjectState => None,
474            PduBody::ArealObjectState => None,
475            PduBody::TSPI => None,
476            PduBody::Appearance => None,
477            PduBody::ArticulatedParts => None,
478            PduBody::LEFire => None,
479            PduBody::LEDetonation => None,
480            PduBody::CreateEntityR(body) => body.originator(),
481            PduBody::RemoveEntityR(body) => body.originator(),
482            PduBody::StartResumeR(body) => body.originator(),
483            PduBody::StopFreezeR(body) => body.originator(),
484            PduBody::AcknowledgeR(body) => body.originator(),
485            PduBody::ActionRequestR(body) => body.originator(),
486            PduBody::ActionResponseR(body) => body.originator(),
487            PduBody::DataQueryR(body) => body.originator(),
488            PduBody::SetDataR(body) => body.originator(),
489            PduBody::DataR(body) => body.originator(),
490            PduBody::EventReportR(body) => body.originator(),
491            PduBody::CommentR(body) => body.originator(),
492            PduBody::RecordR(body) => body.originator(),
493            PduBody::SetRecordR(body) => body.originator(),
494            PduBody::RecordQueryR(body) => body.originator(),
495            PduBody::CollisionElastic(body) => body.originator(),
496            PduBody::EntityStateUpdate(body) => body.originator(),
497            PduBody::DirectedEnergyFire => None,
498            PduBody::EntityDamageStatus => None,
499            PduBody::InformationOperationsAction => None,
500            PduBody::InformationOperationsReport => None,
501            PduBody::Attribute(body) => body.originator(),
502        }
503    }
504
505    #[allow(clippy::match_same_arms)]
506    fn receiver(&self) -> Option<&EntityId> {
507        match self {
508            PduBody::Other(body) => body.receiver(),
509            PduBody::EntityState(body) => body.receiver(),
510            PduBody::Fire(body) => body.receiver(),
511            PduBody::Detonation(body) => body.receiver(),
512            PduBody::Collision(body) => body.receiver(),
513            PduBody::ServiceRequest(body) => body.receiver(),
514            PduBody::ResupplyOffer(body) => body.receiver(),
515            PduBody::ResupplyReceived(body) => body.receiver(),
516            PduBody::ResupplyCancel(body) => body.receiver(),
517            PduBody::RepairComplete(body) => body.receiver(),
518            PduBody::RepairResponse(body) => body.receiver(),
519            PduBody::CreateEntity(body) => body.receiver(),
520            PduBody::RemoveEntity(body) => body.receiver(),
521            PduBody::StartResume(body) => body.receiver(),
522            PduBody::StopFreeze(body) => body.receiver(),
523            PduBody::Acknowledge(body) => body.receiver(),
524            PduBody::ActionRequest(body) => body.receiver(),
525            PduBody::ActionResponse(body) => body.receiver(),
526            PduBody::DataQuery(body) => body.receiver(),
527            PduBody::SetData(body) => body.receiver(),
528            PduBody::Data(body) => body.receiver(),
529            PduBody::EventReport(body) => body.receiver(),
530            PduBody::Comment(body) => body.receiver(),
531            PduBody::ElectromagneticEmission(body) => body.receiver(),
532            PduBody::Designator(body) => body.receiver(),
533            PduBody::Transmitter(body) => body.receiver(),
534            PduBody::Signal(body) => body.receiver(),
535            PduBody::Receiver(body) => body.receiver(),
536            PduBody::IFF(body) => body.receiver(),
537            PduBody::UnderwaterAcoustic(body) => body.receiver(),
538            PduBody::SupplementalEmissionEntityState(body) => body.receiver(),
539            PduBody::IntercomSignal => None,
540            PduBody::IntercomControl => None,
541            PduBody::AggregateState(body) => body.receiver(),
542            PduBody::IsGroupOf(body) => body.receiver(),
543            PduBody::TransferOwnership(body) => body.receiver(),
544            PduBody::IsPartOf(body) => body.receiver(),
545            PduBody::MinefieldState => None,
546            PduBody::MinefieldQuery => None,
547            PduBody::MinefieldData => None,
548            PduBody::MinefieldResponseNACK => None,
549            PduBody::EnvironmentalProcess => None,
550            PduBody::GriddedData => None,
551            PduBody::PointObjectState => None,
552            PduBody::LinearObjectState => None,
553            PduBody::ArealObjectState => None,
554            PduBody::TSPI => None,
555            PduBody::Appearance => None,
556            PduBody::ArticulatedParts => None,
557            PduBody::LEFire => None,
558            PduBody::LEDetonation => None,
559            PduBody::CreateEntityR(body) => body.receiver(),
560            PduBody::RemoveEntityR(body) => body.receiver(),
561            PduBody::StartResumeR(body) => body.receiver(),
562            PduBody::StopFreezeR(body) => body.receiver(),
563            PduBody::AcknowledgeR(body) => body.receiver(),
564            PduBody::ActionRequestR(body) => body.receiver(),
565            PduBody::ActionResponseR(body) => body.receiver(),
566            PduBody::DataQueryR(body) => body.receiver(),
567            PduBody::SetDataR(body) => body.receiver(),
568            PduBody::DataR(body) => body.receiver(),
569            PduBody::EventReportR(body) => body.receiver(),
570            PduBody::CommentR(body) => body.receiver(),
571            PduBody::RecordR(body) => body.receiver(),
572            PduBody::SetRecordR(body) => body.receiver(),
573            PduBody::RecordQueryR(body) => body.receiver(),
574            PduBody::CollisionElastic(body) => body.receiver(),
575            PduBody::EntityStateUpdate(body) => body.receiver(),
576            PduBody::DirectedEnergyFire => None,
577            PduBody::EntityDamageStatus => None,
578            PduBody::InformationOperationsAction => None,
579            PduBody::InformationOperationsReport => None,
580            PduBody::Attribute(body) => body.receiver(),
581        }
582    }
583}
584
585impl From<PduType> for ProtocolFamily {
586    #[allow(clippy::match_same_arms)]
587    fn from(pdu_type: PduType) -> Self {
588        match pdu_type {
589            PduType::Other => ProtocolFamily::Other,
590            PduType::EntityState => ProtocolFamily::EntityInformationInteraction,
591            PduType::Fire => ProtocolFamily::Warfare,
592            PduType::Detonation => ProtocolFamily::Warfare,
593            PduType::Collision => ProtocolFamily::EntityInformationInteraction,
594            PduType::ServiceRequest => ProtocolFamily::Logistics,
595            PduType::ResupplyOffer => ProtocolFamily::Logistics,
596            PduType::ResupplyReceived => ProtocolFamily::Logistics,
597            PduType::ResupplyCancel => ProtocolFamily::Logistics,
598            PduType::RepairComplete => ProtocolFamily::Logistics,
599            PduType::RepairResponse => ProtocolFamily::Logistics,
600            PduType::CreateEntity => ProtocolFamily::SimulationManagement,
601            PduType::RemoveEntity => ProtocolFamily::SimulationManagement,
602            PduType::StartResume => ProtocolFamily::SimulationManagement,
603            PduType::StopFreeze => ProtocolFamily::SimulationManagement,
604            PduType::Acknowledge => ProtocolFamily::SimulationManagement,
605            PduType::ActionRequest => ProtocolFamily::SimulationManagement,
606            PduType::ActionResponse => ProtocolFamily::SimulationManagement,
607            PduType::DataQuery => ProtocolFamily::SimulationManagement,
608            PduType::SetData => ProtocolFamily::SimulationManagement,
609            PduType::Data => ProtocolFamily::SimulationManagement,
610            PduType::EventReport => ProtocolFamily::SimulationManagement,
611            PduType::Comment => ProtocolFamily::SimulationManagement,
612            PduType::ElectromagneticEmission => ProtocolFamily::DistributedEmissionRegeneration,
613            PduType::Designator => ProtocolFamily::DistributedEmissionRegeneration,
614            PduType::Transmitter => ProtocolFamily::RadioCommunications,
615            PduType::Signal => ProtocolFamily::RadioCommunications,
616            PduType::Receiver => ProtocolFamily::RadioCommunications,
617            PduType::IFF => ProtocolFamily::DistributedEmissionRegeneration,
618            PduType::UnderwaterAcoustic => ProtocolFamily::DistributedEmissionRegeneration,
619            PduType::SupplementalEmissionEntityState => {
620                ProtocolFamily::DistributedEmissionRegeneration
621            }
622            PduType::IntercomSignal => ProtocolFamily::RadioCommunications,
623            PduType::IntercomControl => ProtocolFamily::RadioCommunications,
624            PduType::AggregateState => ProtocolFamily::EntityManagement,
625            PduType::IsGroupOf => ProtocolFamily::EntityManagement,
626            PduType::TransferOwnership => ProtocolFamily::EntityManagement,
627            PduType::IsPartOf => ProtocolFamily::EntityManagement,
628            PduType::MinefieldState => ProtocolFamily::Minefield,
629            PduType::MinefieldQuery => ProtocolFamily::Minefield,
630            PduType::MinefieldData => ProtocolFamily::Minefield,
631            PduType::MinefieldResponseNACK => ProtocolFamily::Minefield,
632            PduType::EnvironmentalProcess => ProtocolFamily::SyntheticEnvironment,
633            PduType::GriddedData => ProtocolFamily::SyntheticEnvironment,
634            PduType::PointObjectState => ProtocolFamily::SyntheticEnvironment,
635            PduType::LinearObjectState => ProtocolFamily::SyntheticEnvironment,
636            PduType::ArealObjectState => ProtocolFamily::SyntheticEnvironment,
637            PduType::TSPI => ProtocolFamily::LiveEntity_LE_InformationInteraction,
638            PduType::Appearance => ProtocolFamily::LiveEntity_LE_InformationInteraction,
639            PduType::ArticulatedParts => ProtocolFamily::LiveEntity_LE_InformationInteraction,
640            PduType::LEFire => ProtocolFamily::LiveEntity_LE_InformationInteraction,
641            PduType::LEDetonation => ProtocolFamily::LiveEntity_LE_InformationInteraction,
642            PduType::CreateEntityR => ProtocolFamily::SimulationManagementWithReliability,
643            PduType::RemoveEntityR => ProtocolFamily::SimulationManagementWithReliability,
644            PduType::StartResumeR => ProtocolFamily::SimulationManagementWithReliability,
645            PduType::StopFreezeR => ProtocolFamily::SimulationManagementWithReliability,
646            PduType::AcknowledgeR => ProtocolFamily::SimulationManagementWithReliability,
647            PduType::ActionRequestR => ProtocolFamily::SimulationManagementWithReliability,
648            PduType::ActionResponseR => ProtocolFamily::SimulationManagementWithReliability,
649            PduType::DataQueryR => ProtocolFamily::SimulationManagementWithReliability,
650            PduType::SetDataR => ProtocolFamily::SimulationManagementWithReliability,
651            PduType::DataR => ProtocolFamily::SimulationManagementWithReliability,
652            PduType::EventReportR => ProtocolFamily::SimulationManagementWithReliability,
653            PduType::CommentR => ProtocolFamily::SimulationManagementWithReliability,
654            PduType::RecordR => ProtocolFamily::SimulationManagementWithReliability,
655            PduType::SetRecordR => ProtocolFamily::SimulationManagementWithReliability,
656            PduType::RecordQueryR => ProtocolFamily::SimulationManagementWithReliability,
657            PduType::CollisionElastic => ProtocolFamily::EntityInformationInteraction,
658            PduType::EntityStateUpdate => ProtocolFamily::EntityInformationInteraction,
659            PduType::DirectedEnergyFire => ProtocolFamily::Warfare,
660            PduType::EntityDamageStatus => ProtocolFamily::Warfare,
661            PduType::InformationOperationsAction => ProtocolFamily::InformationOperations,
662            PduType::InformationOperationsReport => ProtocolFamily::InformationOperations,
663            PduType::Attribute => ProtocolFamily::EntityInformationInteraction,
664            PduType::Unspecified(unspecified_value) => {
665                ProtocolFamily::Unspecified(unspecified_value)
666            }
667        }
668    }
669}
670
671/// 6.2.80 Simulation Address record
672#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)]
673#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
674pub struct SimulationAddress {
675    pub site_id: u16,
676    pub application_id: u16,
677}
678
679impl SimulationAddress {
680    #[must_use]
681    pub fn new(site_id: u16, application_id: u16) -> Self {
682        SimulationAddress {
683            site_id,
684            application_id,
685        }
686    }
687}
688
689impl Default for SimulationAddress {
690    fn default() -> Self {
691        Self {
692            site_id: NO_SITE,
693            application_id: NO_APPLIC,
694        }
695    }
696}
697
698impl Display for SimulationAddress {
699    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
700        write!(f, "{}:{}", self.site_id, self.application_id)
701    }
702}
703
704#[allow(clippy::get_first)]
705impl TryFrom<&[&str]> for SimulationAddress {
706    type Error = DisError;
707
708    fn try_from(value: &[&str]) -> Result<Self, Self::Error> {
709        const NUM_DIGITS: usize = 2;
710        if value.len() != NUM_DIGITS {
711            return Err(DisError::ParseError(format!(
712                "SimulationAddress string pattern does not contain precisely {NUM_DIGITS} digits"
713            )));
714        }
715        Ok(Self {
716            site_id: value
717                .get(0)
718                .expect("Impossible - checked for correct number of digits")
719                .parse::<u16>()
720                .map_err(|_| DisError::ParseError("Invalid site id digit".to_string()))?,
721            application_id: value
722                .get(1)
723                .expect("Impossible - checked for correct number of digits")
724                .parse::<u16>()
725                .map_err(|_| DisError::ParseError("Invalid application id digit".to_string()))?,
726        })
727    }
728}
729
730impl FromStr for SimulationAddress {
731    type Err = DisError;
732
733    fn from_str(s: &str) -> Result<Self, Self::Err> {
734        let ss = s.split(':').collect::<Vec<&str>>();
735        Self::try_from(ss.as_slice())
736    }
737}
738
739impl TryFrom<&str> for SimulationAddress {
740    type Error = DisError;
741
742    fn try_from(value: &str) -> Result<Self, Self::Error> {
743        SimulationAddress::from_str(value)
744    }
745}
746
747impl TryFrom<String> for SimulationAddress {
748    type Error = DisError;
749
750    fn try_from(value: String) -> Result<Self, Self::Error> {
751        TryFrom::<&str>::try_from(&value)
752    }
753}
754
755/// 6.2.28 Entity Identifier record
756/// 6.2.81 Simulation Identifier record
757#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
758#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
759pub struct EntityId {
760    pub simulation_address: SimulationAddress,
761    pub entity_id: u16,
762}
763
764impl EntityId {
765    #[must_use]
766    pub fn new(site_id: u16, application_id: u16, entity_id: u16) -> Self {
767        Self {
768            simulation_address: SimulationAddress {
769                site_id,
770                application_id,
771            },
772            entity_id,
773        }
774    }
775
776    #[must_use]
777    pub fn new_sim_address(simulation_address: SimulationAddress, entity_id: u16) -> Self {
778        Self {
779            simulation_address,
780            entity_id,
781        }
782    }
783
784    #[must_use]
785    pub fn new_simulation_identifier(simulation_address: SimulationAddress) -> Self {
786        Self {
787            simulation_address,
788            entity_id: NO_ENTITY,
789        }
790    }
791
792    #[must_use]
793    #[allow(clippy::cast_possible_truncation)]
794    pub fn record_length(&self) -> u16 {
795        SIX_OCTETS as u16
796    }
797}
798
799impl Default for EntityId {
800    fn default() -> Self {
801        Self {
802            simulation_address: SimulationAddress::default(),
803            entity_id: NO_ENTITY,
804        }
805    }
806}
807
808impl Display for EntityId {
809    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
810        write!(f, "{}:{}", self.simulation_address, self.entity_id)
811    }
812}
813
814#[allow(clippy::get_first)]
815impl FromStr for EntityId {
816    type Err = DisError;
817
818    fn from_str(s: &str) -> Result<Self, Self::Err> {
819        const NUM_DIGITS: usize = 3;
820        let mut ss = s.split(':').collect::<Vec<&str>>();
821        if ss.len() != NUM_DIGITS {
822            return Err(DisError::ParseError(format!(
823                "EntityId string pattern does not contain precisely {NUM_DIGITS} digits"
824            )));
825        }
826        let entity_id = ss
827            .pop()
828            .expect("Impossible - checked for correct number of digits")
829            .parse::<u16>()
830            .map_err(|_| DisError::ParseError("Invalid entity id digit".to_string()))?;
831        Ok(Self {
832            simulation_address: ss.as_slice().try_into()?,
833            entity_id,
834        })
835    }
836}
837
838impl TryFrom<&str> for EntityId {
839    type Error = DisError;
840
841    fn try_from(value: &str) -> Result<Self, Self::Error> {
842        EntityId::from_str(value)
843    }
844}
845
846impl TryFrom<String> for EntityId {
847    type Error = DisError;
848
849    fn try_from(value: String) -> Result<Self, Self::Error> {
850        TryFrom::<&str>::try_from(&value)
851    }
852}
853
854#[derive(Copy, Clone, Debug, PartialEq)]
855#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
856pub struct EventId {
857    pub simulation_address: SimulationAddress,
858    pub event_id: u16,
859}
860
861impl EventId {
862    #[must_use]
863    pub fn new(simulation_address: SimulationAddress, event_id: u16) -> Self {
864        Self {
865            simulation_address,
866            event_id,
867        }
868    }
869
870    #[must_use]
871    pub fn new_sim_address(simulation_address: SimulationAddress, event_id: u16) -> Self {
872        Self {
873            simulation_address,
874            event_id,
875        }
876    }
877}
878
879impl Default for EventId {
880    fn default() -> Self {
881        Self {
882            simulation_address: SimulationAddress::default(),
883            event_id: NO_ENTITY,
884        }
885    }
886}
887
888impl Display for EventId {
889    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
890        write!(f, "{}:{}", self.simulation_address, self.event_id)
891    }
892}
893
894#[allow(clippy::get_first)]
895impl FromStr for EventId {
896    type Err = DisError;
897
898    fn from_str(s: &str) -> Result<Self, Self::Err> {
899        const NUM_DIGITS: usize = 3;
900        let mut ss = s.split(':').collect::<Vec<&str>>();
901        if ss.len() != NUM_DIGITS {
902            return Err(DisError::ParseError(format!(
903                "EventId string pattern does not contain precisely {NUM_DIGITS} digits"
904            )));
905        }
906        let event_id = ss
907            .pop()
908            .expect("Impossible - checked for correct number of digits")
909            .parse::<u16>()
910            .map_err(|_| DisError::ParseError("Invalid event id digit".to_string()))?;
911        Ok(Self {
912            simulation_address: ss.as_slice().try_into()?,
913            event_id,
914        })
915    }
916}
917
918impl TryFrom<&str> for EventId {
919    type Error = DisError;
920
921    fn try_from(value: &str) -> Result<Self, Self::Error> {
922        EventId::from_str(value)
923    }
924}
925
926impl TryFrom<String> for EventId {
927    type Error = DisError;
928
929    fn try_from(value: String) -> Result<Self, Self::Error> {
930        TryFrom::<&str>::try_from(&value)
931    }
932}
933
934/// 6.2.96 Vector record
935/// 6.2.7 Angular Velocity Vector record
936#[derive(Copy, Clone, Default, Debug, PartialEq)]
937#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
938pub struct VectorF32 {
939    pub first_vector_component: f32,
940    pub second_vector_component: f32,
941    pub third_vector_component: f32,
942}
943
944impl VectorF32 {
945    #[must_use]
946    pub fn new(first: f32, second: f32, third: f32) -> Self {
947        VectorF32 {
948            first_vector_component: first,
949            second_vector_component: second,
950            third_vector_component: third,
951        }
952    }
953
954    #[must_use]
955    pub fn with_first(mut self, first: f32) -> Self {
956        self.first_vector_component = first;
957        self
958    }
959
960    #[must_use]
961    pub fn with_second(mut self, second: f32) -> Self {
962        self.second_vector_component = second;
963        self
964    }
965
966    #[must_use]
967    pub fn with_third(mut self, third: f32) -> Self {
968        self.third_vector_component = third;
969        self
970    }
971}
972
973// TODO rename Location to World Coordinate
974/// 6.2.98 World Coordinates record
975#[derive(Copy, Clone, Debug, Default, PartialEq)]
976#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
977pub struct Location {
978    pub x_coordinate: f64,
979    pub y_coordinate: f64,
980    pub z_coordinate: f64,
981}
982
983impl Location {
984    #[must_use]
985    pub fn new(x: f64, y: f64, z: f64) -> Self {
986        Location {
987            x_coordinate: x,
988            y_coordinate: y,
989            z_coordinate: z,
990        }
991    }
992
993    #[must_use]
994    pub fn with_x(mut self, x: f64) -> Self {
995        self.x_coordinate = x;
996        self
997    }
998
999    #[must_use]
1000    pub fn with_y(mut self, y: f64) -> Self {
1001        self.y_coordinate = y;
1002        self
1003    }
1004
1005    #[must_use]
1006    pub fn with_z(mut self, z: f64) -> Self {
1007        self.z_coordinate = z;
1008        self
1009    }
1010}
1011
1012// TODO rename Orientation to EulerAngle
1013/// 6.2.32 Euler Angles record
1014#[derive(Copy, Clone, Default, Debug, PartialEq)]
1015#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1016pub struct Orientation {
1017    pub psi: f32,
1018    pub theta: f32,
1019    pub phi: f32,
1020}
1021
1022impl Orientation {
1023    #[must_use]
1024    #[allow(clippy::similar_names)]
1025    pub fn new(psi: f32, theta: f32, phi: f32) -> Self {
1026        Orientation { psi, theta, phi }
1027    }
1028
1029    #[must_use]
1030    pub fn with_psi(mut self, psi: f32) -> Self {
1031        self.psi = psi;
1032        self
1033    }
1034
1035    #[must_use]
1036    pub fn with_theta(mut self, theta: f32) -> Self {
1037        self.theta = theta;
1038        self
1039    }
1040
1041    #[must_use]
1042    pub fn with_phi(mut self, phi: f32) -> Self {
1043        self.phi = phi;
1044        self
1045    }
1046}
1047
1048/// 6.2.30 Entity Type record
1049#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
1050#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1051pub struct EntityType {
1052    pub kind: EntityKind,
1053    pub domain: PlatformDomain,
1054    pub country: Country,
1055    pub category: u8,
1056    pub subcategory: u8,
1057    pub specific: u8,
1058    pub extra: u8,
1059}
1060
1061impl EntityType {
1062    #[must_use]
1063    pub fn with_kind(mut self, kind: EntityKind) -> Self {
1064        self.kind = kind;
1065        self
1066    }
1067
1068    #[must_use]
1069    pub fn with_domain(mut self, domain: PlatformDomain) -> Self {
1070        self.domain = domain;
1071        self
1072    }
1073
1074    #[must_use]
1075    pub fn with_country(mut self, country: Country) -> Self {
1076        self.country = country;
1077        self
1078    }
1079
1080    #[must_use]
1081    pub fn with_category(mut self, category: u8) -> Self {
1082        self.category = category;
1083        self
1084    }
1085
1086    #[must_use]
1087    pub fn with_subcategory(mut self, subcategory: u8) -> Self {
1088        self.subcategory = subcategory;
1089        self
1090    }
1091
1092    #[must_use]
1093    pub fn with_specific(mut self, specific: u8) -> Self {
1094        self.specific = specific;
1095        self
1096    }
1097
1098    #[must_use]
1099    pub fn with_extra(mut self, extra: u8) -> Self {
1100        self.extra = extra;
1101        self
1102    }
1103
1104    #[must_use]
1105    #[allow(clippy::cast_possible_truncation)]
1106    pub fn record_length(&self) -> u16 {
1107        EIGHT_OCTETS as u16
1108    }
1109}
1110
1111impl Display for EntityType {
1112    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1113        write!(
1114            f,
1115            "{}:{}:{}:{}:{}:{}:{}",
1116            u8::from(self.kind),
1117            u8::from(self.domain),
1118            u16::from(self.country),
1119            self.category,
1120            self.subcategory,
1121            self.specific,
1122            self.extra
1123        )
1124    }
1125}
1126
1127#[allow(clippy::get_first)]
1128impl FromStr for EntityType {
1129    type Err = DisError;
1130
1131    fn from_str(s: &str) -> Result<Self, Self::Err> {
1132        const NUM_DIGITS: usize = 7;
1133        let ss = s.split(':').collect::<Vec<&str>>();
1134        if ss.len() != NUM_DIGITS {
1135            return Err(DisError::ParseError(format!(
1136                "EntityType string pattern does not contain precisely {NUM_DIGITS} digits"
1137            )));
1138        }
1139        Ok(Self {
1140            kind: ss
1141                .get(0)
1142                .expect("Impossible - checked for correct number of digits")
1143                .parse::<u8>()
1144                .map_err(|_| DisError::ParseError("Invalid kind digit".to_string()))?
1145                .into(),
1146            domain: ss
1147                .get(1)
1148                .expect("Impossible - checked for correct number of digits")
1149                .parse::<u8>()
1150                .map_err(|_| DisError::ParseError("Invalid domain digit".to_string()))?
1151                .into(),
1152            country: ss
1153                .get(2)
1154                .expect("Impossible - checked for correct number of digits")
1155                .parse::<u16>()
1156                .map_err(|_| DisError::ParseError("Invalid country digit".to_string()))?
1157                .into(),
1158            category: ss
1159                .get(3)
1160                .expect("Impossible - checked for correct number of digits")
1161                .parse::<u8>()
1162                .map_err(|_| DisError::ParseError("Invalid category digit".to_string()))?,
1163            subcategory: ss
1164                .get(4)
1165                .expect("Impossible - checked for correct number of digits")
1166                .parse::<u8>()
1167                .map_err(|_| DisError::ParseError("Invalid subcategory digit".to_string()))?,
1168            specific: ss
1169                .get(5)
1170                .expect("Impossible - checked for correct number of digits")
1171                .parse::<u8>()
1172                .map_err(|_| DisError::ParseError("Invalid specific digit".to_string()))?,
1173            extra: ss
1174                .get(6)
1175                .expect("Impossible - checked for correct number of digits")
1176                .parse::<u8>()
1177                .map_err(|_| DisError::ParseError("Invalid extra digit".to_string()))?,
1178        })
1179    }
1180}
1181
1182impl TryFrom<&str> for EntityType {
1183    type Error = DisError;
1184
1185    fn try_from(value: &str) -> Result<Self, Self::Error> {
1186        EntityType::from_str(value)
1187    }
1188}
1189
1190impl TryFrom<String> for EntityType {
1191    type Error = DisError;
1192
1193    fn try_from(value: String) -> Result<Self, Self::Error> {
1194        TryFrom::<&str>::try_from(&value)
1195    }
1196}
1197
1198/// 6.2.19 Descriptor records
1199#[derive(Clone, Debug, PartialEq)]
1200#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1201pub enum DescriptorRecord {
1202    /// 6.2.19.2 Munition Descriptor record
1203    #[cfg_attr(feature = "serde", serde(rename = "munition"))]
1204    Munition {
1205        entity_type: EntityType,
1206        munition: MunitionDescriptor,
1207    },
1208    /// 6.2.19.4 Expendable Descriptor record
1209    #[cfg_attr(feature = "serde", serde(rename = "expendable"))]
1210    Expendable { entity_type: EntityType },
1211    /// 6.2.19.3 Explosion Descriptor record
1212    #[cfg_attr(feature = "serde", serde(rename = "explosion"))]
1213    Explosion {
1214        entity_type: EntityType,
1215        explosive_material: ExplosiveMaterialCategories,
1216        explosive_force: f32,
1217    },
1218}
1219
1220impl DescriptorRecord {
1221    #[must_use]
1222    pub fn new_munition(entity_type: EntityType, munition: MunitionDescriptor) -> Self {
1223        DescriptorRecord::Munition {
1224            entity_type,
1225            munition,
1226        }
1227    }
1228
1229    #[must_use]
1230    pub fn new_expendable(entity_type: EntityType) -> Self {
1231        DescriptorRecord::Expendable { entity_type }
1232    }
1233
1234    #[must_use]
1235    pub fn new_explosion(
1236        entity_type: EntityType,
1237        explosive_material: ExplosiveMaterialCategories,
1238        explosive_force: f32,
1239    ) -> Self {
1240        DescriptorRecord::Explosion {
1241            entity_type,
1242            explosive_material,
1243            explosive_force,
1244        }
1245    }
1246}
1247
1248impl Default for DescriptorRecord {
1249    fn default() -> Self {
1250        DescriptorRecord::new_munition(EntityType::default(), MunitionDescriptor::default())
1251    }
1252}
1253
1254/// 6.2.19.2 Munition Descriptor record
1255#[derive(Clone, Default, Debug, PartialEq)]
1256#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1257pub struct MunitionDescriptor {
1258    pub warhead: MunitionDescriptorWarhead,
1259    pub fuse: MunitionDescriptorFuse,
1260    pub quantity: u16,
1261    pub rate: u16,
1262}
1263
1264impl MunitionDescriptor {
1265    #[must_use]
1266    pub fn with_warhead(mut self, warhead: MunitionDescriptorWarhead) -> Self {
1267        self.warhead = warhead;
1268        self
1269    }
1270
1271    #[must_use]
1272    pub fn with_fuse(mut self, fuse: MunitionDescriptorFuse) -> Self {
1273        self.fuse = fuse;
1274        self
1275    }
1276
1277    #[must_use]
1278    pub fn with_quantity(mut self, quantity: u16) -> Self {
1279        self.quantity = quantity;
1280        self
1281    }
1282
1283    #[must_use]
1284    pub fn with_rate(mut self, rate: u16) -> Self {
1285        self.rate = rate;
1286        self
1287    }
1288}
1289
1290/// Custom type to model timestamps, just wrapping a `u32` value. By default,
1291/// the `PduHeader` uses this type. Users can decide to convert the raw value
1292/// to a `DisTimeStamp`, which models the Absolute and Relative interpretations of the value as defined by the standard.
1293///
1294/// The standard defines the value to be a number of DIS time units since the top of the hour.
1295/// There are 2^31 - 1 time units in an hour.
1296/// This results in each time unit representing exactly 3600/(2^31) seconds (approximately 1.67638063 μs).
1297///
1298/// This raw timestamp could also be interpreted as a Unix timestamp, or something else
1299/// like a monotonically increasing timestamp. This is left up to the client applications of the protocol _by this library_.
1300#[derive(Copy, Clone, Default, Debug, PartialEq)]
1301#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1302pub struct TimeStamp {
1303    pub raw_timestamp: u32,
1304}
1305
1306impl TimeStamp {
1307    #[must_use]
1308    pub fn new(raw_timestamp: u32) -> Self {
1309        Self { raw_timestamp }
1310    }
1311}
1312
1313impl From<u32> for TimeStamp {
1314    fn from(value: u32) -> Self {
1315        Self {
1316            raw_timestamp: value,
1317        }
1318    }
1319}
1320
1321impl From<TimeStamp> for u32 {
1322    fn from(value: TimeStamp) -> Self {
1323        value.raw_timestamp
1324    }
1325}
1326
1327impl From<DisTimeStamp> for TimeStamp {
1328    fn from(value: DisTimeStamp) -> Self {
1329        let raw_timestamp = match value {
1330            DisTimeStamp::Absolute {
1331                units_past_the_hour,
1332                nanoseconds_past_the_hour: _,
1333            } => (units_past_the_hour << 1) | LEAST_SIGNIFICANT_BIT,
1334            DisTimeStamp::Relative {
1335                units_past_the_hour,
1336                nanoseconds_past_the_hour: _,
1337            } => units_past_the_hour << 1,
1338        };
1339
1340        Self { raw_timestamp }
1341    }
1342}
1343
1344/// A timestamp type that models the timestamp mechanism as described in the
1345/// DIS standard (section 6.2.88 Timestamp). This timestamp interprets an u32 value
1346/// as an Absolute or a Relative timestamp based on the Least Significant Bit.
1347/// The remaining (upper) bits represent the units of time passed since the
1348/// beginning of the current hour in the selected time reference.
1349/// The `DisTimeStamp` stores both the units past the hour, and a conversion to
1350/// nanoseconds past the hour.
1351#[derive(Debug)]
1352pub enum DisTimeStamp {
1353    Absolute {
1354        units_past_the_hour: u32,
1355        nanoseconds_past_the_hour: u32,
1356    },
1357    Relative {
1358        units_past_the_hour: u32,
1359        nanoseconds_past_the_hour: u32,
1360    },
1361}
1362
1363impl DisTimeStamp {
1364    #[must_use]
1365    pub fn new_absolute_from_secs(seconds_past_the_hour: u32) -> Self {
1366        let nanoseconds_past_the_hour = DisTimeStamp::seconds_to_nanoseconds(seconds_past_the_hour);
1367        let units_past_the_hour =
1368            DisTimeStamp::nanoseconds_to_dis_time_units(nanoseconds_past_the_hour);
1369        Self::Absolute {
1370            units_past_the_hour,
1371            nanoseconds_past_the_hour,
1372        }
1373    }
1374
1375    #[must_use]
1376    pub fn new_relative_from_secs(seconds_past_the_hour: u32) -> Self {
1377        let nanoseconds_past_the_hour = DisTimeStamp::seconds_to_nanoseconds(seconds_past_the_hour);
1378        let units_past_the_hour =
1379            DisTimeStamp::nanoseconds_to_dis_time_units(nanoseconds_past_the_hour);
1380        Self::Relative {
1381            units_past_the_hour,
1382            nanoseconds_past_the_hour,
1383        }
1384    }
1385
1386    #[must_use]
1387    pub fn new_absolute_from_units(units_past_the_hour: u32) -> Self {
1388        Self::Absolute {
1389            units_past_the_hour,
1390            nanoseconds_past_the_hour: Self::dis_time_units_to_nanoseconds(units_past_the_hour),
1391        }
1392    }
1393
1394    #[must_use]
1395    pub fn new_relative_from_units(units_past_the_hour: u32) -> Self {
1396        Self::Relative {
1397            units_past_the_hour,
1398            nanoseconds_past_the_hour: Self::dis_time_units_to_nanoseconds(units_past_the_hour),
1399        }
1400    }
1401
1402    /// Helper function to convert seconds to nanoseconds
1403    fn seconds_to_nanoseconds(seconds: u32) -> u32 {
1404        seconds * 1_000_000
1405    }
1406
1407    /// Helper function to convert nanoseconds pas the hour to DIS Time Units past the hour.
1408    #[allow(clippy::cast_possible_truncation)]
1409    fn nanoseconds_to_dis_time_units(nanoseconds_past_the_hour: u32) -> u32 {
1410        (nanoseconds_past_the_hour as f32 / NANOSECONDS_PER_TIME_UNIT) as u32
1411    }
1412
1413    #[allow(clippy::cast_possible_truncation)]
1414    fn dis_time_units_to_nanoseconds(dis_time_units: u32) -> u32 {
1415        (dis_time_units as f32 * NANOSECONDS_PER_TIME_UNIT) as u32
1416    }
1417}
1418
1419impl From<u32> for DisTimeStamp {
1420    fn from(value: u32) -> Self {
1421        let absolute_bit = (value & LEAST_SIGNIFICANT_BIT) == LEAST_SIGNIFICANT_BIT;
1422        let units_past_the_hour = value >> 1;
1423        let nanoseconds_past_the_hour =
1424            (units_past_the_hour as f32 * NANOSECONDS_PER_TIME_UNIT) as u32;
1425
1426        if absolute_bit {
1427            Self::Absolute {
1428                units_past_the_hour,
1429                nanoseconds_past_the_hour,
1430            }
1431        } else {
1432            Self::Relative {
1433                units_past_the_hour,
1434                nanoseconds_past_the_hour,
1435            }
1436        }
1437    }
1438}
1439
1440impl From<TimeStamp> for DisTimeStamp {
1441    fn from(value: TimeStamp) -> Self {
1442        DisTimeStamp::from(value.raw_timestamp)
1443    }
1444}
1445
1446impl From<DisTimeStamp> for u32 {
1447    fn from(value: DisTimeStamp) -> Self {
1448        TimeStamp::from(value).raw_timestamp
1449    }
1450}
1451
1452/// 6.2.14 Clock Time record
1453#[derive(Copy, Clone, Default, Debug, PartialEq)]
1454#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1455pub struct ClockTime {
1456    pub hour: i32,
1457    pub time_past_hour: u32,
1458}
1459
1460impl ClockTime {
1461    #[must_use]
1462    pub fn new(hour: i32, time_past_hour: u32) -> Self {
1463        Self {
1464            hour,
1465            time_past_hour,
1466        }
1467    }
1468}
1469
1470/// 6.2.18 Datum Specification record
1471#[derive(Clone, Default, Debug, PartialEq)]
1472pub struct DatumSpecification {
1473    pub fixed_datum_records: Vec<FixedDatum>,
1474    pub variable_datum_records: Vec<VariableDatum>,
1475}
1476
1477impl DatumSpecification {
1478    #[must_use]
1479    pub fn new(
1480        fixed_datum_records: Vec<FixedDatum>,
1481        variable_datum_records: Vec<VariableDatum>,
1482    ) -> Self {
1483        Self {
1484            fixed_datum_records,
1485            variable_datum_records,
1486        }
1487    }
1488}
1489
1490pub const FIXED_DATUM_LENGTH: u16 = 8;
1491pub const BASE_VARIABLE_DATUM_LENGTH: u16 = 8;
1492
1493/// 6.2.37 Fixed Datum record
1494#[derive(Clone, Debug, PartialEq)]
1495#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1496pub struct FixedDatum {
1497    pub datum_id: VariableRecordType,
1498    pub datum_value: u32,
1499}
1500
1501impl FixedDatum {
1502    #[must_use]
1503    pub fn new(datum_id: VariableRecordType, datum_value: u32) -> Self {
1504        Self {
1505            datum_id,
1506            datum_value,
1507        }
1508    }
1509}
1510
1511/// 6.2.93 Variable Datum record
1512#[derive(Clone, Debug, PartialEq)]
1513#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1514pub struct VariableDatum {
1515    pub datum_id: VariableRecordType,
1516    pub datum_value: Vec<u8>,
1517}
1518
1519impl VariableDatum {
1520    #[must_use]
1521    pub fn new(datum_id: VariableRecordType, datum_value: Vec<u8>) -> Self {
1522        Self {
1523            datum_id,
1524            datum_value,
1525        }
1526    }
1527}
1528
1529/// Struct to hold the length (in bits or bytes) of parts of a padded record.
1530/// Such that `data_length` + `padding_length` = `record_length`.
1531#[derive(Debug)]
1532pub struct PaddedRecordLengths {
1533    pub data_length: usize,
1534    pub padding_length: usize,
1535    pub record_length: usize,
1536}
1537
1538impl PaddedRecordLengths {
1539    #[must_use]
1540    pub fn new(
1541        data_length_bytes: usize,
1542        padding_length_bytes: usize,
1543        record_length_bytes: usize,
1544    ) -> Self {
1545        Self {
1546            data_length: data_length_bytes,
1547            padding_length: padding_length_bytes,
1548            record_length: record_length_bytes,
1549        }
1550    }
1551}
1552
1553/// Calculates the length of a data record when padded to `pad_to_num` octets or bits,
1554/// given that the length of the data in the record is `data_length`.
1555/// The function returns a tuple consisting of the length of the data, the length of the padding, and the total (padded) length of the record.
1556///
1557/// For example, a piece of data of 12 bytes that needs to be aligned to 16 bytes will have a
1558/// data length of 12 bytes, a padding of 4 bytes and a final length of 12 + 4 bytes. The function will return 16 in this case.
1559pub(crate) fn length_padded_to_num(data_length: usize, pad_to_num: usize) -> PaddedRecordLengths {
1560    let data_remaining = data_length % pad_to_num;
1561    let padding_num = if data_remaining == 0 {
1562        0usize
1563    } else {
1564        pad_to_num - data_remaining
1565    };
1566    let record_length = data_length + padding_num;
1567    assert_eq!(
1568        record_length % pad_to_num,
1569        NO_REMAINDER,
1570        "The length for the data record is not aligned to {pad_to_num} octets. Data length is {data_length} octets."
1571    );
1572
1573    PaddedRecordLengths::new(data_length, padding_num, record_length)
1574}
1575
1576/// 6.2.94 Variable Parameter record
1577#[derive(Clone, Debug, PartialEq)]
1578#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1579pub enum VariableParameter {
1580    Articulated(ArticulatedPart),
1581    Attached(AttachedPart),
1582    Separation(SeparationParameter),
1583    EntityType(EntityTypeParameter),
1584    EntityAssociation(EntityAssociationParameter),
1585    Unspecified(u8, [u8; FIFTEEN_OCTETS]),
1586}
1587
1588/// 6.2.94.2 Articulated Part VP record
1589#[derive(Copy, Clone, Debug, Default, PartialEq)]
1590#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1591pub struct ArticulatedPart {
1592    pub change_indicator: ChangeIndicator,
1593    pub attachment_id: u16,
1594    pub type_metric: ArticulatedPartsTypeMetric,
1595    pub type_class: ArticulatedPartsTypeClass,
1596    pub parameter_value: f32,
1597}
1598
1599impl ArticulatedPart {
1600    #[must_use]
1601    pub fn with_change_indicator(mut self, change_indicator: ChangeIndicator) -> Self {
1602        self.change_indicator = change_indicator;
1603        self
1604    }
1605
1606    #[must_use]
1607    pub fn with_attachment_id(mut self, attachment_id: u16) -> Self {
1608        self.attachment_id = attachment_id;
1609        self
1610    }
1611
1612    #[must_use]
1613    pub fn with_type_metric(mut self, type_metric: ArticulatedPartsTypeMetric) -> Self {
1614        self.type_metric = type_metric;
1615        self
1616    }
1617
1618    #[must_use]
1619    pub fn with_type_class(mut self, type_class: ArticulatedPartsTypeClass) -> Self {
1620        self.type_class = type_class;
1621        self
1622    }
1623
1624    #[must_use]
1625    pub fn with_parameter_value(mut self, parameter_value: f32) -> Self {
1626        self.parameter_value = parameter_value;
1627        self
1628    }
1629
1630    #[must_use]
1631    pub fn to_variable_parameter(self) -> VariableParameter {
1632        VariableParameter::Articulated(self)
1633    }
1634}
1635
1636/// 6.2.94.3 Attached Part VP record
1637#[derive(Copy, Clone, Debug, Default, PartialEq)]
1638#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1639pub struct AttachedPart {
1640    pub detached_indicator: AttachedPartDetachedIndicator,
1641    pub attachment_id: u16,
1642    pub parameter_type: AttachedParts,
1643    pub attached_part_type: EntityType,
1644}
1645
1646impl AttachedPart {
1647    #[must_use]
1648    pub fn with_detached_indicator(
1649        mut self,
1650        detached_indicator: AttachedPartDetachedIndicator,
1651    ) -> Self {
1652        self.detached_indicator = detached_indicator;
1653        self
1654    }
1655
1656    #[must_use]
1657    pub fn with_attachment_id(mut self, attachment_id: u16) -> Self {
1658        self.attachment_id = attachment_id;
1659        self
1660    }
1661
1662    #[must_use]
1663    pub fn with_parameter_type(mut self, parameter_type: AttachedParts) -> Self {
1664        self.parameter_type = parameter_type;
1665        self
1666    }
1667
1668    #[must_use]
1669    pub fn with_attached_part_type(mut self, attached_part_type: EntityType) -> Self {
1670        self.attached_part_type = attached_part_type;
1671        self
1672    }
1673
1674    #[must_use]
1675    pub fn to_variable_parameter(self) -> VariableParameter {
1676        VariableParameter::Attached(self)
1677    }
1678}
1679
1680/// 6.2.94.6 Separation VP record
1681#[derive(Copy, Clone, Debug, Default, PartialEq)]
1682#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1683pub struct SeparationParameter {
1684    pub reason: SeparationReasonForSeparation,
1685    pub pre_entity_indicator: SeparationPreEntityIndicator,
1686    pub parent_entity_id: EntityId,
1687    pub station_name: StationName,
1688    pub station_number: u16,
1689}
1690
1691impl SeparationParameter {
1692    #[must_use]
1693    pub fn with_reason(mut self, reason: SeparationReasonForSeparation) -> Self {
1694        self.reason = reason;
1695        self
1696    }
1697
1698    #[must_use]
1699    pub fn with_pre_entity_indicator(
1700        mut self,
1701        pre_entity_indicator: SeparationPreEntityIndicator,
1702    ) -> Self {
1703        self.pre_entity_indicator = pre_entity_indicator;
1704        self
1705    }
1706
1707    #[must_use]
1708    pub fn with_parent_entity_id(mut self, parent_entity_id: EntityId) -> Self {
1709        self.parent_entity_id = parent_entity_id;
1710        self
1711    }
1712
1713    #[must_use]
1714    pub fn with_station_name(mut self, station_name: StationName) -> Self {
1715        self.station_name = station_name;
1716        self
1717    }
1718
1719    #[must_use]
1720    pub fn with_station_number(mut self, station_number: u16) -> Self {
1721        self.station_number = station_number;
1722        self
1723    }
1724
1725    #[must_use]
1726    pub fn to_variable_parameter(self) -> VariableParameter {
1727        VariableParameter::Separation(self)
1728    }
1729}
1730
1731/// 6.2.94.5 Entity Type VP record
1732#[derive(Copy, Clone, Debug, Default, PartialEq)]
1733#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1734pub struct EntityTypeParameter {
1735    pub change_indicator: ChangeIndicator,
1736    pub entity_type: EntityType,
1737}
1738
1739impl EntityTypeParameter {
1740    #[must_use]
1741    pub fn with_change_indicator(mut self, change_indicator: ChangeIndicator) -> Self {
1742        self.change_indicator = change_indicator;
1743        self
1744    }
1745
1746    #[must_use]
1747    pub fn with_entity_type(mut self, entity_type: EntityType) -> Self {
1748        self.entity_type = entity_type;
1749        self
1750    }
1751
1752    #[must_use]
1753    pub fn to_variable_parameter(self) -> VariableParameter {
1754        VariableParameter::EntityType(self)
1755    }
1756}
1757
1758/// 6.2.94.4 Entity Association VP Record
1759#[derive(Copy, Clone, Debug, Default, PartialEq)]
1760#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1761pub struct EntityAssociationParameter {
1762    pub change_indicator: ChangeIndicator,
1763    pub association_status: EntityAssociationAssociationStatus,
1764    pub association_type: EntityAssociationPhysicalAssociationType,
1765    pub entity_id: EntityId,
1766    pub own_station_location: StationName,
1767    pub physical_connection_type: EntityAssociationPhysicalConnectionType,
1768    pub group_member_type: EntityAssociationGroupMemberType,
1769    pub group_number: u16,
1770}
1771
1772impl EntityAssociationParameter {
1773    #[must_use]
1774    pub fn with_change_indicator(mut self, change_indicator: ChangeIndicator) -> Self {
1775        self.change_indicator = change_indicator;
1776        self
1777    }
1778
1779    #[must_use]
1780    pub fn with_association_status(
1781        mut self,
1782        association_status: EntityAssociationAssociationStatus,
1783    ) -> Self {
1784        self.association_status = association_status;
1785        self
1786    }
1787
1788    #[must_use]
1789    pub fn with_association_type(
1790        mut self,
1791        association_type: EntityAssociationPhysicalAssociationType,
1792    ) -> Self {
1793        self.association_type = association_type;
1794        self
1795    }
1796
1797    #[must_use]
1798    pub fn with_entity_id(mut self, entity_id: EntityId) -> Self {
1799        self.entity_id = entity_id;
1800        self
1801    }
1802
1803    #[must_use]
1804    pub fn with_own_station_location(mut self, own_station_location: StationName) -> Self {
1805        self.own_station_location = own_station_location;
1806        self
1807    }
1808
1809    #[must_use]
1810    pub fn with_physical_connection_type(
1811        mut self,
1812        physical_connection_type: EntityAssociationPhysicalConnectionType,
1813    ) -> Self {
1814        self.physical_connection_type = physical_connection_type;
1815        self
1816    }
1817
1818    #[must_use]
1819    pub fn with_group_member_type(
1820        mut self,
1821        group_member_type: EntityAssociationGroupMemberType,
1822    ) -> Self {
1823        self.group_member_type = group_member_type;
1824        self
1825    }
1826
1827    #[must_use]
1828    pub fn with_group_number(mut self, group_number: u16) -> Self {
1829        self.group_number = group_number;
1830        self
1831    }
1832
1833    #[must_use]
1834    pub fn to_variable_parameter(self) -> VariableParameter {
1835        VariableParameter::EntityAssociation(self)
1836    }
1837}
1838
1839/// 6.2.11 Beam Data record
1840#[derive(Copy, Clone, Default, Debug, PartialEq)]
1841#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1842pub struct BeamData {
1843    pub azimuth_center: f32,
1844    pub azimuth_sweep: f32,
1845    pub elevation_center: f32,
1846    pub elevation_sweep: f32,
1847    pub sweep_sync: f32,
1848}
1849
1850impl BeamData {
1851    #[must_use]
1852    pub fn new() -> Self {
1853        Self::default()
1854    }
1855
1856    #[must_use]
1857    pub fn with_azimuth_center(mut self, azimuth_center: f32) -> Self {
1858        self.azimuth_center = azimuth_center;
1859        self
1860    }
1861
1862    #[must_use]
1863    pub fn with_azimuth_sweep(mut self, azimuth_sweep: f32) -> Self {
1864        self.azimuth_sweep = azimuth_sweep;
1865        self
1866    }
1867
1868    #[must_use]
1869    pub fn with_elevation_center(mut self, elevation_center: f32) -> Self {
1870        self.elevation_center = elevation_center;
1871        self
1872    }
1873
1874    #[must_use]
1875    pub fn with_elevation_sweep(mut self, elevation_sweep: f32) -> Self {
1876        self.elevation_sweep = elevation_sweep;
1877        self
1878    }
1879
1880    #[must_use]
1881    pub fn with_sweep_sync(mut self, sweep_sync: f32) -> Self {
1882        self.sweep_sync = sweep_sync;
1883        self
1884    }
1885}
1886
1887pub const SUPPLY_QUANTITY_RECORD_LENGTH: u16 = 12;
1888
1889/// 6.2.86 Supply Quantity record
1890#[derive(Clone, Debug, Default, PartialEq)]
1891#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1892pub struct SupplyQuantity {
1893    pub supply_type: EntityType,
1894    pub quantity: f32,
1895}
1896
1897impl SupplyQuantity {
1898    #[must_use]
1899    pub fn with_supply_type(mut self, supply_type: EntityType) -> Self {
1900        self.supply_type = supply_type;
1901        self
1902    }
1903
1904    #[must_use]
1905    pub fn with_quantity(mut self, quantity: f32) -> Self {
1906        self.quantity = quantity;
1907        self
1908    }
1909}
1910
1911pub const BASE_RECORD_SPEC_RECORD_LENGTH: u16 = 16;
1912
1913/// 6.2.73 Record Specification record
1914#[derive(Clone, Debug, Default, PartialEq)]
1915#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1916pub struct RecordSpecification {
1917    pub record_sets: Vec<RecordSet>,
1918}
1919
1920impl RecordSpecification {
1921    #[must_use]
1922    pub fn with_record_set(mut self, record: RecordSet) -> Self {
1923        self.record_sets.push(record);
1924        self
1925    }
1926
1927    #[must_use]
1928    pub fn with_record_sets(mut self, records: Vec<RecordSet>) -> Self {
1929        self.record_sets = records;
1930        self
1931    }
1932}
1933
1934/// Part of 6.2.73 Record Specification record
1935#[derive(Clone, Debug, Default, PartialEq)]
1936#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1937pub struct RecordSet {
1938    pub record_id: VariableRecordType,
1939    pub record_serial_number: u32,
1940    pub record_length_bytes: u16,
1941    pub records: Vec<Vec<u8>>,
1942}
1943
1944impl RecordSet {
1945    #[must_use]
1946    pub fn with_record_id(mut self, record_id: VariableRecordType) -> Self {
1947        self.record_id = record_id;
1948        self
1949    }
1950
1951    #[must_use]
1952    pub fn with_record_serial_number(mut self, record_serial_number: u32) -> Self {
1953        self.record_serial_number = record_serial_number;
1954        self
1955    }
1956
1957    /// Adds `record` to be the Record Values in this `RecordSet`.
1958    /// It is specified in the DIS standard that all Record Values in a `RecordSet` are of the same length.
1959    /// It is up to the caller of the function to ensure only Record Values of same length are added,
1960    /// the length of the last added value is assumed for all previously added.
1961    #[must_use]
1962    #[allow(clippy::cast_possible_truncation)]
1963    pub fn with_record(mut self, record: Vec<u8>) -> Self {
1964        self.record_length_bytes = record.len() as u16;
1965        self.records.push(record);
1966        self
1967    }
1968
1969    /// Sets `records` to be the records in this `RecordSet`.
1970    /// It is specified in the DIS standard that all Record Values in a `RecordSet` are of the same length (i.e., the inner `Vec`).
1971    #[must_use]
1972    pub fn with_records(mut self, records: Vec<Vec<u8>>) -> Self {
1973        self.record_length_bytes = if let Some(record) = records.first() {
1974            record.len()
1975        } else {
1976            0
1977        } as u16;
1978        self.records = records;
1979        self
1980    }
1981}
1982
1983#[cfg(test)]
1984mod tests {
1985    use super::*;
1986
1987    const ENTITY_TYPE_STR: &str = "0:1:2:3:4:5:6";
1988    const ENTITY_TYPE_STR_INVALID: &str = "0,1,2,3,4,5,6";
1989    const ENTITY_TYPE_STR_INVALID_EXTRA: &str = "0:1:2:3:4:5:six";
1990    const ENTITY_TYPE_STR_NOT_SEVEN_DIGITS: &str = "0:1:2:3:4:5";
1991    const ENTITY_TYPE: EntityType = EntityType {
1992        kind: EntityKind::Other,
1993        domain: PlatformDomain::Land,
1994        country: Country::Albania_ALB_,
1995        category: 3,
1996        subcategory: 4,
1997        specific: 5,
1998        extra: 6,
1999    };
2000    const SIMULATION_ADDRESS_STR: &str = "0:1";
2001    const SIMULATION_ADDRESS_STR_INVALID: &str = "0,1";
2002    const SIMULATION_ADDRESS_STR_INVALID_APPLICATION_ID: &str = "0:one";
2003    const SIMULATION_ADDRESS_STR_NOT_TWO_DIGITS: &str = "0";
2004    const SIMULATION_ADDRESS: SimulationAddress = SimulationAddress {
2005        site_id: 0,
2006        application_id: 1,
2007    };
2008    const ENTITY_ID_STR: &str = "0:1:2";
2009    const ENTITY_ID_STR_INVALID: &str = "0,1,2";
2010    const ENTITY_ID_STR_INVALID_ENTITY_ID: &str = "0:1:two";
2011    const ENTITY_ID_STR_NOT_THREE_DIGITS: &str = "0:1";
2012    const ENTITY_ID: EntityId = EntityId {
2013        simulation_address: SIMULATION_ADDRESS,
2014        entity_id: 2,
2015    };
2016    const EVENT_ID_STR: &str = "0:1:2";
2017    const EVENT_ID_STR_INVALID: &str = "0,1,2";
2018    const EVENT_ID_STR_INVALID_EVENT_ID: &str = "0:1:two";
2019    const EVENT_ID_STR_NOT_THREE_DIGITS: &str = "0:1";
2020    const EVENT_ID: EventId = EventId {
2021        simulation_address: SIMULATION_ADDRESS,
2022        event_id: 2,
2023    };
2024
2025    #[test]
2026    fn entity_type_display() {
2027        assert_eq!(ENTITY_TYPE_STR, ENTITY_TYPE.to_string());
2028    }
2029
2030    #[test]
2031    fn entity_type_from_str() {
2032        assert_eq!(EntityType::from_str(ENTITY_TYPE_STR).unwrap(), ENTITY_TYPE);
2033        let err = EntityType::from_str(ENTITY_TYPE_STR_INVALID);
2034        assert!(err.is_err());
2035        assert!(matches!(err, Err(DisError::ParseError(_))));
2036        assert_eq!(
2037            err.unwrap_err().to_string(),
2038            "EntityType string pattern does not contain precisely 7 digits"
2039        );
2040        let err = EntityType::from_str(ENTITY_TYPE_STR_INVALID_EXTRA);
2041        assert!(err.is_err());
2042        assert!(matches!(err, Err(DisError::ParseError(_))));
2043        assert_eq!(err.unwrap_err().to_string(), "Invalid extra digit");
2044    }
2045
2046    #[test]
2047    fn entity_type_from_str_not_seven_digits() {
2048        let err = EntityType::from_str(ENTITY_TYPE_STR_NOT_SEVEN_DIGITS);
2049        assert!(err.is_err());
2050        assert!(matches!(err, Err(DisError::ParseError(_))));
2051        assert_eq!(
2052            err.unwrap_err().to_string(),
2053            "EntityType string pattern does not contain precisely 7 digits"
2054        );
2055    }
2056
2057    #[test]
2058    fn entity_type_try_from_str() {
2059        assert_eq!(
2060            TryInto::<EntityType>::try_into(ENTITY_TYPE_STR).unwrap(),
2061            ENTITY_TYPE
2062        );
2063        let err = TryInto::<EntityType>::try_into(ENTITY_TYPE_STR_INVALID);
2064        assert!(err.is_err());
2065        assert!(matches!(err, Err(DisError::ParseError(_))));
2066        assert_eq!(
2067            err.unwrap_err().to_string(),
2068            "EntityType string pattern does not contain precisely 7 digits"
2069        );
2070        let err = TryInto::<EntityType>::try_into(ENTITY_TYPE_STR_INVALID_EXTRA);
2071        assert!(err.is_err());
2072        assert!(matches!(err, Err(DisError::ParseError(_))));
2073        assert_eq!(err.unwrap_err().to_string(), "Invalid extra digit");
2074    }
2075
2076    #[test]
2077    fn entity_type_try_from_string() {
2078        assert_eq!(
2079            TryInto::<EntityType>::try_into(ENTITY_TYPE_STR.to_string()).unwrap(),
2080            ENTITY_TYPE
2081        );
2082        let err = TryInto::<EntityType>::try_into(ENTITY_TYPE_STR_INVALID.to_string());
2083        assert!(err.is_err());
2084        assert!(matches!(err, Err(DisError::ParseError(_))));
2085        assert_eq!(
2086            err.unwrap_err().to_string(),
2087            "EntityType string pattern does not contain precisely 7 digits"
2088        );
2089        let err = TryInto::<EntityType>::try_into(ENTITY_TYPE_STR_INVALID_EXTRA.to_string());
2090        assert!(err.is_err());
2091        assert!(matches!(err, Err(DisError::ParseError(_))));
2092        assert_eq!(err.unwrap_err().to_string(), "Invalid extra digit");
2093    }
2094
2095    #[test]
2096    fn simulation_address_display() {
2097        assert_eq!(SIMULATION_ADDRESS_STR, SIMULATION_ADDRESS.to_string());
2098    }
2099
2100    #[test]
2101    fn simulation_address_from_str() {
2102        assert_eq!(
2103            SimulationAddress::from_str(SIMULATION_ADDRESS_STR).unwrap(),
2104            SIMULATION_ADDRESS
2105        );
2106        let err = SimulationAddress::from_str(SIMULATION_ADDRESS_STR_INVALID);
2107        assert!(err.is_err());
2108        assert!(matches!(err, Err(DisError::ParseError(_))));
2109        assert_eq!(
2110            err.unwrap_err().to_string(),
2111            "SimulationAddress string pattern does not contain precisely 2 digits"
2112        );
2113        let err = SimulationAddress::from_str(SIMULATION_ADDRESS_STR_INVALID_APPLICATION_ID);
2114        assert!(err.is_err());
2115        assert!(matches!(err, Err(DisError::ParseError(_))));
2116        assert_eq!(err.unwrap_err().to_string(), "Invalid application id digit");
2117    }
2118
2119    #[test]
2120    fn simulation_address_from_str_not_two_digits() {
2121        let err = SimulationAddress::from_str(SIMULATION_ADDRESS_STR_NOT_TWO_DIGITS);
2122        assert!(err.is_err());
2123        assert!(matches!(err, Err(DisError::ParseError(_))));
2124        assert_eq!(
2125            err.unwrap_err().to_string(),
2126            "SimulationAddress string pattern does not contain precisely 2 digits"
2127        );
2128    }
2129
2130    #[test]
2131    fn simulation_address_try_from_str() {
2132        assert_eq!(
2133            TryInto::<SimulationAddress>::try_into(SIMULATION_ADDRESS_STR).unwrap(),
2134            SIMULATION_ADDRESS
2135        );
2136        let err = TryInto::<SimulationAddress>::try_into(SIMULATION_ADDRESS_STR_INVALID);
2137        assert!(err.is_err());
2138        assert!(matches!(err, Err(DisError::ParseError(_))));
2139        assert_eq!(
2140            err.unwrap_err().to_string(),
2141            "SimulationAddress string pattern does not contain precisely 2 digits"
2142        );
2143        let err =
2144            TryInto::<SimulationAddress>::try_into(SIMULATION_ADDRESS_STR_INVALID_APPLICATION_ID);
2145        assert!(err.is_err());
2146        assert!(matches!(err, Err(DisError::ParseError(_))));
2147        assert_eq!(err.unwrap_err().to_string(), "Invalid application id digit");
2148    }
2149
2150    #[test]
2151    fn simulation_address_try_from_string() {
2152        assert_eq!(
2153            TryInto::<SimulationAddress>::try_into(SIMULATION_ADDRESS_STR.to_string()).unwrap(),
2154            SIMULATION_ADDRESS
2155        );
2156        let err =
2157            TryInto::<SimulationAddress>::try_into(SIMULATION_ADDRESS_STR_INVALID.to_string());
2158        assert!(err.is_err());
2159        assert!(matches!(err, Err(DisError::ParseError(_))));
2160        assert_eq!(
2161            err.unwrap_err().to_string(),
2162            "SimulationAddress string pattern does not contain precisely 2 digits"
2163        );
2164        let err = TryInto::<SimulationAddress>::try_into(
2165            SIMULATION_ADDRESS_STR_INVALID_APPLICATION_ID.to_string(),
2166        );
2167        assert!(err.is_err());
2168        assert!(matches!(err, Err(DisError::ParseError(_))));
2169        assert_eq!(err.unwrap_err().to_string(), "Invalid application id digit");
2170    }
2171
2172    #[test]
2173    fn entity_id_display() {
2174        assert_eq!(ENTITY_ID_STR, ENTITY_ID.to_string());
2175    }
2176
2177    #[test]
2178    fn entity_id_from_str() {
2179        assert_eq!(EntityId::from_str(ENTITY_ID_STR).unwrap(), ENTITY_ID);
2180        let err = EntityId::from_str(ENTITY_ID_STR_INVALID);
2181        assert!(err.is_err());
2182        assert!(matches!(err, Err(DisError::ParseError(_))));
2183        assert_eq!(
2184            err.unwrap_err().to_string(),
2185            "EntityId string pattern does not contain precisely 3 digits"
2186        );
2187        let err = EntityId::from_str(ENTITY_ID_STR_INVALID_ENTITY_ID);
2188        assert!(err.is_err());
2189        assert!(matches!(err, Err(DisError::ParseError(_))));
2190        assert_eq!(err.unwrap_err().to_string(), "Invalid entity id digit");
2191    }
2192
2193    #[test]
2194    fn entity_id_from_str_not_three_digits() {
2195        let err = EntityId::from_str(ENTITY_ID_STR_NOT_THREE_DIGITS);
2196        assert!(err.is_err());
2197        assert!(matches!(err, Err(DisError::ParseError(_))));
2198        assert_eq!(
2199            err.unwrap_err().to_string(),
2200            "EntityId string pattern does not contain precisely 3 digits"
2201        );
2202    }
2203
2204    #[test]
2205    fn entity_id_try_from_str() {
2206        assert_eq!(
2207            TryInto::<EntityId>::try_into(ENTITY_ID_STR).unwrap(),
2208            ENTITY_ID
2209        );
2210        let err = TryInto::<EntityId>::try_into(ENTITY_ID_STR_INVALID);
2211        assert!(err.is_err());
2212        assert!(matches!(err, Err(DisError::ParseError(_))));
2213        assert_eq!(
2214            err.unwrap_err().to_string(),
2215            "EntityId string pattern does not contain precisely 3 digits"
2216        );
2217        let err = TryInto::<EntityId>::try_into(ENTITY_ID_STR_INVALID_ENTITY_ID);
2218        assert!(err.is_err());
2219        assert!(matches!(err, Err(DisError::ParseError(_))));
2220        assert_eq!(err.unwrap_err().to_string(), "Invalid entity id digit");
2221    }
2222
2223    #[test]
2224    fn entity_id_try_from_string() {
2225        assert_eq!(
2226            TryInto::<EntityId>::try_into(ENTITY_ID_STR.to_string()).unwrap(),
2227            ENTITY_ID
2228        );
2229        let err = TryInto::<EntityId>::try_into(ENTITY_ID_STR_INVALID.to_string());
2230        assert!(err.is_err());
2231        assert!(matches!(err, Err(DisError::ParseError(_))));
2232        assert_eq!(
2233            err.unwrap_err().to_string(),
2234            "EntityId string pattern does not contain precisely 3 digits"
2235        );
2236        let err = TryInto::<EntityId>::try_into(ENTITY_ID_STR_INVALID_ENTITY_ID.to_string());
2237        assert!(err.is_err());
2238        assert!(matches!(err, Err(DisError::ParseError(_))));
2239        assert_eq!(err.unwrap_err().to_string(), "Invalid entity id digit");
2240    }
2241
2242    #[test]
2243    fn event_id_display() {
2244        assert_eq!(EVENT_ID_STR, EVENT_ID.to_string());
2245    }
2246
2247    #[test]
2248    fn event_id_from_str() {
2249        assert_eq!(EventId::from_str(EVENT_ID_STR).unwrap(), EVENT_ID);
2250        let err = EventId::from_str(EVENT_ID_STR_INVALID);
2251        assert!(err.is_err());
2252        assert!(matches!(err, Err(DisError::ParseError(_))));
2253        assert_eq!(
2254            err.unwrap_err().to_string(),
2255            "EventId string pattern does not contain precisely 3 digits"
2256        );
2257        let err = EventId::from_str(EVENT_ID_STR_INVALID_EVENT_ID);
2258        assert!(err.is_err());
2259        assert!(matches!(err, Err(DisError::ParseError(_))));
2260        assert_eq!(err.unwrap_err().to_string(), "Invalid event id digit");
2261    }
2262
2263    #[test]
2264    fn event_id_from_str_not_three_digits() {
2265        let err = EventId::from_str(EVENT_ID_STR_NOT_THREE_DIGITS);
2266        assert!(err.is_err());
2267        assert!(matches!(err, Err(DisError::ParseError(_))));
2268        assert_eq!(
2269            err.unwrap_err().to_string(),
2270            "EventId string pattern does not contain precisely 3 digits"
2271        );
2272    }
2273
2274    #[test]
2275    fn event_id_try_from_str() {
2276        assert_eq!(
2277            TryInto::<EventId>::try_into(EVENT_ID_STR).unwrap(),
2278            EVENT_ID
2279        );
2280        let err = TryInto::<EventId>::try_into(EVENT_ID_STR_INVALID);
2281        assert!(err.is_err());
2282        assert!(matches!(err, Err(DisError::ParseError(_))));
2283        assert_eq!(
2284            err.unwrap_err().to_string(),
2285            "EventId string pattern does not contain precisely 3 digits"
2286        );
2287        let err = TryInto::<EventId>::try_into(EVENT_ID_STR_INVALID_EVENT_ID);
2288        assert!(err.is_err());
2289        assert!(matches!(err, Err(DisError::ParseError(_))));
2290        assert_eq!(err.unwrap_err().to_string(), "Invalid event id digit");
2291    }
2292
2293    #[test]
2294    fn event_id_try_from_string() {
2295        assert_eq!(
2296            TryInto::<EventId>::try_into(EVENT_ID_STR.to_string()).unwrap(),
2297            EVENT_ID
2298        );
2299        let err = TryInto::<EventId>::try_into(EVENT_ID_STR_INVALID.to_string());
2300        assert!(err.is_err());
2301        assert!(matches!(err, Err(DisError::ParseError(_))));
2302        assert_eq!(
2303            err.unwrap_err().to_string(),
2304            "EventId string pattern does not contain precisely 3 digits"
2305        );
2306        let err = TryInto::<EventId>::try_into(EVENT_ID_STR_INVALID_EVENT_ID.to_string());
2307        assert!(err.is_err());
2308        assert!(matches!(err, Err(DisError::ParseError(_))));
2309        assert_eq!(err.unwrap_err().to_string(), "Invalid event id digit");
2310    }
2311}