]> git.lizzy.rs Git - connect-rs.git/blobdiff - src/protocol.rs
refactor to have datagram already serialized in memory
[connect-rs.git] / src / protocol.rs
index ee4d8dccdad16bc1f02197d6d7b131c10bbfaaae..1848a830de863df99530d0aa1c634a28b750568c 100644 (file)
@@ -4,6 +4,13 @@ use std::error::Error;
 
 const VERSION: u16 = 1;
 
+pub const SIZE_PREFIX_BYTE_SIZE: usize = 4;
+const VERSION_BYTE_SIZE: usize = 2;
+const TAG_BYTE_SIZE: usize = 2;
+
+pub const DATAGRAM_HEADER_BYTE_SIZE: usize =
+    SIZE_PREFIX_BYTE_SIZE + VERSION_BYTE_SIZE + TAG_BYTE_SIZE;
+
 /// Encountered when there is an issue constructing, serializing, or deserializing a [`ConnectDatagram`].
 ///
 #[derive(Debug, Clone)]
@@ -15,9 +22,9 @@ pub enum DatagramError {
     TooLargeMessage,
 
     /// Did not provide the complete byte-string necessary to deserialize the [`ConnectDatagram`].
-    IncompleteBytes,
+    InsufficientBytes,
 
-    /// Wraps a [`TryFromSliceError`] encountered when the version or recipient tags cannot be
+    /// Wraps a [`TryFromSliceError`] encountered when the version or tag fields cannot be
     /// parsed from the provided bytes.
     BytesParseFail(TryFromSliceError),
 }
@@ -29,29 +36,37 @@ impl std::fmt::Display for DatagramError {
         match self {
             DatagramError::EmptyMessage => formatter.write_str("tried to construct a `ConnectDatagram` with an empty message body"),
             DatagramError::TooLargeMessage => formatter.write_str("tried to construct a `ConnectDatagram` with a message body larger than 100MB"),
-            DatagramError::IncompleteBytes => formatter.write_str("did not provide the complete byte-string necessary to deserialize the `ConnectDatagram`"),
+            DatagramError::InsufficientBytes => formatter.write_str("did not provide the complete byte-string necessary to deserialize the `ConnectDatagram`"),
             DatagramError::BytesParseFail(err) => std::fmt::Display::fmt(err, formatter),
         }
     }
 }
 
-/// A simple size-prefixed packet format containing a version tag, recipient tag, and message body.
+/// A simple size-prefixed packet format containing a version id, optional tag, and message payload.
 ///
 /// The version tag is decided by the library version and used to maintain backwards
 /// compatibility with previous datagram formats.
 ///
 #[derive(Clone)]
 pub struct ConnectDatagram {
-    version: u16,
-    recipient: u16,
-    data: Option<Vec<u8>>,
+    buffer: Vec<u8>,
 }
 
