dis-rust 0.2.10

A rust implementation of the DIS simulation protocol.
Documentation
//     dis-rust - A rust implementation of the DIS simulation protocol.
//     Copyright (C) 2022 Thomas Mann
// 
//     This software is dual-licensed. It is available under the conditions of
//     the GNU Affero General Public License (see the LICENSE file included) 
//     or under a commercial license (email contact@coffeebreakdevs.com for
//     details).

use std::any::Any;

use bytes::{BytesMut, BufMut, Buf};
use serde::{Serialize, Deserialize};

use crate::common::{entity_id_record::{EntityIDRecord}, entity_type_record::{Country, EntityTypeRecord, Kind}, euler_angles_record::EulerAnglesRecord, linear_velocity_record::LinearVelocityRecord, pdu::PDU, pdu_header_record::{PDUHeaderRecord, PDUType, ProtocolFamily}, world_coordinate_record::WorldCoordinateRecord, dis_error::DISError};

use super::{entity_appearance_record::EntityAppearanceRecord, dead_reckoning_parameters_record::DeadReckoningParametersRecord, entity_marking_record::EntityMarkingRecord, entity_capabilities_record::{EntityCapabilitiesRecord}};

#[derive(Clone, Debug,)]
/// Entity State PDU as defined in IEEE 1278.1 standard. Used to communicate the state of an entity during the simulation.
pub struct EntityStatePDU {
    pub pdu_header_record: PDUHeaderRecord,
    pub entity_id_record: EntityIDRecord,
    pub force_id_field: ForceID,
    pub number_of_articulation_parameters_field: u8,
    pub entity_type_record: EntityTypeRecord,
    pub alternative_entity_type_record: EntityTypeRecord,
    pub entity_linear_velocity_record: LinearVelocityRecord,
    pub entity_location_record: WorldCoordinateRecord,
    pub entity_orientation_record: EulerAnglesRecord,
    pub entity_appearance_record: EntityAppearanceRecord,
    pub dead_reckoning_parameters_record: DeadReckoningParametersRecord,
    pub entity_marking_record: EntityMarkingRecord,
    pub entity_capabilities_record: EntityCapabilitiesRecord,
    pub articulation_parameter_record: f32 // TODO: Correct this!
}

impl EntityStatePDU {
    /// Provides a function to create a default EntityStatePDU.
    /// The provided PDU represents a simple munition used for testing. 
    pub fn default() -> Self {
        EntityStatePDU {
            pdu_header_record: PDUHeaderRecord::default(PDUType::EntityState, ProtocolFamily::EntityInformation, 864/8), // length assumes no articulating parameters (TODO: Calculate dynamically!)
            entity_id_record: EntityIDRecord::default(2),
            force_id_field: ForceID::Other,
            number_of_articulation_parameters_field: 0,
            entity_type_record: EntityTypeRecord{
                kind_field: Kind::Munition,
                domain_field: 1,
                country_field: Country::Other,
                category_field: 3,
                subcategory_field: 0,
                specific_field: 0,
                extra_field: 0},
            alternative_entity_type_record: EntityTypeRecord{
                kind_field: Kind::Munition,
                domain_field: 1,
                country_field: Country::Other,
                category_field: 0,
                subcategory_field: 0,
                specific_field: 0,
                extra_field: 0},
            entity_linear_velocity_record: LinearVelocityRecord::new(0.0, 0.0, 0.0),
            entity_location_record: WorldCoordinateRecord::new(0.0, 0.0, 0.0),
            entity_orientation_record: EulerAnglesRecord::new(0.0, 0.0, 0.0),
            entity_appearance_record: EntityAppearanceRecord::default(),
            dead_reckoning_parameters_record: DeadReckoningParametersRecord::default(),
            entity_marking_record: EntityMarkingRecord::default("".to_string()),
            entity_capabilities_record: EntityCapabilitiesRecord::default(),
            articulation_parameter_record: 0.0 // TODO: Needs correcting to proper value!
        }
    }  
}

impl PDU for EntityStatePDU {
    /// Creates an EntityStatePDU from a BytesMut struct.
    fn deserialise(mut buffer: BytesMut) -> Result<EntityStatePDU, DISError> {
        let pdu_header = PDUHeaderRecord::decode(&mut buffer);
        if pdu_header.pdu_type == PDUType::EntityState {
            let entity_id = EntityIDRecord::decode(&mut buffer);
            let force_id = ForceID::decode(&mut buffer);
            let articulation_params = &buffer.get_u8();
            let entity_type = EntityTypeRecord::decode(&mut buffer);
            let alt_entity_type = EntityTypeRecord::decode(&mut buffer);
            let linear_velocity = LinearVelocityRecord::decode(&mut buffer);
            let world_coordinate = WorldCoordinateRecord::decode(&mut buffer);
            let orientation = EulerAnglesRecord::decode(&mut buffer);
            let appearance = EntityAppearanceRecord::decode(&mut buffer);
            let dead_reckoning = DeadReckoningParametersRecord::decode(&mut buffer);
            let entity_marking = EntityMarkingRecord::decode(&mut buffer);
            let entity_capabilities = EntityCapabilitiesRecord::decode(&mut buffer);
            
            return Ok(EntityStatePDU {
                pdu_header_record: pdu_header,
                entity_id_record: entity_id,
                force_id_field: force_id,
                number_of_articulation_parameters_field: *articulation_params,
                entity_type_record: entity_type,
                alternative_entity_type_record: alt_entity_type,
                entity_linear_velocity_record: linear_velocity,
                entity_location_record: world_coordinate,
                entity_orientation_record: orientation,
                entity_appearance_record: appearance,
                dead_reckoning_parameters_record: dead_reckoning,
                entity_marking_record: entity_marking,
                entity_capabilities_record: entity_capabilities,
                articulation_parameter_record: 0.0,
            })
        } else {
            Err(DISError::InvalidDISHeader)
        }
    }

