medusa-proto 0.9.0

Medusa protocol parser
Documentation
use {
    core::{ num::NonZeroU16, ops::Range },
    crate::types::*,
};

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Span {
    pub start: usize,
    pub length: NonZeroU16,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Frame {
    pub channel: ChannelId,
    pub sequence: SequenceId,

    pub payload: Payload,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Payload {
    Check {
        ids: Span,
    },
    Checked {
        lost: Option<Span>,
    },

    InitHandshake {
        desirable_window: NonZeroU16
    },

    EndHandshake {
        window: NonZeroU16
    },

    Packet {
        data: Span,
    },

    Disconnect,
    KeepAlive,
}

impl Span {
    /// Create Span object with zero start and specified length.
    /// 
    /// # Safety
    /// 
    /// Same safety as `Self::new_unchecked`
    pub const unsafe fn only_length_unchecked(length: u16) -> Self {
        Self::new_unchecked(0, length)
    }
    /// Try create Span object with zero start and specified length.
    /// 
    /// Returns `None` if `length == 0`.
    pub const fn try_only_length(length: u16) -> Option<Self> {
        Self::try_new(0, length)
    }

    /// Create Span object with zero start and speicfied length.
    /// 
    /// Panics when length == 0.
    pub const fn only_length(length: u16) -> Self {
        Self::new(0, length)
    }

    /// Convert Span into exclusive range.
    /// 
    /// ```rust
    /// use medusa_proto::frame::Span;
    /// 
    /// let array = [0_u8, 1, 2, 3, 4];
    /// let span = Span::new(1, 2);
    /// let slice = &array[span.into_range()];
    /// 
    /// assert!(slice == &[1, 2]);
    /// ```
    pub const fn into_range(self) -> Range<usize> {
        self.start..(self.start + self.length.get() as usize)
    }

    /// Convert Span into tuple containing start and length.
    /// 
    /// ```rust
    /// use medusa_proto::frame::Span;
    /// 
    /// let span = Span::new(0, 2);
    /// let (start, length) = span.into_tuple();
    /// 
    /// assert_eq!(start, 0);
    /// assert_eq!(length, 2.try_into().unwrap());
    /// ```
    pub const fn into_tuple(self) -> (usize, NonZeroU16) {
        (self.start, self.length)
    }

    /// Try create Span object. Returns None if `length == 0`.
    /// 
    /// ```rust
    /// use medusa_proto::frame::Span;
    /// 
    /// assert!(Span::try_new(0, 0).is_none());
    /// assert!(Span::try_new(0, 1).is_some());
    /// ```
    #[allow(clippy::question_mark)]
    pub const fn try_new(start: usize, length: u16) -> Option<Span> {
        Some(Self {
            start,
            // const try currently is not stable
            length: if let Some(value) = NonZeroU16::new(length) {
                value
            } else {
                return None
            }
        })
    }

    /// Create Span object. The main difference from `Span::new` or `Span::try_new` is that
    /// length type is NonZeroU16 instead of u16. This eliminates zero check for already non zero number.
    /// 
    /// ```rust
    /// use medusa_proto::frame::Span;
    /// 
    /// let span = Span::new_nonzero(0, 10.try_into().unwrap());
    /// assert_eq!(span.start, 0);
    /// assert_eq!(span.length, 10.try_into().unwrap());
    /// ```
    pub const fn new_nonzero(start: usize, length: NonZeroU16) -> Self {
        Self { start, length }
    }

    /// Create Span object without any zero checks for `length`.
    /// 
    /// # Safety
    /// 
    /// This will result in undefined behavior of `length == 0`.
    /// 
    /// # Examples
    /// 
    /// This is ok.
    /// 
    /// ```
    /// use medusa_proto::frame::Span;
    /// 
    /// let span = unsafe { Span::new_unchecked(0, 10) };
    /// assert_eq!(span.start, 0);
    /// assert_eq!(span.length, 10.try_into().unwrap());
    /// ```
    /// 
    /// But this is undefined behavior
    /// 
    /// ```no_run
    /// use medusa_proto::frame::Span;
    /// use core::num::NonZeroU16;
    /// 
    /// let span = unsafe { Span::new_unchecked(0, 0) };
    /// assert_eq!(span.start, 0);
    /// assert_eq!(span.length, unsafe { NonZeroU16::new_unchecked(0) });  // passed! (Probably, behavior is undefined after all)
    /// ```
    pub const unsafe fn new_unchecked(start: usize, length: u16) -> Self {
        Self { start, length: NonZeroU16::new_unchecked(length) }
    }

    /// Create Span object.
    /// Panics if `length == 0`.
    /// 
    /// This is ok.
    /// ```rust
    /// use medusa_proto::frame::Span;
    /// 
    /// let span = Span::new(0, 1);
    /// assert_eq!(span.start, 0);
    /// assert_eq!(span.length, 1.try_into().unwrap());
    /// ```
    /// 
    /// But this will panic
    /// 
    /// ```should_panic
    /// use medusa_proto::frame::Span;
    /// use std::panic::catch_unwind;
    /// 
    /// let result = Span::new(0, 0); // panicked
    /// ```
    pub const fn new(start: usize, length: u16) -> Self {
        if let Some(instance) = Self::try_new(start, length) {
            instance
        } else {
            panic!("`length` should not be zero")
        }
    }
}

impl Payload {
    pub const DEFAULT_WINDOW: NonZeroU16 = unsafe { NonZeroU16::new_unchecked(4096) };

    pub const INIT_HANDSHAKE: u8 = 0;
    pub const END_HANDSHAKE: u8  = 1;
    pub const PACKET: u8         = 2;
    pub const DISCONNECT: u8     = 3;
    pub const CHECK: u8          = 4;
    pub const CHECKED: u8        = 5;
    pub const KEEP_ALIVE: u8     = 6;
}