+#[allow(dead_code)]
 impl ConnectDatagram {
-    /// Creates a new [`ConnectDatagram`] based on an intended recipient and message body.
+    /// Creates a new [`ConnectDatagram`] with the intended message body.
+    ///
+    /// This will return a [EmptyMessage](`DatagramError::EmptyMessage`) error if the `data`
+    /// parameter contains no bytes, or in other words, when there is no message body.
     ///
-    /// The version tag is decided by the library version and used to maintain backwards
-    /// compatibility with previous datagram formats.
+    /// This will return a [TooLargeMessage](`DatagramError::TooLargeMessage`) error if the `data`
+    /// parameter contains a buffer size greater than 100,000,000 (bytes), or 100MB.
+    ///
+    pub fn new(data: Vec<u8>) -> Result<Self, DatagramError> {
+        Self::with_tag(0, data)
+    }
+
+    /// Creates a new [`ConnectDatagram`] based on an intended tag field and message body.
     ///
     /// This will return a [EmptyMessage](`DatagramError::EmptyMessage`) error if the `data`
     /// parameter contains no bytes, or in other words, when there is no message body.
@@ -59,178 +74,232 @@ impl ConnectDatagram {
     /// This will return a [TooLargeMessage](`DatagramError::TooLargeMessage`) error if the `data`
     /// parameter contains a buffer size greater than 100,000,000 (bytes), or 100MB.
     ///
-    pub fn new(recipient: u16, data: Vec<u8>) -> Result<Self, DatagramError> {
+    pub fn with_tag(tag: u16, data: Vec<u8>) -> Result<Self, DatagramError> {
         if data.len() > 100_000_000 {
             Err(DatagramError::TooLargeMessage)
         } else if data.len() > 0 {
-            Ok(Self {
-                version: VERSION,
-                recipient,
-                data: Some(data),
-            })
+            let mut buffer: Vec<u8> = Vec::with_capacity(DATAGRAM_HEADER_BYTE_SIZE + data.len());
+
+            buffer.extend(
+                ((DATAGRAM_HEADER_BYTE_SIZE - SIZE_PREFIX_BYTE_SIZE + data.len()) as u32)
+                    .to_be_bytes(),
+            );
+            buffer.extend(VERSION.to_be_bytes());
+            buffer.extend(tag.to_be_bytes());
+            buffer.extend(data);
+
+            Ok(Self { buffer })
         } else {
             Err(DatagramError::EmptyMessage)
         }
     }
 
-    /// Gets the version number of the datagram.
+    /// Updates the size prefix value in the internal buffer to the current size of the buffer.
+    ///
+    #[inline]
+    fn update_size_prefix(&mut self) {
+        self.buffer.splice(
+            ..VERSION_BYTE_SIZE,
+            ((DATAGRAM_HEADER_BYTE_SIZE - SIZE_PREFIX_BYTE_SIZE + self.data_size()) as u32)
+                .to_be_bytes(),
+        );
+    }
+
+    /// Gets the version number field of the datagram protocol.
     ///
     pub fn version(&self) -> u16 {
-        self.version
+        let start = SIZE_PREFIX_BYTE_SIZE;
+        let end = start + VERSION_BYTE_SIZE;
+
+        let buf = self.buffer[start..end]
+            .as_ref()
+            .try_into()
+            .expect("could not parse big-endian bytes into version variable");
+
+        u16::from_be_bytes(buf)
     }
 
-    /// Gets the recipient of the datagram.
+    /// Gets the tag field of the datagram.
     ///
-    pub fn recipient(&self) -> u16 {
-        self.recipient
+    pub fn tag(&self) -> u16 {
+        let start = SIZE_PREFIX_BYTE_SIZE + VERSION_BYTE_SIZE;
+        let end = start + TAG_BYTE_SIZE;
+
+        let buf = self.buffer[start..end]
+            .as_ref()
+            .try_into()
+            .expect("could not parse big-endian bytes into tag variable");
+
+        u16::from_be_bytes(buf)
+    }
+
+    /// Sets the message body of the datagram.
+    ///
+    pub fn set_tag(&mut self, tag: u16) {
+        let start = SIZE_PREFIX_BYTE_SIZE + VERSION_BYTE_SIZE;
+        let end = start + TAG_BYTE_SIZE;
+
+        self.buffer.splice(start..end, tag.to_be_bytes());
     }
 
     /// Gets the message body of the datagram.
     ///
-    pub fn data(&self) -> Option<&Vec<u8>> {
-        self.data.as_ref()
+    pub fn data(&self) -> &[u8] {
+        &self.buffer[DATAGRAM_HEADER_BYTE_SIZE..]
     }
 
-    /// Takes ownership of the message body of the datagram.
+    /// Sets the message body of the datagram and returns the previous contents.
     ///
-    pub fn take_data(&mut self) -> Option<Vec<u8>> {
-        self.data.take()
+    pub fn set_data(&mut self, data: Vec<u8>) -> Result<Vec<u8>, DatagramError> {
+        let data_size = data.len();
+
+        if data_size > 100_000_000 {
+            Err(DatagramError::TooLargeMessage)
+        } else if data_size > 0 {
+            if data_size < self.buffer.len() {
+                self.buffer.truncate(DATAGRAM_HEADER_BYTE_SIZE + data_size);
+            }
+
+            let old_data = self
+                .buffer
+                .splice(DATAGRAM_HEADER_BYTE_SIZE.., data)
+                .collect();
+
+            self.update_size_prefix();
+
+            Ok(old_data)
+        } else {
+            Err(DatagramError::EmptyMessage)
+        }
     }
 
     /// Calculates the size-prefixed serialized byte-size of the datagram.
     ///
     /// This will include the byte-size of the size-prefix.
     ///
-    pub fn size(&self) -> usize {
-        let data_len = if let Some(data) = self.data() {
-            data.len()
-        } else {
-            0
-        };
+    pub fn serialized_size(&self) -> usize {
+        self.buffer.len()
+    }
 
-        8 + data_len
+    /// Calculates the byte-size of the datagram message body.
+    ///
+    /// This will exclude all datagram header fields like the tag.
+    ///
+    pub fn data_size(&self) -> usize {
+        self.buffer.len() - DATAGRAM_HEADER_BYTE_SIZE
     }
 
     /// Constructs a serialized representation of the datagram contents.
     ///
-    pub(crate) fn bytes(&self) -> Vec<u8> {
-        let mut bytes = Vec::with_capacity(self.size());
-
-        bytes.extend(&self.version.to_be_bytes());
-        bytes.extend(&self.recipient.to_be_bytes());
-
-        if let Some(data) = self.data() {
-            bytes.extend(data.as_slice());
-        }
-
-        return bytes;
+    pub(crate) fn as_bytes(&self) -> &[u8] {
+        self.buffer.as_slice()
     }
 
     /// Serializes the datagram.
     ///
-    pub fn encode(self) -> Vec<u8> {
-        let content_encoded = self.bytes();
-        let size: u32 = (content_encoded.len()) as u32;
-
-        let mut bytes = Vec::from(size.to_be_bytes());
-        bytes.extend(content_encoded);
-
-        return bytes;
+    pub fn into_bytes(self) -> Vec<u8> {
+        self.buffer
     }
 
-    /// Deserializes the datagram from a buffer.
-    ///
-    /// The buffer **should not** contain the size-prefix, and only contain the byte contents of the
-    /// struct (version, recipient, and message body).
+    /// Deserializes the datagram from bytes.
     ///
-    pub fn decode(mut buffer: Vec<u8>) -> Result<Self, DatagramError> {
-        if buffer.len() > 4 {
-            let mem_size = std::mem::size_of::<u16>();
-            let data = buffer.split_off(mem_size * 2);
-
-            let (version_bytes, recipient_bytes) = buffer.split_at(mem_size);
-
-            match version_bytes.try_into() {
-                Ok(version_slice) => match recipient_bytes.try_into() {
-                    Ok(recipient_slice) => {
-                        let version = u16::from_be_bytes(version_slice);
-                        let recipient = u16::from_be_bytes(recipient_slice);
-
-                        Ok(Self {
-                            version,
-                            recipient,
-                            data: Some(data),
-                        })
-                    }
+    pub fn from_bytes(buffer: &[u8]) -> Result<Self, DatagramError> {
+        if buffer.len() > DATAGRAM_HEADER_BYTE_SIZE {
+            Ok(Self {
+                buffer: buffer.to_vec(),
+            })
+        } else {
+            Err(DatagramError::InsufficientBytes)
+        }
+    }
 
-                    Err(err) => Err(DatagramError::BytesParseFail(err)),
-                },
+    /// Deserializes the datagram from bytes, and infers the size-prefix given the data.
+    ///
+    pub fn from_bytes_without_prefix(buffer: &[u8]) -> Result<Self, DatagramError> {
+        if buffer.len() > DATAGRAM_HEADER_BYTE_SIZE - SIZE_PREFIX_BYTE_SIZE {
+            let mut new_buffer = Vec::with_capacity(SIZE_PREFIX_BYTE_SIZE + buffer.len());
+            new_buffer.extend((buffer.len() as u32).to_be_bytes());
+            new_buffer.extend_from_slice(buffer);
 
-                Err(err) => Err(DatagramError::BytesParseFail(err)),
-            }
+            Ok(Self { buffer: new_buffer })
         } else {
-            Err(DatagramError::IncompleteBytes)
+            Err(DatagramError::InsufficientBytes)
         }
     }
 }
 
 #[cfg(test)]
 mod tests {
-    use crate::protocol::ConnectDatagram;
+    use crate::{protocol::ConnectDatagram, DATAGRAM_HEADER_BYTE_SIZE};
 
     #[test]
     fn serialized_size() -> anyhow::Result<()> {
-        let mut data = Vec::new();
-        for _ in 0..5 {
-            data.push(1);
-        }
+        let data: Vec<u8> = vec![0, 1, 2, 3, 4];
         assert_eq!(5, data.len());
 
-        let sample = ConnectDatagram::new(1, data)?;
-        assert_eq!(8 + 5, sample.encode().len());
+        let sample = ConnectDatagram::with_tag(1, data)?;
+        assert_eq!(DATAGRAM_HEADER_BYTE_SIZE + 5, sample.serialized_size());
+        assert_eq!(DATAGRAM_HEADER_BYTE_SIZE + 5, sample.into_bytes().len());
 
         Ok(())
     }
 
     #[test]
-    fn take_data() -> anyhow::Result<()> {
-        let mut data = Vec::new();
-        for _ in 0..5 {
-            data.push(1);
-        }
+    fn get_data() -> anyhow::Result<()> {
+        let data: Vec<u8> = vec![0, 1, 2, 3, 4];
+        assert_eq!(5, data.len());
 
-        let mut sample = ConnectDatagram::new(1, data)?;
+        let sample = ConnectDatagram::with_tag(1, data)?;
 
-        let taken_data = sample.take_data().unwrap();
-        assert!(sample.data().is_none());
+        let taken_data = sample.data();
         assert_eq!(5, taken_data.len());
 
         Ok(())
     }
 
-    #[async_std::test]
-    async fn encode_and_decode() -> anyhow::Result<()> {
-        let mut data = Vec::new();
-        for _ in 0..5 {
-            data.push(1);
-        }
+    #[test]
+    fn encode_and_decode() -> anyhow::Result<()> {
+        let data: Vec<u8> = vec![0, 1, 2, 3, 4];
+        assert_eq!(5, data.len());
+
+        let sample = ConnectDatagram::with_tag(1, data)?;
+        let serialized_size = sample.serialized_size();
+        assert_eq!(DATAGRAM_HEADER_BYTE_SIZE + 5, serialized_size);
+
+        let payload = sample.into_bytes();
+        assert_eq!(serialized_size, payload.len());
+
+        let sample_back_res = ConnectDatagram::from_bytes(payload.as_slice());
+        assert!(sample_back_res.is_ok());
+
+        let sample_back = sample_back_res.unwrap();
+        assert_eq!(sample_back.version(), 1);
+        assert_eq!(sample_back.tag(), 1);
+        assert_eq!(sample_back.data().len(), 5);
+
+        Ok(())
+    }
+
+    #[test]
+    fn encode_and_decode_without_prefix() -> anyhow::Result<()> {
+        let data: Vec<u8> = vec![0, 1, 2, 3, 4];
         assert_eq!(5, data.len());
 
-        let sample = ConnectDatagram::new(1, data)?;
-        let serialized_size = sample.size();
-        assert_eq!(8 + 5, serialized_size);
+        let sample = ConnectDatagram::with_tag(1, data)?;
+        let serialized_size = sample.serialized_size();
+        assert_eq!(DATAGRAM_HEADER_BYTE_SIZE + 5, serialized_size);
 
-        let mut payload = sample.encode();
+        let mut payload = sample.into_bytes();
         assert_eq!(serialized_size, payload.len());
 
-        let payload = payload.split_off(std::mem::size_of::<u32>());
-        let sample_back_res = ConnectDatagram::decode(payload);
+        let payload = payload.split_off(crate::protocol::SIZE_PREFIX_BYTE_SIZE);
+        let sample_back_res = ConnectDatagram::from_bytes_without_prefix(payload.as_slice());
         assert!(sample_back_res.is_ok());
 
         let sample_back = sample_back_res.unwrap();
         assert_eq!(sample_back.version(), 1);
-        assert_eq!(sample_back.recipient(), 1);
-        assert_eq!(sample_back.data().unwrap().len(), 5);
+        assert_eq!(sample_back.tag(), 1);
+        assert_eq!(sample_back.data().len(), 5);
 
         Ok(())
     }