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#[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#[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#[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#[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#[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#[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#[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#[derive(Clone, Debug, PartialEq)]
1200#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1201pub enum DescriptorRecord {
1202 #[cfg_attr(feature = "serde", serde(rename = "munition"))]
1204 Munition {
1205 entity_type: EntityType,
1206 munition: MunitionDescriptor,
1207 },
1208 #[cfg_attr(feature = "serde", serde(rename = "expendable"))]
1210 Expendable { entity_type: EntityType },
1211 #[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#[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#[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#[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 fn seconds_to_nanoseconds(seconds: u32) -> u32 {
1404 seconds * 1_000_000
1405 }
1406
1407 #[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#[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#[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#[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#[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#[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
1553pub(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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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 #[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 #[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}