    /// Fills a BytesMut struct with a EntityStatePDU serialised into binary. This buffer is then ready to be sent via
    /// UDP to the simluation network.
    fn serialise(&self, buf: &mut BytesMut) {
        self.pdu_header_record.serialize(buf);
        self.entity_id_record.serialize(buf);
        buf.put_u8(self.force_id_field as u8);
        buf.put_u8(self.number_of_articulation_parameters_field as u8);
        self.entity_type_record.serialize(buf);
        self.alternative_entity_type_record.serialize(buf);
        self.entity_linear_velocity_record.serialize(buf);
        self.entity_location_record.serialize(buf);
        self.entity_orientation_record.serialize(buf);
        self.entity_appearance_record.serialize(buf);
        self.dead_reckoning_parameters_record.serialize(buf);
        self.entity_marking_record.serialize(buf);
        self.entity_capabilities_record.serialize(buf);
    }

    fn as_any(&self) -> &dyn Any {
        self
      }

    fn deserialise_without_header(mut buffer: BytesMut, pdu_header: PDUHeaderRecord) -> Result<Self, DISError> where Self: Sized {
        let entity_id = EntityIDRecord::decode(&mut buffer);
            let force_id = ForceID::decode(&mut buffer);
            let articulation_params = &buffer.get_u8();
            let entity_type = EntityTypeRecord::decode(&mut buffer);
            let alt_entity_type = EntityTypeRecord::decode(&mut buffer);
            let linear_velocity = LinearVelocityRecord::decode(&mut buffer);
            let world_coordinate = WorldCoordinateRecord::decode(&mut buffer);
            let orientation = EulerAnglesRecord::decode(&mut buffer);
            let appearance = EntityAppearanceRecord::decode(&mut buffer);
            let dead_reckoning = DeadReckoningParametersRecord::decode(&mut buffer);
            let entity_marking = EntityMarkingRecord::decode(&mut buffer);
            let entity_capabilities = EntityCapabilitiesRecord::decode(&mut buffer);
            
            return Ok(EntityStatePDU {
                pdu_header_record: pdu_header,
                entity_id_record: entity_id,
                force_id_field: force_id,
                number_of_articulation_parameters_field: *articulation_params,
                entity_type_record: entity_type,
                alternative_entity_type_record: alt_entity_type,
                entity_linear_velocity_record: linear_velocity,
                entity_location_record: world_coordinate,
                entity_orientation_record: orientation,
                entity_appearance_record: appearance,
                dead_reckoning_parameters_record: dead_reckoning,
                entity_marking_record: entity_marking,
                entity_capabilities_record: entity_capabilities,
                articulation_parameter_record: 0.0,
            })
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
/// Enum to represent the Force the entity is part of during the simulation.
pub enum ForceID {
    Other = 0,
    Friendly = 1,
    Opposing = 2,
    Neutral = 3
}

impl ForceID {
    pub fn decode(buf: &mut BytesMut) -> ForceID {
        match buf.get_u8() {
            1 => ForceID::Friendly,
            2 => ForceID::Opposing,
            3 => ForceID::Neutral,
            _ => ForceID::Other,
        }
    }
}

#[cfg(test)]
mod tests {
    use bytes::BytesMut;

    use crate::{common::{pdu_header_record::{PDUHeaderRecord, PDUType, ProtocolFamily}, pdu::PDU}, entity_information::entity_state_pdu::EntityStatePDU};

    #[test]
    fn header_creation() {
        let entity_state_pdu = EntityStatePDU::default();
        let header = PDUHeaderRecord::default(PDUType::EntityState, ProtocolFamily::EntityInformation, 864/8);
        assert_eq!(header.protocol_version, entity_state_pdu.pdu_header_record.protocol_version);
        assert_eq!(header.exercise_id, entity_state_pdu.pdu_header_record.exercise_id);
        assert_eq!(header.pdu_type, entity_state_pdu.pdu_header_record.pdu_type);
        assert_eq!(header.protocol_family, entity_state_pdu.pdu_header_record.protocol_family);
        assert_eq!(header.timestamp, entity_state_pdu.pdu_header_record.timestamp);
        assert_eq!(header.length, entity_state_pdu.pdu_header_record.length);
        assert_eq!(header.padding, entity_state_pdu .pdu_header_record.padding);
    }

    #[test]
    fn header_deserialise() {
        let entity_state_pdu = EntityStatePDU::default();
        let mut buffer = BytesMut::new();
        entity_state_pdu.serialise(&mut buffer);

        let new_entity_state_pdu = EntityStatePDU::deserialise(buffer).unwrap();
        assert_eq!(new_entity_state_pdu.pdu_header_record, entity_state_pdu.pdu_header_record);
    }
}