f1-telemetry-client 0.1.1

Telemetry client for F1 game by Codemasters
Documentation
use async_std::io::{Cursor, Error, ErrorKind};
use byteorder_async::{LittleEndian, ReaderToByteOrder};
use std::time::Duration;

#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd)]
pub struct PacketHeader {
    pub packet_format: u16,
    pub major_version: u8,
    pub minor_version: u8,
    pub packet_version: u8,
    pub packet_id: u8,
    pub session_uid: u64,
    pub session_time: Duration,
    pub frame_identifier: u32,
    pub player_car_index: u8,
    pub secondary_player_car_index: u8,
}

const HEADER_MIN_SIZE: usize = 24;

pub async fn parse_headers(
    cursor: &mut Cursor<Vec<u8>>,
    size: usize,
) -> Result<PacketHeader, Error> {
    ensure_header_size(size)?;

    let packet_format = cursor.byte_order().read_u16::<LittleEndian>().await?;
    let major_version = cursor.byte_order().read_u8().await?;
    let minor_version = cursor.byte_order().read_u8().await?;
    let packet_version = cursor.byte_order().read_u8().await?;
    let packet_id = cursor.byte_order().read_u8().await?;
    let session_uid = cursor.byte_order().read_u64::<LittleEndian>().await?;
    let session_time =
        Duration::from_secs_f32(cursor.byte_order().read_f32::<LittleEndian>().await?);
    let frame_identifier = cursor.byte_order().read_u32::<LittleEndian>().await?;
    let player_car_index = cursor.byte_order().read_u8().await?;
    let secondary_player_car_index = cursor.byte_order().read_u8().await?;

    Ok(PacketHeader {
        packet_format,
        major_version,
        minor_version,
        packet_version,
        packet_id,
        session_uid,
        session_time,
        frame_identifier,
        player_car_index,
        secondary_player_car_index,
    })
}

fn ensure_header_size(size: usize) -> Result<(), Error> {
    if size < HEADER_MIN_SIZE {
        return Err(Error::new(
            ErrorKind::InvalidData,
            "Header size is too small",
        ));
    }

    return Ok(());
}

#[cfg(test)]
mod test {
    use crate::f1_2020::header::parse_headers;
    use async_std::io::Cursor;

    use byteorder_async::{LittleEndian, WriteBytesExt};

    #[async_std::test]
    async fn parse_header_error() {
        let mut buf = Vec::with_capacity(1);
        buf.write_u16::<LittleEndian>(0).unwrap();

        let mut cursor = Cursor::new(buf);
        let len = cursor.get_ref().len();

        let result = parse_headers(&mut cursor, len).await.map_err(|e| e.kind());
        assert_eq!(result, Err(async_std::io::ErrorKind::InvalidData));
    }

    #[async_std::test]
    async fn parse_header_success() {
        let mut buf = Vec::with_capacity(2048);
        buf.write_u16::<LittleEndian>(2020).unwrap();
        buf.write_u8(1).unwrap();
        buf.write_u8(2).unwrap();
        buf.write_u8(3).unwrap();
        buf.write_u8(0).unwrap();
        buf.write_u64::<LittleEndian>(u64::max_value()).unwrap();
        buf.write_f32::<LittleEndian>(1.0).unwrap();
        buf.write_u32::<LittleEndian>(u32::max_value()).unwrap();
        buf.write_u8(19).unwrap();
        buf.write_u8(255).unwrap();

        let mut cursor = Cursor::new(buf);
        let len = cursor.get_mut().len();
        let result = parse_headers(&mut cursor, len).await.unwrap();

        assert_eq!(2020, result.packet_format);
        assert_eq!(1, result.major_version);
        assert_eq!(2, result.minor_version);
        assert_eq!(3, result.packet_version);
        assert_eq!(0, result.packet_id);
        assert_eq!(u64::max_value(), result.session_uid);
        assert_eq!(1, result.session_time.as_secs());
        assert_eq!(1000, result.session_time.as_millis());
        assert_eq!(u32::max_value(), result.frame_identifier);
        assert_eq!(19, result.player_car_index);
        assert_eq!(255, result.secondary_player_car_index);
    }
}