ecksport-codec 0.3.3

Utils for defining type encodings in the Ecksport RPC library
Documentation
//! Core codec trait and trait impls provided for ergonomics.
//!
//! This is not the most efficient impl in general, but it's efficient enough
//! and significantly improves the ergonomics of using the codec system.

use crate::errors::CodecError;
use crate::util;

/// Trait defined to messages over the wire.
pub trait RpcCodec: Sized {
    fn from_slice(buf: &[u8]) -> Result<Self, CodecError>;
    fn fill_buf(&self, buf: &mut Vec<u8>) -> Result<(), CodecError>;
}

impl RpcCodec for () {
    fn from_slice(_buf: &[u8]) -> Result<Self, CodecError> {
        Ok(())
    }

    fn fill_buf(&self, _buf: &mut Vec<u8>) -> Result<(), CodecError> {
        Ok(())
    }
}

/// Direct memcpying codec for transmitting bytebufs efficiently.  This encodes
/// the vec directly and gobbles the entire input.
impl RpcCodec for Vec<u8> {
    fn from_slice(buf: &[u8]) -> Result<Self, CodecError> {
        Ok(buf.to_vec())
    }

    fn fill_buf(&self, buf: &mut Vec<u8>) -> Result<(), CodecError> {
        buf.extend_from_slice(&self);
        Ok(())
    }
}

macro_rules! decl_tuple_codec {
    ( $($vident:ident: $tident:ident),* ) => {
        impl<$($tident: RpcCodec),*> RpcCodec for ($($tident),*) {
            fn from_slice(buf: &[u8]) -> Result<Self, CodecError> {
                let mut cur = util::Cursor::new(buf);

                $(let $vident = cur.take_len_tagged_inst::<$tident>()?;)*

                if !cur.is_at_end() {
                    return Err(CodecError::LeftoverBytes(
                        cur.remaining_bytes(),
                        cur.inner().len(),
                    ));
                }


                Ok(($($vident),*))
            }

            fn fill_buf(&self, buf: &mut Vec<u8>) -> Result<(), CodecError> {
                let ($($vident),*) = self;

                $(util::write_len_tagged_inst($vident, buf)?;)*

                Ok(())
            }
        }
    }
}

decl_tuple_codec!(t1: T1, t2: T2);
decl_tuple_codec!(t1: T1, t2: T2, t3: T3);
decl_tuple_codec!(t1: T1, t2: T2, t3: T3, t4: T4);

// FIXME figure out a better way to do this
/*impl<T: RpcCodec> RpcCodec for Vec<T> {
    fn from_slice(buf: &[u8]) -> Result<Self, CodecError> {
        let mut cur = Cursor::new(buf);

        let mut list = Vec::new();
        let cnt = cur.take_u32()?;
        for _ in 0..cnt {
            let ent = cur.take_len_tagged_inst::<T>()?;
            list.push(ent);
        }

        if !cur.is_at_end() {
            return Err(CodecError::LeftoverBytes(
                cur.remaining_bytes(),
                cur.buf.len(),
            ));
        }

        Ok(list)
    }

    fn fill_buf(&self, buf: &mut Vec<u8>) -> Result<(), CodecError> {
        let cnt = buf.len();
        if cnt > u32::MAX as usize {
            return Err(CodecError::ContainerTooLarge(cnt));
        }

        write_u32(cnt as u32, buf);
        for e in self {
            write_len_tagged_inst(e, buf)?;
        }

        Ok(())
    }
}*/

/// Similar implementation to `Vec<u8>` but with UTF-8 check.
impl RpcCodec for String {
    fn from_slice(buf: &[u8]) -> Result<Self, CodecError> {
        Ok(std::str::from_utf8(buf)
            .map_err(|_| CodecError::NonUtf8String)?
            .to_owned())
    }

    fn fill_buf(&self, buf: &mut Vec<u8>) -> Result<(), CodecError> {
        buf.extend_from_slice(self.as_bytes());
        Ok(())
    }
}

