diff --git a/framed/Cargo.toml b/framed/Cargo.toml index 9a3efb1..6f60201 100644 --- a/framed/Cargo.toml +++ b/framed/Cargo.toml @@ -1,5 +1,8 @@ [package] name = "framed" +# Next version is 0.2.0. I changed the type of max_encoded_len +# was: pub fn max_encoded_len(usize) -> Result +# now: pub const fn max_encoded_len(usize) -> usize version = "0.1.4" description = "Send and receive data over lossy streams of bytes." authors = ["Alex Helfet "] diff --git a/framed/src/lib.rs b/framed/src/lib.rs index 1a34b42..d6a6c81 100644 --- a/framed/src/lib.rs +++ b/framed/src/lib.rs @@ -53,6 +53,10 @@ //! /// just been received. //! pub type Encoded = [u8]; //! +//! /// A buffer that is used as temporary storage. +//! /// There are no guarantees on its contents after use. +//! pub type TempBuffer = [u8]; +//! //! /// Heap-allocated user data used as a return type. //! #[cfg(feature = "use_std")] //! pub struct BoxPayload(_); @@ -82,8 +86,15 @@ #![deny(warnings)] #![cfg_attr(not(feature = "use_std"), no_std)] +// TODO: Disable this when toolchain != nightly. +#![feature(const_fn)] + // ## extern crate statements extern crate cobs; + +#[cfg(feature = "use_std")] +extern crate core; + extern crate ref_slice; #[cfg(feature = "typed")] @@ -125,6 +136,10 @@ pub type Payload = [u8]; /// just been received. pub type Encoded = [u8]; +/// A buffer that is used as temporary storage. +/// There are no guarantees on its contents after use. +pub type TempBuffer = [u8]; + // Note: BoxPayload and BoxEncoded store data in a Vec as that's // what `cobs` returns us and converting them into a Box<[u8]> (with // Vec::into_boxed_slice(self)) would require re-allocation. @@ -193,9 +208,9 @@ const FOOTER_LEN: usize = 1; /// /// This function will panic if `dest` is not large enough for the encoded frame. /// Ensure `dest.len() >= max_encoded_len(p.len())`. -pub fn encode_to_slice(p: &Payload, dest: &mut [u8]) -> Result { +pub fn encode_to_slice(p: &Payload, dest: &mut Encoded) -> Result { // Panic if code won't fit in `dest` because this is a programmer error. - assert!(max_encoded_len(p.len())? <= dest.len()); + assert!(max_encoded_len(p.len()) <= dest.len()); let cobs_len = cobs::encode(&p, &mut dest[HEADER_LEN..]); let footer_idx = HEADER_LEN + cobs_len; @@ -210,7 +225,7 @@ pub fn encode_to_slice(p: &Payload, dest: &mut [u8]) -> Result { /// Encode the supplied payload data as a frame and return it on the heap. #[cfg(feature = "use_std")] pub fn encode_to_box(p: &Payload) -> Result { - let mut buf = vec![0; max_encoded_len(p.len())?]; + let mut buf = vec![0; max_encoded_len(p.len())]; let len = encode_to_slice(p, &mut *buf)?; buf.truncate(len); Ok(BoxEncoded::from(buf)) @@ -337,8 +352,10 @@ pub fn decode_from_reader(r: &mut Read) -> Result { decode_to_box(&*next_frame) } -/// Returns the maximum possible decoded length given a frame with -/// the encoded length supplied. +/// Returns an upper bound for the decoded length of the payload +/// within a frame with the encoded length supplied. +/// +/// Useful for calculating an appropriate buffer length. pub fn max_decoded_len(code_len: usize) -> Result { let framing_len = HEADER_LEN + FOOTER_LEN; if code_len < framing_len { @@ -351,14 +368,50 @@ pub fn max_decoded_len(code_len: usize) -> Result { Ok(cobs_decode_limit) } -/// Returns the maximum possible encoded length for a frame with +/// Returns an upper bound for the encoded length of a frame with /// the payload length supplied. -pub fn max_encoded_len(payload_len: usize) -> Result { - Ok(HEADER_LEN - + cobs::max_encoding_length(payload_len) - + FOOTER_LEN) +/// +/// Useful for calculating an appropriate buffer length. +pub const fn max_encoded_len(payload_len: usize) -> usize { + HEADER_LEN + + cobs_max_encoded_len(payload_len) + + FOOTER_LEN } +/// Copied from `cobs` crate to make a `const` version. +/// +/// Source: https://github.com/awelkie/cobs.rs/blob/f8ff1ad2aa7cd069a924d75170d3def3fa6df10b/src/lib.rs#L183-L188 +/// +/// TODO: Submit a PR to `cobs` to make `cobs::max_encoding_length` a `const fn`. +/// Issue for this: https://github.com/fluffysquirrels/framed-rs/issues/19 +const fn cobs_max_encoded_len(payload_len: usize) -> usize { + payload_len + + (payload_len / 254) + + // This `+ 1` was + // `+ if payload_len % 254 > 0 { 1 } else { 0 }` in cobs.rs, + // but that won't compile in a const fn. `1` is less than both the + // values in the if and else branches, so use that instead, with the + // acceptable cost of allocating 1 byte more than required some of the + // time. + // + // const fn compiler error was: + // ``` + // error[E0019]: constant function contains unimplemented expression type + // --> framed/src/lib.rs:388:11 + // | + // 388 | + if payload_len % 254 > 0 { 1 } else { 0 } + // | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + // + // error: aborting due to previous error + // ``` + // + // Relevant section of const fn design doc: + // https://github.com/rust-lang/rfcs/blob/5f69ff50de1fb6d0dd8c005b4f11f6e436e1f34c/text/0911-const-fn.md#detailed-design + // const fn tracking issue: https://github.com/rust-lang/rust/issues/24111 + + + 1 +} /// Sends encoded frames over an inner `io::Write` instance. #[cfg(feature = "use_std")] @@ -446,11 +499,11 @@ mod tests { #[test] fn max_encoded_len_ok() { - assert_eq!(max_encoded_len(0) .unwrap(), 1); - assert_eq!(max_encoded_len(1) .unwrap(), 3); - assert_eq!(max_encoded_len(2) .unwrap(), 4); - assert_eq!(max_encoded_len(254).unwrap(), 256); - assert_eq!(max_encoded_len(255).unwrap(), 258); + assert_eq!(max_encoded_len(0) , 2); + assert_eq!(max_encoded_len(1) , 3); + assert_eq!(max_encoded_len(2) , 4); + assert_eq!(max_encoded_len(254), 257); + assert_eq!(max_encoded_len(255), 258); } #[test] diff --git a/framed/src/typed.rs b/framed/src/typed.rs index 11cfa7b..4f4a2fc 100644 --- a/framed/src/typed.rs +++ b/framed/src/typed.rs @@ -8,23 +8,83 @@ //! length types (arrays, maps). Its lack of stability fits with the //! frame encoding in this crate: unsuitable for long-term storage or //! transmission between different versions of an application. -//! -//! This module currently requires `std`, the standard library. +use ::{Encoded, TempBuffer}; use error::{Result}; use serde::Serialize; use serde::de::DeserializeOwned; use ssmarshal; +#[cfg(feature = "use_std")] use std::io::{Read, Write}; -use std::marker::PhantomData; -use std::mem::size_of; +use core::marker::PhantomData; +use core::mem::size_of; + +/// Serializes and encodes the supplied value `v` into destination +/// buffer `dest`, using `ser_buf` as a temporary serialization buffer. +/// +/// # Examples +/// +/// ```rust +/// extern crate framed; +/// use framed::typed::*; +/// extern crate serde; +/// #[macro_use] +/// extern crate serde_derive; +/// +/// #[derive(Serialize)] +/// struct Test { +/// a: u8, +/// b: u16, +/// } +/// +/// fn main() { +/// let mut ser_buf = [0u8; max_serialize_buf_len::()]; +/// let mut encoded = [0u8; max_encoded_len::()]; +/// encode_to_slice::( +/// &Test { a: 1, b: 2 }, +/// &mut ser_buf, +/// &mut encoded +/// ).expect("encode ok"); +/// } +/// ``` +/// +pub fn encode_to_slice( + v: &T, + ser_buf: &mut TempBuffer, + dest: &mut Encoded, +) -> Result { + assert!(ser_buf.len() >= max_serialize_buf_len::()); + assert!(dest.len() >= max_encoded_len::()); + + let ser_len = ssmarshal::serialize(ser_buf, v)?; + let ser = &ser_buf[0..ser_len]; + super::encode_to_slice(ser, dest) +} + +/// Returns an upper bound for the encoded length of a frame with a +/// serialized `T` value as its payload. +/// +/// Useful for calculating an appropriate buffer length. +pub const fn max_encoded_len() -> usize { + super::max_encoded_len(max_serialize_buf_len::()) +} + +/// Returns an upper bound for the temporary serialization buffer +/// length needed by `encode_to_slice` when serializing a +/// value of type `T`. +pub const fn max_serialize_buf_len() -> usize { + size_of::() +} + /// Sends encoded structs of type `T` over an inner `io::Write` instance. +#[cfg(feature = "use_std")] pub struct Sender { w: W, _t: PhantomData, } +#[cfg(feature = "use_std")] impl Sender { /// Construct a `Sender` that sends encoded structs over the supplied /// `io::Write`. @@ -54,6 +114,8 @@ impl Sender { /// /// See also: [`send`](#method.send) pub fn queue(&mut self, v: &T) -> Result { + // TODO: Re-use encode_to_slice + // This uses a dynamically allocated buffer. // // I couldn't get a no_std version to compile with a stack-allocated @@ -84,7 +146,7 @@ impl Sender { #[cfg(feature = "trace")] { println!("framed: Serialized = {:?}", ser); } - super::encode_to_writer(&ser, &mut self.w) + ::encode_to_writer(&ser, &mut self.w) } /// Encode the supplied payload as a frame, write it to the @@ -102,11 +164,13 @@ impl Sender { } /// Receives encoded structs of type `T` from an inner `io::Read` instance. +#[cfg(feature = "use_std")] pub struct Receiver { r: R, _t: PhantomData, } +#[cfg(feature = "use_std")] impl Receiver { /// Construct a `Receiver` that receives encoded structs from the supplied /// `io::Read`. @@ -133,9 +197,6 @@ impl Receiver { #[cfg(test)] mod tests { - use channel::Channel; - use error::Error; - use std::io::{Read, Write}; use super::*; #[derive(Debug, Deserialize, Eq, PartialEq, Serialize)] @@ -156,26 +217,7 @@ mod tests { a: [u8; 3], } - #[test] - fn one() { - let (mut tx, mut rx) = pair(); - let v = val(); - tx.send(&v).unwrap(); - let r = rx.recv().unwrap(); - println!("r: {:#?}", r); - assert_eq!(v, r); - } - - #[test] - fn empty_input() { - let (mut _tx, mut rx) = pair(); - match rx.recv() { - Err(Error::EofBeforeFrame) => (), - e @ _ => panic!("Bad value: {:?}", e) - } - } - - fn val() -> Test { + fn test_val() -> Test { let v = Test { i8: 1, i16: 2, @@ -196,10 +238,93 @@ mod tests { v } - fn pair() -> (Sender, Test>, Receiver, Test>) { - let c = Channel::new(); - let tx = Sender::new(Box::new(c.writer()) as Box); - let rx = Receiver::new(Box::new(c.reader()) as Box); - (tx, rx) + mod slice_tests { + use super::*; + + #[test] + fn first() { + let mut ser_buf = [0u8; 100]; + let mut enc_buf = [0u8; 100]; + let len = super::encode_to_slice( + &test_val(), &mut ser_buf, &mut enc + ).unwrap(); + let enc = &enc_buf[0..len]; + + super::decode_from_slice(&enc + // TODO: Deserialize the test value. + } + + #[test] + #[should_panic] + fn bad_ser_buf_len() { + let mut ser_buf = [0u8; 1]; + let mut enc = [0u8; 100]; + super::encode_to_slice( + &test_val(), &mut ser_buf, &mut enc + ).unwrap(); + } + + #[test] + #[should_panic] + fn bad_dest_len() { + let mut ser_buf = [0u8; 100]; + let mut enc = [0u8; 1]; + super::encode_to_slice( + &test_val(), &mut ser_buf, &mut enc + ).unwrap(); + } + } + + mod len_tests { + use super::*; + + #[test] + fn serialize_buf_len() { + assert_eq!(max_serialize_buf_len::(), 40); + assert_eq!(max_serialize_buf_len::(), 1); + assert_eq!(max_serialize_buf_len::(), 2); + assert_eq!(max_serialize_buf_len::(), 4); + assert_eq!(max_serialize_buf_len::(), 8); + } + + #[test] + fn encoded_len() { + assert_eq!(max_encoded_len::(), 42); + assert_eq!(max_encoded_len::(), 3); + } + } + + #[cfg(feature = "use_std")] + mod rw_tests { + use channel::Channel; + use error::Error; + use std::io::{Read, Write}; + use super::*; + + #[test] + fn one() { + let (mut tx, mut rx) = pair(); + let v = test_val(); + tx.send(&v).unwrap(); + let r = rx.recv().unwrap(); + println!("r: {:#?}", r); + assert_eq!(v, r); + } + + #[test] + fn empty_input() { + let (mut _tx, mut rx) = pair(); + match rx.recv() { + Err(Error::EofBeforeFrame) => (), + e @ _ => panic!("Bad value: {:?}", e) + } + } + + fn pair() -> (Sender, Test>, Receiver, Test>) { + let c = Channel::new(); + let tx = Sender::new(Box::new(c.writer()) as Box); + let rx = Receiver::new(Box::new(c.reader()) as Box); + (tx, rx) + } } }