rust-raknet 1.0.0

RakNet Protocol implementation by Rust.
Documentation
use crate::arq::{FrameSetPacket, Reliability};
use crate::error::*;
use std::collections::HashMap;
use std::time::{Duration, Instant};

pub(crate) const MAX_FRAGMENTS: usize = 65_536;

struct Fragment {
    pub flags: u8,
    pub compound_size: u32,
    pub ordered_frame_index: u32,
    pub order_channel: u8,
    pub frames: HashMap<u32, FrameSetPacket>,
    bytes: usize,
    updated: Instant,
}

impl Fragment {
    pub fn new(flags: u8, compound_size: u32, ordered_frame_index: u32, order_channel: u8) -> Self {
        Self {
            flags,
            compound_size,
            ordered_frame_index,
            order_channel,
            frames: HashMap::new(),
            bytes: 0,
            updated: Instant::now(),
        }
    }

    pub fn full(&self) -> bool {
        self.compound_size != 0 && self.frames.len() == self.compound_size as usize
    }

    pub fn insert(&mut self, frame: FrameSetPacket) -> Result<usize> {
        if self.full() {
            return Ok(0);
        }

        if frame.fragment_index >= self.compound_size
            || frame.compound_size != self.compound_size
            || frame.order_channel != self.order_channel
            || frame.ordered_frame_index != self.ordered_frame_index
            || (frame.flags & 0xe0) != (self.flags & 0xe0)
        {
            return Err(RaknetError::PacketParseError);
        }

        if let std::collections::hash_map::Entry::Vacant(entry) =
            self.frames.entry(frame.fragment_index)
        {
            let bytes = frame.data.len() + 128;
            entry.insert(frame);
            self.bytes += bytes;
            self.updated = Instant::now();
            Ok(bytes)
        } else {
            Ok(0)
        }
    }

    pub fn merge(&self) -> Result<FrameSetPacket> {
        if !self.full() {
            return Err(RaknetError::PacketParseError);
        }

        let last_index = self
            .compound_size
            .checked_sub(1)
            .ok_or(RaknetError::PacketParseError)?;
        let sequence_number = self
            .frames
            .get(&last_index)
            .ok_or(RaknetError::PacketParseError)?
            .sequence_number;
        let capacity = (0..self.compound_size)
            .try_fold(0usize, |total, index| {
                total.checked_add(self.frames.get(&index)?.data.len())
            })
            .ok_or(RaknetError::PacketSizeExceedMTU)?;
        let mut data = Vec::with_capacity(capacity);
        for index in 0..self.compound_size {
            let frame = self
                .frames
                .get(&index)
                .ok_or(RaknetError::PacketParseError)?;
            data.extend_from_slice(&frame.data);
        }

        let mut ret = FrameSetPacket::new(Reliability::from((self.flags & 224) >> 5)?, data);

        ret.ordered_frame_index = self.ordered_frame_index;
        ret.order_channel = self.order_channel;
        ret.sequence_number = sequence_number;
        Ok(ret)
    }
}

#[derive(Default)]
pub(crate) struct FragmentQ {
    fragments: HashMap<u16, Fragment>,
    bytes: usize,
}

impl FragmentQ {
    fn insert_frame(&mut self, frame: FrameSetPacket) -> Result<u16> {
        let compound_id = frame.compound_id;
        if frame.compound_size == 0
            || frame.compound_size as usize > MAX_FRAGMENTS
            || self.bytes.saturating_add(frame.data.len() + 128) > 64 * 1024 * 1024
            || (self.fragments.len() >= 1024 && !self.fragments.contains_key(&compound_id))
        {
            return Err(RaknetError::PacketParseError);
        }
        let fragment = self.fragments.entry(compound_id).or_insert_with(|| {
            Fragment::new(
                frame.flags,
                frame.compound_size,
                frame.ordered_frame_index,
                frame.order_channel,
            )
        });
        self.bytes += fragment.insert(frame)?;
        Ok(compound_id)
    }

    pub(crate) fn insert_and_take_completed(
        &mut self,
        frame: FrameSetPacket,
    ) -> Result<Option<FrameSetPacket>> {
        let compound_id = self.insert_frame(frame)?;
        if self.fragments.get(&compound_id).is_some_and(Fragment::full) {
            self.fragments
                .remove(&compound_id)
                .map(|fragment| {
                    self.bytes -= fragment.bytes;
                    fragment.merge()
                })
                .transpose()
        } else {
            Ok(None)
        }
    }

    pub(crate) fn size(&self) -> usize {
        self.fragments.len()
    }

    pub(crate) fn expired(&self) -> bool {
        self.fragments
            .values()
            .any(|fragment| fragment.updated.elapsed() >= Duration::from_secs(60))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn fragment(index: u32) -> FrameSetPacket {
        let mut frame = FrameSetPacket::new(Reliability::ReliableOrdered, vec![0xfe]);
        frame.flags |= 16;
        frame.compound_size = 2;
        frame.fragment_index = index;
        frame
    }

    #[test]
    fn incomplete_fragments_expire_and_completed_ones_release_the_budget() {
        let mut queue = FragmentQ::default();
        queue.insert_and_take_completed(fragment(0)).unwrap();
        assert!(!queue.expired());
        assert!(queue.bytes > 1);
        queue.fragments.get_mut(&0).unwrap().updated = Instant::now() - Duration::from_secs(61);
        assert!(queue.expired());
        assert!(
            queue
                .insert_and_take_completed(fragment(1))
                .unwrap()
                .is_some()
        );
        assert_eq!(queue.bytes, 0);
        assert!(!queue.expired());
    }

    #[test]
    fn conflicting_compound_metadata_is_rejected() {
        let mut queue = FragmentQ::default();
        queue.insert_and_take_completed(fragment(0)).unwrap();
        let mut frame = fragment(1);
        frame.order_channel = 2;
        assert!(queue.insert_and_take_completed(frame).is_err());
    }
}