macro_rules! decl_int_codec {
    ( $ity:ty ) => {
        impl RpcCodec for $ity {
            fn from_slice(buf: &[u8]) -> Result<Self, CodecError> {
                const NBYTES: usize = (<$ity>::BITS / 8) as usize;
                let mut cur = util::Cursor::new(buf);
                let ibuf = cur.take_arr::<NBYTES>()?;
                Ok(<$ity>::from_be_bytes(ibuf))
            }

            fn fill_buf(&self, buf: &mut Vec<u8>) -> Result<(), CodecError> {
                buf.extend_from_slice(&self.to_be_bytes());
                Ok(())
            }
        }
    };
}

decl_int_codec!(i8);
decl_int_codec!(i16);
decl_int_codec!(i32);
decl_int_codec!(i64);
decl_int_codec!(u16);
decl_int_codec!(u32);
decl_int_codec!(u64);

macro_rules! decl_intvec_codec {
    ( $ity:ty ) => {
        impl RpcCodec for Vec<$ity> {
            fn from_slice(buf: &[u8]) -> Result<Self, CodecError> {
                const NBYTES: usize = (<$ity>::BITS / 8) as usize;

                // Check to see that it matches what we expect.
                let leftover = buf.len() % NBYTES;
                if leftover > 0 {
                    return Err(CodecError::LeftoverBytes(leftover, buf.len()));
                }

                let mut cur = util::Cursor::new(buf);

                let cnt = buf.len() / NBYTES;
                let mut list = Vec::new();

                for _ in 0..cnt {
                    let ibuf = cur.take_arr::<NBYTES>()?;
                    list.push(<$ity>::from_be_bytes(ibuf));
                }

                Ok(list)
            }

            fn fill_buf(&self, buf: &mut Vec<u8>) -> Result<(), CodecError> {
                for i in self {
                    buf.extend_from_slice(&i.to_be_bytes());
                }
                Ok(())
            }
        }
    };
}

decl_intvec_codec!(i8);
decl_intvec_codec!(i16);
decl_intvec_codec!(i32);
decl_intvec_codec!(i64);
decl_intvec_codec!(u16);
decl_intvec_codec!(u32);
decl_intvec_codec!(u64);

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

    #[test]
    fn test_tuple_3() {
        type Tup = (String, String, Vec<u8>);
        let val: Tup = ("foo".to_string(), "bar".to_string(), vec![2, 5, 7, 10, 12]);

        let buf = encode_to_vec(&val).expect("test: encode");
        eprintln!("val {val:?}, buf {buf:?}");
        let dec = <Tup as RpcCodec>::from_slice(&buf).expect("test: decode");

        assert_eq!(dec, val);
    }

    #[test]
    fn test_int_arr() {
        let v: Vec<i16> = vec![5, 10, 15, -1, -2];
        let buf = encode_to_vec(&v).expect("test: encode");
        assert_eq!(buf.as_slice(), &[0, 5, 0, 10, 0, 15, 255, 255, 255, 254]);
        let res = <Vec<i16> as RpcCodec>::from_slice(&buf).expect("test: decode");
        assert_eq!(res, v);
    }

    #[test]
    fn test_int_arr_fail() {
        let raw_buf = &[0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3];
        let res = <Vec<u64> as RpcCodec>::from_slice(raw_buf);
        eprintln!("result {res:?}");
        if !matches!(res, Err(CodecError::LeftoverBytes(_, _))) {
            panic!("not expected result");
        }
    }

    #[test]
    fn test_string_invalid() {
        // This is invalid UTF-8 because the high bit is set indicating there's
        // a subsequent byte but there isn't one.
        let buf = vec![0xff];
        let res = <String as RpcCodec>::from_slice(&buf);
        eprintln!("result {res:?}");
        if !matches!(res, Err(CodecError::NonUtf8String)) {
            panic!("not expected result");
        }
    }
}