1use crate::common::entity_state::model::{
2 DrEulerAngles, DrOtherParameters, DrParameters, DrWorldOrientationQuaternion, EntityAppearance,
3 EntityMarking, EntityState,
4};
5use crate::common::model::{EntityType, PduBody, PduHeader};
6use crate::common::parser;
7use crate::common::parser::{entity_id, entity_type, sanitize_marking, vec3_f32};
8use crate::enumerations::{
9 DeadReckoningAlgorithm, EntityMarkingCharacterSet, ForceId, ProtocolVersion,
10};
11use crate::v6::entity_state::parser::entity_capabilities;
12use nom::bytes::complete::take;
13use nom::multi::count;
14use nom::number::complete::{be_f32, be_u16, be_u32, be_u8};
15use nom::IResult;
16
17pub(crate) fn entity_state_body(
18 header: &PduHeader,
19) -> impl Fn(&[u8]) -> IResult<&[u8], PduBody> + '_ {
20 move |input: &[u8]| {
21 let (input, entity_id_val) = entity_id(input)?;
22 let (input, force_id_val) = force_id(input)?;
23 let (input, variable_parameters_no) = be_u8(input)?;
24 let (input, entity_type_val) = entity_type(input)?;
25 let (input, alternative_entity_type) = entity_type(input)?;
26 let (input, entity_linear_velocity) = vec3_f32(input)?;
27 let (input, entity_location) = parser::location(input)?;
28 let (input, entity_orientation) = parser::orientation(input)?;
29 let (input, entity_appearance) = entity_appearance(entity_type_val)(input)?;
30 let (input, dead_reckoning_parameters) = dr_parameters(input)?;
31 let (input, entity_marking) = entity_marking(input)?;
32 #[allow(clippy::wildcard_in_or_patterns)]
33 let (input, entity_capabilities) =
34 if header.protocol_version == ProtocolVersion::IEEE1278_12012 {
35 crate::v7::entity_state::parser::entity_capabilities(entity_type_val)(input)?
36 } else {
37 let (input, entity_capabilities) = entity_capabilities(input)?;
38 (
39 input,
40 crate::enumerations::EntityCapabilities::from(entity_capabilities),
41 )
42 };
43 let (input, variable_parameters) = if variable_parameters_no > 0 {
44 count(parser::variable_parameter, variable_parameters_no as usize)(input)?
45 } else {
46 (input, vec![])
47 };
48
49 let body = EntityState::builder()
50 .with_entity_id(entity_id_val)
51 .with_force_id(force_id_val)
52 .with_entity_type(entity_type_val)
53 .with_alternative_entity_type(alternative_entity_type)
54 .with_velocity(entity_linear_velocity)
55 .with_location(entity_location)
56 .with_orientation(entity_orientation)
57 .with_appearance(entity_appearance)
58 .with_dead_reckoning_parameters(dead_reckoning_parameters)
59 .with_marking(entity_marking)
60 .with_capabilities(entity_capabilities)
61 .with_variable_parameters(variable_parameters)
62 .build();
63
64 Ok((input, body.into_pdu_body()))
65 }
66}
67
68pub(crate) fn force_id(input: &[u8]) -> IResult<&[u8], ForceId> {
69 let (input, force_id) = be_u8(input)?;
70 Ok((input, ForceId::from(force_id)))
71}
72
73pub(crate) fn entity_appearance(
74 entity_type: EntityType,
75) -> impl Fn(&[u8]) -> IResult<&[u8], EntityAppearance> {
76 move |input: &[u8]| {
77 let (input, appearance) = be_u32(input)?;
78
79 Ok((
80 input,
81 EntityAppearance::from_bytes(appearance, &entity_type),
82 ))
83 }
84}
85
86pub(crate) fn entity_marking(input: &[u8]) -> IResult<&[u8], EntityMarking> {
91 let mut buf: [u8; 11] = [0; 11];
92 let (input, marking_character_set) = be_u8(input)?;
93 let (input, ()) = nom::multi::fill(be_u8, &mut buf)(input)?;
94
95 let marking_character_set = EntityMarkingCharacterSet::from(marking_character_set);
96 let marking_string = sanitize_marking(&buf[..]);
97
98 Ok((
99 input,
100 EntityMarking {
101 marking_character_set,
102 marking_string,
103 },
104 ))
105}
106
107pub(crate) fn dr_parameters(input: &[u8]) -> IResult<&[u8], DrParameters> {
108 let (input, algorithm) = be_u8(input)?;
109 let algorithm = DeadReckoningAlgorithm::from(algorithm);
110
111 let (input, other_parameters) = dr_other_parameters(input, algorithm)?;
112
113 let (input, acceleration) = vec3_f32(input)?;
137 let (input, velocity) = vec3_f32(input)?;
138
139 Ok((
140 input,
141 DrParameters {
142 algorithm,
143 other_parameters,
144 linear_acceleration: acceleration,
145 angular_velocity: velocity,
146 },
147 ))
148}
149
150#[allow(clippy::missing_errors_doc)]
151pub fn dr_other_parameters(
152 input: &[u8],
153 algorithm: DeadReckoningAlgorithm,
154) -> IResult<&[u8], DrOtherParameters> {
155 let (input, other_parameters) = match algorithm {
157 DeadReckoningAlgorithm::StaticNonmovingEntity |
158 DeadReckoningAlgorithm::DRM_FPW_ConstantVelocityLowAccelerationLinearMotionEntity |
159 DeadReckoningAlgorithm::DRM_FVW_HighSpeedOrManeuveringEntity |
160 DeadReckoningAlgorithm::DRM_FPB_SimilarToFPWExceptInBodyCoordinates |
161 DeadReckoningAlgorithm::DRM_FVB_SimilarToFVWExceptInBodyCoordinates => {
162 dr_other_parameters_euler(input)?
163 }
164 DeadReckoningAlgorithm::DRM_RPW_ConstantVelocityLowAccelerationLinearMotionEntityWithExtrapolationOfOrientation |
165 DeadReckoningAlgorithm::DRM_RVW_HighSpeedOrManeuveringEntityWithExtrapolationOfOrientation |
166 DeadReckoningAlgorithm::DRM_RPB_SimilarToRPWExceptInBodyCoordinates |
167 DeadReckoningAlgorithm::DRM_RVB_SimilarToRVWExceptInBodyCoordinates => {
168 dr_other_parameters_quaternion(input)?
169 }
170 DeadReckoningAlgorithm::Other => {
171 dr_other_parameters_none(input)?
172 }
173 DeadReckoningAlgorithm::Unspecified(_) => {
174 dr_other_parameters_none(input)?
175 }
176 };
177
178 Ok((input, other_parameters))
179}
180
181pub(crate) fn dr_other_parameters_none(input: &[u8]) -> IResult<&[u8], DrOtherParameters> {
182 let (input, params) = take(15usize)(input)?;
183 Ok((input, DrOtherParameters::None(params.try_into().unwrap())))
184}
185
186pub(crate) fn dr_other_parameters_euler(input: &[u8]) -> IResult<&[u8], DrOtherParameters> {
187 let (input, _param_type) = be_u8(input)?;
188 let (input, _unused) = be_u16(input)?;
189 let (input, local_yaw) = be_f32(input)?;
190 let (input, local_pitch) = be_f32(input)?;
191 let (input, local_roll) = be_f32(input)?;
192 Ok((
193 input,
194 DrOtherParameters::LocalEulerAngles(DrEulerAngles {
195 local_yaw,
196 local_pitch,
197 local_roll,
198 }),
199 ))
200}
201
202pub(crate) fn dr_other_parameters_quaternion(input: &[u8]) -> IResult<&[u8], DrOtherParameters> {
203 let (input, _param_type) = be_u8(input)?;
204 let (input, nil) = be_u16(input)?;
205 let (input, x) = be_f32(input)?;
206 let (input, y) = be_f32(input)?;
207 let (input, z) = be_f32(input)?;
208 Ok((
209 input,
210 DrOtherParameters::WorldOrientationQuaternion(DrWorldOrientationQuaternion {
211 nil,
212 x,
213 y,
214 z,
215 }),
216 ))
217}
218
219#[cfg(test)]
220mod tests {
221 use crate::common::entity_state::model::EntityAppearance;
222 use crate::common::entity_state::parser::{entity_appearance, entity_marking};
223 use crate::common::model::VariableParameter;
224 use crate::common::model::{EntityType, PduBody};
225 use crate::common::parser::{location, parse_pdu, variable_parameter};
226 use crate::enumerations::*;
227 use crate::v6::entity_state::parser::entity_capabilities;
228
229 #[test]
230 fn parse_pdu_entity_state() {
231 let bytes: [u8; 208] = [
232 0x06, 0x01, 0x01, 0x01, 0x4e, 0xea, 0x3b, 0x60, 0x00, 0xd0, 0x00, 0x00, 0x01, 0xf4,
233 0x03, 0x84, 0x00, 0x0e, 0x01, 0x04, 0x01, 0x02, 0x00, 0x99, 0x32, 0x04, 0x04, 0x00,
234 0x01, 0x02, 0x00, 0x99, 0x32, 0x04, 0x04, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
235 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x50, 0xc4, 0x1a, 0xde, 0xa4, 0xbe, 0xcc,
236 0x41, 0x50, 0xc9, 0xfa, 0x13, 0x3c, 0xf0, 0x5d, 0x41, 0x35, 0x79, 0x16, 0x9e, 0x7a,
237 0x16, 0x78, 0xbf, 0x3e, 0xdd, 0xfa, 0x3e, 0x2e, 0x36, 0xdd, 0x3f, 0xe6, 0x27, 0xc9,
238 0x00, 0x40, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
239 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
240 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
241 0x00, 0x00, 0x01, 0x45, 0x59, 0x45, 0x20, 0x31, 0x30, 0x20, 0x20, 0x20, 0x20, 0x20,
242 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x01, 0x3f, 0x80,
243 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x0b,
244 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
245 0x10, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
246 0x00, 0x00, 0x11, 0x4d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
247 ];
248
249 let pdu = parse_pdu(&bytes);
250 assert!(pdu.is_ok());
251 let pdu = pdu.unwrap();
252 assert_eq!(pdu.header.pdu_type, PduType::EntityState);
253 assert_eq!(pdu.header.pdu_length, 208u16);
254 if let PduBody::EntityState(pdu) = pdu.body {
255 assert_eq!(pdu.entity_id.simulation_address.site_id, 500u16);
256 assert_eq!(pdu.entity_id.simulation_address.application_id, 900u16);
257 assert_eq!(pdu.entity_id.entity_id, 14u16);
258 assert_eq!(pdu.force_id, ForceId::Friendly);
259 assert!(!pdu.variable_parameters.is_empty());
260 assert_eq!(pdu.variable_parameters.len(), 4usize);
261 assert_eq!(
262 pdu.entity_type,
263 EntityType {
264 kind: EntityKind::Platform,
265 domain: PlatformDomain::Air,
266 country: Country::Netherlands_NLD_,
267 category: 50,
268 subcategory: 4,
269 specific: 4,
270 extra: 0
271 }
272 );
273
274 if let EntityAppearance::AirPlatform(appearance) = pdu.entity_appearance {
275 assert_eq!(appearance.paint_scheme, AppearancePaintScheme::UniformColor);
276 assert!(!appearance.propulsion_killed);
277 assert_eq!(appearance.damage, AppearanceDamage::NoDamage);
278 assert!(!appearance.is_smoke_emanating);
279 assert!(!appearance.is_engine_emitting_smoke);
280 assert_eq!(appearance.trailing_effects, AppearanceTrailingEffects::None);
281 assert_eq!(
282 appearance.canopy_troop_door,
283 AppearanceCanopy::SingleCanopySingleTroopDoorOpen
284 );
285 assert!(!appearance.landing_lights_on);
286 assert!(!appearance.navigation_lights_on);
287 assert!(!appearance.anticollision_lights_on);
288 assert!(!appearance.is_flaming);
289 assert!(!appearance.afterburner_on);
290 assert!(!appearance.is_frozen);
291 assert!(!appearance.power_plant_on);
292 assert_eq!(appearance.state, AppearanceEntityOrObjectState::Active);
293 } else {
294 panic!();
295 }
296
297 assert_eq!(pdu.dead_reckoning_parameters.algorithm, DeadReckoningAlgorithm::DRM_RVW_HighSpeedOrManeuveringEntityWithExtrapolationOfOrientation);
298 assert_eq!(pdu.entity_marking.marking_string, String::from("EYE 10"));
299 let capabilities: EntityCapabilities = pdu.entity_capabilities;
300 if let EntityCapabilities::AirPlatformEntityCapabilities(capabilities) = capabilities {
301 assert!(!capabilities.ammunition_supply);
302 assert!(!capabilities.fuel_supply);
303 assert!(!capabilities.recovery);
304 assert!(!capabilities.repair);
305 }
306 assert_eq!(pdu.variable_parameters.len(), 4);
307 let parameter_1 = pdu.variable_parameters.first().unwrap();
308 if let VariableParameter::Articulated(part) = parameter_1 {
309 assert_eq!(part.change_indicator, ChangeIndicator::from(0u8));
310 assert_eq!(part.attachment_id, 0u16);
311 assert_eq!(part.type_metric, ArticulatedPartsTypeMetric::Position);
312 assert_eq!(part.type_class, ArticulatedPartsTypeClass::LandingGear); assert_eq!(part.parameter_value, 1f32);
314 } else {
315 panic!();
316 }
317 } else {
318 panic!();
319 }
320 }
321
322 #[test]
323 fn parse_entity_location() {
324 let bytes: [u8; 24] = [
325 0x41, 0x50, 0xc4, 0x1a, 0xde, 0xa4, 0xbe, 0xcc, 0x41, 0x50, 0xc9, 0xfa, 0x13, 0x3c,
326 0xf0, 0x5d, 0x41, 0x35, 0x79, 0x16, 0x9e, 0x7a, 0x16, 0x78,
327 ];
328
329 let location = location(&bytes);
330 assert!(location.is_ok());
331 let (input, location) = location.unwrap();
332 assert_eq!(input.len(), 0);
333 assert_eq!(location.x_coordinate, 4_395_115.478_805_255);
334 assert_eq!(location.y_coordinate, 4_401_128.300_594_416);
335 assert_eq!(location.z_coordinate, 1_407_254.619_050_411_5);
336 }
337
338 #[test]
339 fn parse_marking_ascii() {
340 let bytes: [u8; 12] = [
341 0x01, 0x45, 0x59, 0x45, 0x20, 0x31, 0x30, 0x20, 0x20, 0x20, 0x20, 0x20,
342 ];
343
344 let marking = entity_marking(&bytes);
345 assert!(marking.is_ok());
346 let (input, marking) = marking.unwrap();
347 assert_eq!(
348 marking.marking_character_set,
349 EntityMarkingCharacterSet::ASCII
350 );
351 assert_eq!(marking.marking_string, "EYE 10");
352
353 assert!(input.is_empty());
354 }
355
356 #[test]
357 fn parse_marking_trailing_control_chars() {
358 let bytes: [u8; 12] = [
359 0x01, 0x45, 0x59, 0x45, 0x20, 0x31, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00,
360 ];
361
362 let marking = entity_marking(&bytes);
363 assert!(marking.is_ok());
364 let (input, marking) = marking.unwrap();
365 assert_eq!(
366 marking.marking_character_set,
367 EntityMarkingCharacterSet::ASCII
368 );
369 assert_eq!(marking.marking_string, "EYE 10");
370
371 assert!(input.is_empty());
372 }
373
374 #[test]
375 fn parse_appearance_none() {
376 let input: [u8; 4] = [0x00, 0x00, 0x00, 0x00];
377 let entity_type = EntityType::default()
378 .with_kind(EntityKind::Platform)
379 .with_domain(PlatformDomain::Air);
380
381 let res = entity_appearance(entity_type)(&input);
382 assert!(res.is_ok());
383 let (input, appearance) = res.expect("value is Ok");
384
385 if let EntityAppearance::AirPlatform(appearance) = appearance {
386 assert_eq!(appearance.paint_scheme, AppearancePaintScheme::UniformColor);
387 assert!(!appearance.propulsion_killed);
388 assert_eq!(appearance.damage, AppearanceDamage::NoDamage);
389 assert!(!appearance.is_smoke_emanating);
390 assert!(!appearance.is_engine_emitting_smoke);
391 assert_eq!(appearance.trailing_effects, AppearanceTrailingEffects::None);
392 assert_eq!(
393 appearance.canopy_troop_door,
394 AppearanceCanopy::NotApplicable
395 );
396 assert!(!appearance.landing_lights_on);
397 assert!(!appearance.is_flaming);
398 } else {
399 panic!();
400 }
401 assert!(input.is_empty());
402 }
403
404 #[test]
405 fn parse_appearance_emitting_engine_smoke() {
406 let input: [u8; 4] = [0x06, 0x00, 0x00, 0x00];
407 let entity_type = EntityType::default()
408 .with_kind(EntityKind::Platform)
409 .with_domain(PlatformDomain::Air);
410
411 let res = entity_appearance(entity_type)(&input);
412 assert!(res.is_ok());
413 let (input, appearance) = res.expect("value is Ok");
414
415 if let EntityAppearance::AirPlatform(appearance) = appearance {
416 assert!(appearance.is_smoke_emanating);
417 assert!(appearance.is_engine_emitting_smoke);
418 } else {
419 panic!();
420 }
421 assert!(input.is_empty());
422 }
423
424 #[test]
425 fn parse_entity_capabilities_none() {
426 let input: [u8; 4] = [0x00, 0x00, 0x00, 0x00];
427
428 let res = entity_capabilities(&input);
429 assert!(res.is_ok());
430 let (input, capabilities) = res.expect("value is Ok");
431 assert!(!capabilities.ammunition_supply);
432 assert!(!capabilities.fuel_supply);
433 assert!(!capabilities.recovery);
434 assert!(!capabilities.repair);
435
436 assert!(input.is_empty());
437 }
438
439 #[test]
440 fn parse_articulated_parameter_gun1_azimuth() {
441 let input: [u8; 16] = [
442 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
448 ]; let parameter = variable_parameter(&input);
451 assert!(parameter.is_ok());
452 let (input, parameter) = parameter.expect("should be Ok");
453
454 if let VariableParameter::Articulated(articulated_part) = parameter {
455 assert_eq!(
456 articulated_part.change_indicator,
457 ChangeIndicator::from(0u8)
458 );
459 assert_eq!(articulated_part.attachment_id, 0);
460 assert_eq!(
461 articulated_part.type_class,
462 ArticulatedPartsTypeClass::PrimaryTurretNumber1
463 );
464 assert_eq!(
465 articulated_part.type_metric,
466 ArticulatedPartsTypeMetric::Azimuth
467 );
468 } else {
469 panic!();
470 }
471
472 assert!(input.is_empty());
473 }
474
475 #[test]
476 fn parse_articulated_parameter_landing_gear_down() {
477 let input: [u8; 16] = [
478 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x01, 0x3F, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
484 ]; let parameter = variable_parameter(&input);
487 assert!(parameter.is_ok());
488 let (input, parameter) = parameter.expect("should be Ok");
489 if let VariableParameter::Articulated(articulated_part) = parameter {
490 assert_eq!(
491 articulated_part.change_indicator,
492 ChangeIndicator::from(0u8)
493 );
494 assert_eq!(articulated_part.attachment_id, 0);
495 assert_eq!(
496 articulated_part.type_class,
497 ArticulatedPartsTypeClass::LandingGear
498 );
499 assert_eq!(
500 articulated_part.type_metric,
501 ArticulatedPartsTypeMetric::Position
502 );
503 assert_eq!(articulated_part.parameter_value, 1f32);
504 } else {
505 panic!();
506 }
507
508 assert!(input.is_empty());
509 }
510}