Skip to main content

dis_rs/common/
model.rs

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