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 crate::common::{entity_id_record::EntityIDRecord, event_id_record::EventIDRecord, pdu::PDU, pdu_header_record::{PDUHeaderRecord, PDUType, ProtocolFamily}, velocity_vector_record::VelocityVectorRecord, world_coordinate_record::WorldCoordinateRecord, dis_error::DISError};
use bytes::{BytesMut, BufMut, Buf};

use super::burst_descriptor_record::BurstDescriptorRecord;

#[derive(Copy, Clone, Debug)]
/// Fire PDU as defined in IEEE 1278.1 standard. Used to communicate a weapon firing during the simulation.
pub struct FirePDU {
    pub pdu_header_record: PDUHeaderRecord,
    pub firing_entity_id_record: EntityIDRecord,
    pub target_entity_id_record: EntityIDRecord,
    pub munition_id_record: EntityIDRecord,
    pub event_id_record: EventIDRecord,
    pub fire_mission_index_field: u32,
    pub location_in_world_record: WorldCoordinateRecord,
    pub burst_descriptor_record: BurstDescriptorRecord,
    pub velocity_record: VelocityVectorRecord,
    pub range_field: f32
}

impl FirePDU {
    /// Provides a blank FirePDU with all IDs set to 1 (as 0 is not allowed within the DIS protocol).
    /// 
    /// # Examples
    /// 
    /// Creating a blank FirePDU:
    /// 
    /// ```
    /// let fire_pdu = FirePDU::default();
    /// ```
    /// 
    /// Populating a bank FirePDU's firing_entity_id_record:
    /// 
    /// ```
    /// let mut fire_pdu = FirePDU::default();
    /// fire_pdu.firing_entity_id_record = EntityIDRecord::default(2);
    /// ```
    /// 
    pub fn default() -> Self {
        FirePDU {
            pdu_header_record: PDUHeaderRecord::default(PDUType::Fire, ProtocolFamily::Warfare, 96),
            firing_entity_id_record: EntityIDRecord::default(1),
            target_entity_id_record: EntityIDRecord::default(1),
            munition_id_record: EntityIDRecord::default(1),
            event_id_record: EventIDRecord::default(1),
            fire_mission_index_field: 0,
            location_in_world_record: WorldCoordinateRecord::new(0.0, 0.0, 0.0),
            burst_descriptor_record: BurstDescriptorRecord::default(),
            velocity_record: VelocityVectorRecord::new(0.0, 0.0, 0.0),
            range_field: 0.0
        }
    }        
}

impl PDU for FirePDU {
    /// Fills a BytesMut struct with a FirePDU 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.firing_entity_id_record.serialize(buf);
        self.target_entity_id_record.serialize(buf);
        self.munition_id_record.serialize(buf);
        self.event_id_record.serialize(buf);
        buf.put_u32(self.fire_mission_index_field);
        self.location_in_world_record.serialize(buf);
        self.burst_descriptor_record.serialize(buf);
        self.velocity_record.serialize(buf);
        buf.put_f32(self.range_field);
    }

    fn deserialise(mut buffer: BytesMut) -> Result<Self, crate::common::dis_error::DISError> where Self: Sized {
        let pdu_header = PDUHeaderRecord::decode(&mut buffer);
        if pdu_header.pdu_type == PDUType::Fire {
            let firing_entity_id = EntityIDRecord::decode(&mut buffer);
            let target_entity_id = EntityIDRecord::decode(&mut buffer);
            let munition_entity_id = EntityIDRecord::decode(&mut buffer);
            let event_id = EventIDRecord::decode(&mut buffer);
            let fire_mission = buffer.get_u32();
            let location = WorldCoordinateRecord::decode(&mut buffer);
            let burst_descriptor = BurstDescriptorRecord::decode(&mut buffer);
            let velocity = VelocityVectorRecord::decode(&mut buffer);
            let range = buffer.get_f32();
            return Ok(FirePDU {
                pdu_header_record: pdu_header,
                firing_entity_id_record: firing_entity_id,
                target_entity_id_record: target_entity_id,
                munition_id_record: munition_entity_id,
                event_id_record: event_id,
                fire_mission_index_field: fire_mission,
                location_in_world_record: location,
                burst_descriptor_record: burst_descriptor,
                velocity_record: velocity,
                range_field: range,
            })
        } else {
            Err(DISError::InvalidDISHeader)
        }
    }

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

    fn deserialise_without_header(mut buffer: BytesMut, pdu_header: PDUHeaderRecord) -> Result<Self, DISError> where Self: Sized {
        let firing_entity_id = EntityIDRecord::decode(&mut buffer);
            let target_entity_id = EntityIDRecord::decode(&mut buffer);
            let munition_entity_id = EntityIDRecord::decode(&mut buffer);
            let event_id = EventIDRecord::decode(&mut buffer);
            let fire_mission = buffer.get_u32();
            let location = WorldCoordinateRecord::decode(&mut buffer);
            let burst_descriptor = BurstDescriptorRecord::decode(&mut buffer);
            let velocity = VelocityVectorRecord::decode(&mut buffer);
            let range = buffer.get_f32();
            return Ok(FirePDU {
                pdu_header_record: pdu_header,
                firing_entity_id_record: firing_entity_id,
                target_entity_id_record: target_entity_id,
                munition_id_record: munition_entity_id,
                event_id_record: event_id,
                fire_mission_index_field: fire_mission,
                location_in_world_record: location,
                burst_descriptor_record: burst_descriptor,
                velocity_record: velocity,
                range_field: range,
            })
    }
}

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

    use crate::{common::{pdu_header_record::{PDUHeaderRecord, PDUType, ProtocolFamily}, pdu::PDU}, warfare::fire_pdu::FirePDU};

    #[test]
    fn header_creation() {
        let firing_pdu = FirePDU::default();
        let header = PDUHeaderRecord::default(PDUType::Fire, ProtocolFamily::Warfare, 768/8);
        assert_eq!(header.protocol_version, firing_pdu.pdu_header_record.protocol_version);
        assert_eq!(header.exercise_id, firing_pdu.pdu_header_record.exercise_id);
        assert_eq!(header.pdu_type, firing_pdu.pdu_header_record.pdu_type);
        assert_eq!(header.protocol_family, firing_pdu.pdu_header_record.protocol_family);
        assert_eq!(header.timestamp, firing_pdu.pdu_header_record.timestamp);
        assert_eq!(header.length, firing_pdu.pdu_header_record.length);
        assert_eq!(header.padding, firing_pdu.pdu_header_record.padding);
    }

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

        let new_fire_pdu = FirePDU::deserialise(buffer).unwrap();
        assert_eq!(new_fire_pdu.pdu_header_record, fire_pdu.pdu_header_record);
    }
}