diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..e6fac0a --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,26 @@ +on: [push, pull_request] + +name: Actions CI + +jobs: + build_and_test: + name: integer-encoding-rs + strategy: + fail-fast: false + matrix: + features: ["tokio_async", "futures_async", ""] + platform: [ubuntu-latest, windows-latest, macos-latest] + runs-on: ${{ matrix.platform }} + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + - uses: actions-rs/cargo@v1 + with: + command: check + args: --no-default-features --features=${{ matrix.features }} + - uses: actions-rs/cargo@v1 + with: + command: test + args: --no-default-features --features=${{ matrix.features }} diff --git a/.travis.yml b/.travis.yml index 7c506e3..08dca2c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,20 @@ language: rust -# Test on the latest versions of all channels. +os: + - linux + - osx + - windows +dist: focal rust: - - stable - - beta - - nightly + - stable + - nightly +# Test on the latest versions of all channels. +script: + - cargo test --features tokio_async --verbose + - cargo test --features futures_async --verbose + - cargo test --verbose + - cargo build --examples # Run this build on the "container-based infrastructure" # See http://docs.travis-ci.com/user/workers/container-based-infrastructure/. sudo: false diff --git a/Cargo.toml b/Cargo.toml index 2af3a41..44c3a57 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,41 @@ [package] name = "integer-encoding" -version = "1.0.7" +version = "4.0.2" authors = ["Lewin Bormann "] description = "varint+zigzag and fixedint integer encoding/decoding (https://developers.google.com/protocol-buffers/docs/encoding)" repository = "https://github.com/dermesser/integer-encoding-rs" documentation = "https://docs.rs/integer-encoding/" -license-file = "LICENSE" +license = "MIT" keywords = ["integer", "varint", "zigzag", "protobuf", "serialize"] +edition = "2018" [dependencies] +async-trait = { version = "0.1", optional = true } +tokio = { version = "1.0", features = ["io-util"], optional = true } +futures-util = { version = "0.3", optional = true, features = ["io"] } + +[dev-dependencies] +tokio = { version = "1.0", features = ["fs", "rt-multi-thread", "macros"] } +bencher = "~0.1" + +[[example]] +name = "encode_varint_from_stdin" +required-features = ["tokio_async"] + +[[example]] +name = "read_write_file" +required-features = ["tokio_async"] + +[[bench]] +name = "main" +harness = false + +[features] +# Enable one of these features if you want to use the AsyncRead/AsyncWrite traits from +# the futures crate instead of those from tokio. +tokio_async = ["tokio", "async-trait"] +futures_async = ["futures-util", "async-trait"] + +[package.metadata.docs.rs] +features = ["tokio_async"] + diff --git a/LICENSE b/LICENSE index cde2084..cc7245f 100644 --- a/LICENSE +++ b/LICENSE @@ -2,6 +2,7 @@ The MIT License (MIT) Copyright (c) 2016 Google Inc. (lewinb@google.com) -- though not an official Google product or in any way related! +Copyright (c) 2018-2020 Lewin Bormann (lbo@spheniscida.de) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to diff --git a/README.md b/README.md index 0b030be..97529ac 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # integer-encoding-rs [![crates.io](https://img.shields.io/crates/v/integer-encoding.svg)](https://crates.io/crates/integer-encoding) -[![Build Status](https://travis-ci.org/dermesser/integer-encoding-rs.svg?branch=master)](https://travis-ci.org/dermesser/integer-encoding-rs) +[![Actions CI](https://github.com/dermesser/integer-encoding-rs/workflows/Actions%20CI/badge.svg)](https://github.com/dermesser/integer-encoding-rs/actions) [full documentation](https://docs.rs/integer-encoding/) @@ -10,11 +10,27 @@ representations. The format is described here: [Google's protobuf integer encoding technique](https://developers.google.com/protocol-buffers/docs/encoding). +Please feel free to use `cargo bench` to determine the rate at which your +machine can encode and decode varints and fixedints. Note that one iteration +comprises each eight rounds of encoding (or decoding) a signed and an unsigned +integer each -- divide the resulting benchmark time by 16 in order to have a +rough estimate of time per operation. The integers are very large, so the +results represent the worst case. + +## Crate + +If you use Tokio v0.2 and you use the asynchronous types in this crate (feature +`tokio_async`), you may be interested in the `v2.0` branch. It is still +maintained with the occasional fix for edge cases and depends on Tokio v0.2. + ## FixedInt `FixedInt` casts integers to bytes by either copying the underlying memory or -performing a transmutation. The encoded values use machine endianness -(little-endian on x86). +performing a transmutation. The encoded values use are little-endian. + +However, a trait method is implemented for all integer types allowing convenient conversion between +little and big endian. That is, if you receive a big-endian on the wire and decode it, it will first +be interpreted as little-endian; converting will recover the correct value. ## VarInt diff --git a/benches/main.rs b/benches/main.rs new file mode 100644 index 0000000..476999c --- /dev/null +++ b/benches/main.rs @@ -0,0 +1,175 @@ +use bencher::Bencher; + +use integer_encoding::*; + +fn encode_v(b: &mut Bencher) { + let my_u64s = [ + 9494929199119074561, + 3823923198123425321, + 2595862268225688522, + 1231230009321245673, + 2909812083312547546, + 3492011001874124465, + 4848848884210156473, + 4012941340125654654, + ] as [u64; 8]; + let my_i64s = [ + -122193043711204545, + 2446312246543212452, + -445854216865433664, + 3242135654513135465, + -543122132545464312, + 3613543123031434343, + -431231254654543211, + 7854615463131234543, + ] as [i64; 8]; + + let mut dst = [0 as u8; 10]; + + b.iter(|| { + // 8x each. + my_u64s[0].encode_var(&mut dst); + my_u64s[1].encode_var(&mut dst); + my_u64s[2].encode_var(&mut dst); + my_u64s[3].encode_var(&mut dst); + my_u64s[4].encode_var(&mut dst); + my_u64s[5].encode_var(&mut dst); + my_u64s[6].encode_var(&mut dst); + my_u64s[7].encode_var(&mut dst); + + my_i64s[0].encode_var(&mut dst); + my_i64s[1].encode_var(&mut dst); + my_i64s[2].encode_var(&mut dst); + my_i64s[3].encode_var(&mut dst); + my_i64s[4].encode_var(&mut dst); + my_i64s[5].encode_var(&mut dst); + my_i64s[6].encode_var(&mut dst); + my_i64s[7].encode_var(&mut dst); + }); +} + +fn decode_v(b: &mut Bencher) { + let my_u64s = [ + 9494929199119074561, + 3823923198123425321, + 2595862268225688522, + 1231230009321245673, + 2909812083312547546, + 3492011001874124465, + 4848848884210156473, + 4012941340125654654, + ] as [u64; 8]; + let my_i64s = [ + -122193043711204545, + 2446312246543212452, + -445854216865433664, + 3242135654513135465, + -543122132545464312, + 3613543123031434343, + -431231254654543211, + 7854615463131234543, + ] as [i64; 8]; + + let u64_src = [ + my_u64s[0].encode_var_vec(), + my_u64s[1].encode_var_vec(), + my_u64s[2].encode_var_vec(), + my_u64s[3].encode_var_vec(), + my_u64s[4].encode_var_vec(), + my_u64s[5].encode_var_vec(), + my_u64s[6].encode_var_vec(), + my_u64s[7].encode_var_vec(), + ] as [Vec; 8]; + let i64_src = [ + my_i64s[0].encode_var_vec(), + my_i64s[1].encode_var_vec(), + my_i64s[2].encode_var_vec(), + my_i64s[3].encode_var_vec(), + my_i64s[4].encode_var_vec(), + my_i64s[5].encode_var_vec(), + my_i64s[6].encode_var_vec(), + my_i64s[7].encode_var_vec(), + ] as [Vec; 8]; + + b.iter(|| { + // 8x each. + u64::decode_var(&u64_src[0]).unwrap(); + u64::decode_var(&u64_src[1]).unwrap(); + u64::decode_var(&u64_src[2]).unwrap(); + u64::decode_var(&u64_src[3]).unwrap(); + u64::decode_var(&u64_src[4]).unwrap(); + u64::decode_var(&u64_src[5]).unwrap(); + u64::decode_var(&u64_src[6]).unwrap(); + u64::decode_var(&u64_src[7]).unwrap(); + + i64::decode_var(&i64_src[0]).unwrap(); + i64::decode_var(&i64_src[1]).unwrap(); + i64::decode_var(&i64_src[2]).unwrap(); + i64::decode_var(&i64_src[3]).unwrap(); + i64::decode_var(&i64_src[4]).unwrap(); + i64::decode_var(&i64_src[5]).unwrap(); + i64::decode_var(&i64_src[6]).unwrap(); + i64::decode_var(&i64_src[7]).unwrap(); + }); +} + +bencher::benchmark_group!(varint_benches, encode_v, decode_v); + +fn encode_f(b: &mut Bencher) { + let my_u64 = 94949291991190 as u64; + let my_i64 = -12219304371120 as i64; + + let mut dst = [0 as u8; 8]; + + b.iter(|| { + // 8x each. + my_u64.encode_fixed(&mut dst); + my_u64.encode_fixed(&mut dst); + my_u64.encode_fixed(&mut dst); + my_u64.encode_fixed(&mut dst); + my_u64.encode_fixed(&mut dst); + my_u64.encode_fixed(&mut dst); + my_u64.encode_fixed(&mut dst); + + my_i64.encode_fixed(&mut dst); + my_i64.encode_fixed(&mut dst); + my_i64.encode_fixed(&mut dst); + my_i64.encode_fixed(&mut dst); + my_i64.encode_fixed(&mut dst); + my_i64.encode_fixed(&mut dst); + my_i64.encode_fixed(&mut dst); + my_i64.encode_fixed(&mut dst); + }); +} + +fn decode_f(b: &mut Bencher) { + let my_u64 = 94949291991190 as u64; + let my_i64 = -12219304371120 as i64; + + let u64_src = my_u64.encode_fixed_vec(); + let i64_src = my_i64.encode_fixed_vec(); + + b.iter(|| { + // 8x each. + u64::decode_fixed(&u64_src); + u64::decode_fixed(&u64_src); + u64::decode_fixed(&u64_src); + u64::decode_fixed(&u64_src); + u64::decode_fixed(&u64_src); + u64::decode_fixed(&u64_src); + u64::decode_fixed(&u64_src); + + i64::decode_fixed(&i64_src); + i64::decode_fixed(&i64_src); + i64::decode_fixed(&i64_src); + i64::decode_fixed(&i64_src); + i64::decode_fixed(&i64_src); + i64::decode_fixed(&i64_src); + i64::decode_fixed(&i64_src); + i64::decode_fixed(&i64_src); + }); +} + +bencher::benchmark_group!(fixedint_benches, encode_f, decode_f); + +bencher::benchmark_main!(varint_benches, fixedint_benches); diff --git a/coverage.sh b/coverage.sh new file mode 100755 index 0000000..254e397 --- /dev/null +++ b/coverage.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -x + +KCOV=kcov +KCOV_OPTS="--exclude-pattern=/.cargo,/glibc,/usr/lib,/usr/include" +KCOV_OUT="./kcov-out/" + +export RUSTFLAGS="-C link-dead-code" + +TEST_BIN=$(cargo test 2>&1 > /dev/null | awk '/^ Running target\/debug\/deps\// { print $2 }') + +echo $TEST_BIN +${KCOV} ${KCOV_OPTS} ${KCOV_OUT} ${TEST_BIN} && xdg-open ${KCOV_OUT}/index.html diff --git a/examples/encode_varint_from_stdin.rs b/examples/encode_varint_from_stdin.rs new file mode 100644 index 0000000..c52bf32 --- /dev/null +++ b/examples/encode_varint_from_stdin.rs @@ -0,0 +1,37 @@ +use integer_encoding::VarInt; + +use std::io::{self, BufRead}; +use std::str::FromStr; + +fn binencode(b: &[u8]) -> String { + let mut s = String::new(); + for byte in b { + s.push_str(&format!("{:08b} ", byte)); + } + s +} + +fn main() { + let stdin = io::BufReader::new(io::stdin()); + + println!("Enter decimal numbers here:\n"); + for l in stdin.lines() { + if l.is_err() { + break; + } + let l = l.unwrap(); + match i64::from_str(&l) { + Ok(i) => println!( + "fixed: {:b} encoded (unsigned): {} encoded (signed): {}", + i, + if i >= 0 { + binencode(&(i as u64).encode_var_vec()) + } else { + "-".to_string() + }, + binencode(&i.encode_var_vec()) + ), + Err(e) => println!("{:?}", e), + } + } +} diff --git a/examples/read_write_file.rs b/examples/read_write_file.rs new file mode 100644 index 0000000..b68ac33 --- /dev/null +++ b/examples/read_write_file.rs @@ -0,0 +1,55 @@ +use std::fs; +use std::io; + +use integer_encoding::*; + +async fn write_test_files() -> io::Result<()> { + let _ = fs::remove_file("/tmp/varintbytes"); + let mut f = tokio::fs::File::create("/tmp/varintbytes").await?; + f.write_varint_async(30 as u32).await?; + f.write_varint_async(60 as u32).await?; + f.write_varint_async(90 as u32).await?; + f.write_varint_async(9000000 as u32).await?; + + let _ = fs::remove_file("/tmp/fixedintbytes"); + let mut f = tokio::fs::File::create("/tmp/fixedintbytes").await?; + f.write_fixedint_async(30 as u32).await?; + f.write_fixedint_async(60 as u32).await?; + f.write_fixedint_async(90 as u32).await?; + f.write_fixedint_async(9000000 as u32).await?; + Ok(()) +} + +async fn read_and_verify_varints() -> io::Result<()> { + let f = tokio::fs::File::open("/tmp/varintbytes").await?; + let mut f = tokio::io::BufReader::new(f); + let i1: u32 = f.read_varint_async().await?; + let i2: u32 = f.read_varint_async().await?; + let i3: u32 = f.read_varint_async().await?; + let i4: u32 = f.read_varint_async().await?; + assert!(f.read_varint_async::().await.is_err()); + println!("{:?}", (i1, i2, i3, i4)); + assert!(i2 == 2 * i1 && i3 == 3 * i1); + Ok(()) +} + +async fn read_and_verify_fixedints() -> io::Result<()> { + let f = tokio::fs::File::open("/tmp/fixedintbytes").await?; + let mut f = tokio::io::BufReader::new(f); + let i1: u32 = f.read_fixedint_async().await?; + let i2: u32 = f.read_fixedint_async().await?; + let i3: u32 = f.read_fixedint_async().await?; + let i4: u32 = f.read_fixedint_async().await?; + assert!(f.read_fixedint_async::().await.is_err()); + println!("{:?}", (i1, i2, i3, i4)); + assert!(i2 == 2 * i1 && i3 == 3 * i1); + Ok(()) +} + +#[tokio::main] +async fn main() { + write_test_files().await.unwrap(); + + read_and_verify_varints().await.unwrap(); + read_and_verify_fixedints().await.unwrap(); +} diff --git a/src/fixed.rs b/src/fixed.rs index aa23040..47fcdda 100644 --- a/src/fixed.rs +++ b/src/fixed.rs @@ -1,69 +1,79 @@ -use std::mem::transmute; +use std::convert::TryInto; +use std::mem::size_of; -/// FixedInt provides encoding/decoding to and from fixed int representations. +/// `FixedInt` provides encoding/decoding to and from fixed int representations. Note that current +/// Rust versions already provide this functionality via the `to_le_bytes()` and `to_be_bytes()` +/// methods. +/// /// The emitted bytestring contains the bytes of the integer in machine endianness. pub trait FixedInt: Sized + Copy { - const REQUIRED_SPACE: usize; - /// Returns how many bytes are required to represent the given type. - fn required_space() -> usize; - /// Encode a value into the given slice. `dst` must be exactly `REQUIRED_SPACE` bytes. - fn encode_fixed(self, &mut [u8]); - /// Decode a value from the given slice. `src` must be exactly `REQUIRED_SPACE` bytes. - fn decode_fixed(&[u8]) -> Self; - /// Perform a transmute, i.e. return a "view" into the integer's memory, which is faster than - /// performing a copy. - fn encode_fixed_light<'a>(&'a self) -> &'a [u8]; + type Bytes: AsRef<[u8]>; + const ENCODED_SIZE: usize = size_of::(); + + /// Encode a value into the given slice using little-endian. Returns `None` if `dst` + /// doesn't provide enough space to encode this integer. + /// + /// Use `switch_endianness()` if machine endianness doesn't match the desired target encoding. + fn encode_fixed(self, dst: &mut [u8]) -> Option<()>; + /// Returns the representation of [`FixedInt`] as [`Bytes`], the little-endian representation + /// of self in the stack. + fn encode_fixed_light(self) -> Self::Bytes; + + /// Decode a value from the given slice assuming little-endian. Use `switch_endianness()` on + /// the returned value if the source was not encoded in little-endian. + fn decode_fixed(src: &[u8]) -> Option; /// Helper: Encode the value and return a Vec. fn encode_fixed_vec(self) -> Vec { - let mut v = Vec::new(); - v.resize(Self::required_space(), 0); - self.encode_fixed(&mut v[..]); - v - } - /// Helper: Decode the value from the Vec. - fn decode_fixed_vec(v: &Vec) -> Self { - assert_eq!(v.len(), Self::required_space()); - Self::decode_fixed(&v[..]) + self.encode_fixed_light().as_ref().to_vec() } + + /// integer-encoding-rs always emits and receives little-endian integers (converting implicitly + /// on big-endian machines). If you receive a big-endian integer, and would like it to be + /// treated correctly, use this helper method to convert between endiannesses. + fn switch_endianness(self) -> Self; } macro_rules! impl_fixedint { - ($t:ty, $sz:expr) => { + ($t:ty) => { impl FixedInt for $t { - const REQUIRED_SPACE: usize = $sz; + type Bytes = [u8; size_of::<$t>()]; - fn required_space() -> usize { - Self::REQUIRED_SPACE + fn encode_fixed(self, dst: &mut [u8]) -> Option<()> { + if dst.len() == size_of::() { + dst.clone_from_slice(&self.to_le_bytes()); + Some(()) + } else { + None + } } - fn encode_fixed_light<'a>(&'a self) -> &'a [u8] { - return unsafe { - std::slice::from_raw_parts( - transmute::<&$t, *const u8>(&self), - Self::REQUIRED_SPACE, - ) - }; + fn encode_fixed_light(self) -> Self::Bytes { + self.to_le_bytes() } - fn encode_fixed(self, dst: &mut [u8]) { - assert_eq!(dst.len(), Self::REQUIRED_SPACE); - let encoded = unsafe { transmute::<&$t, &[u8; $sz]>(&self) }; - dst.clone_from_slice(encoded); + fn decode_fixed(src: &[u8]) -> Option { + if src.len() == size_of::() { + Some(Self::from_le_bytes(src.try_into().unwrap())) + } else { + None + } } - fn decode_fixed(src: &[u8]) -> $t { - assert_eq!(src.len(), Self::REQUIRED_SPACE); - return unsafe { *transmute::<*const u8, &$t>(src.as_ptr()) }; + + fn switch_endianness(self) -> Self { + Self::from_le_bytes(self.to_be_bytes()) } } }; } -impl_fixedint!(usize, 8); -impl_fixedint!(u64, 8); -impl_fixedint!(u32, 4); -impl_fixedint!(u16, 2); -impl_fixedint!(isize, 8); -impl_fixedint!(i64, 8); -impl_fixedint!(i32, 4); -impl_fixedint!(i16, 2); +impl_fixedint!(usize); +impl_fixedint!(u64); +impl_fixedint!(u32); +impl_fixedint!(u16); +impl_fixedint!(u8); +impl_fixedint!(isize); +impl_fixedint!(i64); +impl_fixedint!(i32); +impl_fixedint!(i16); +impl_fixedint!(i8); diff --git a/src/fixed_tests.rs b/src/fixed_tests.rs index 92ec7d0..4235cdd 100644 --- a/src/fixed_tests.rs +++ b/src/fixed_tests.rs @@ -1,9 +1,14 @@ #[cfg(test)] mod tests { - use fixed::FixedInt; + use crate::fixed::FixedInt; - use reader::FixedIntReader; - use writer::FixedIntWriter; + #[cfg(any(feature = "tokio_async", feature = "futures_async"))] + use crate::reader::FixedIntAsyncReader; + #[cfg(any(feature = "tokio_async", feature = "futures_async"))] + use crate::writer::FixedIntAsyncWriter; + + use crate::reader::FixedIntReader; + use crate::writer::FixedIntWriter; #[test] fn test_u32_enc() { @@ -16,6 +21,40 @@ mod tests { assert_eq!(result, vec![0, 1]); } #[test] + fn test_u16_endian() { + let le = 1 as u16; + let be = le.switch_endianness(); + assert_eq!(be, 256); + } + #[test] + fn test_u32_endian() { + let le = 1 as u32; + let be = le.switch_endianness(); + assert_eq!(be, 1 << 24); + assert_eq!(be.switch_endianness(), 1); + } + #[test] + fn test_allones_endian() { + assert_eq!(u64::MAX.switch_endianness(), u64::MAX); + } + #[test] + fn test_signed_endian() { + // x86: two's complement, LE. + let le = -2 as i16; + let be = le.switch_endianness(); + assert_eq!(be, -257); + } + #[test] + fn test_u8_enc() { + let result = (255 as u8).encode_fixed_vec(); + assert_eq!(result, vec![255]); + } + #[test] + fn test_i8_enc() { + let result = (-1 as i8).encode_fixed_vec(); + assert_eq!(result, vec![255]); + } + #[test] fn test_i16_enc() { let result = (-32768 as i16).encode_fixed_vec(); assert_eq!(result, vec![0, 128]); @@ -40,30 +79,27 @@ mod tests { fn test_i32_enc_light() { let int = -32767 as i32; let result = int.encode_fixed_light(); - assert_eq!(result, &[1, 128, 255, 255]); + assert_eq!(result, [1, 128, 255, 255]); } #[test] fn test_all_identity() { - let a: u16 = 17; - let b: u32 = 17; - let c: u64 = 17; - let d: i16 = -17; - let e: i32 = -17; - let f: i64 = -17; - - assert_eq!(a, FixedInt::decode_fixed_vec(&a.encode_fixed_vec())); - assert_eq!(b, FixedInt::decode_fixed_vec(&b.encode_fixed_vec())); - assert_eq!(c, FixedInt::decode_fixed_vec(&c.encode_fixed_vec())); - assert_eq!(d, FixedInt::decode_fixed_vec(&d.encode_fixed_vec())); - assert_eq!(e, FixedInt::decode_fixed_vec(&e.encode_fixed_vec())); - assert_eq!(f, FixedInt::decode_fixed_vec(&f.encode_fixed_vec())); - - assert_eq!(a, FixedInt::decode_fixed(&a.encode_fixed_light())); - assert_eq!(b, FixedInt::decode_fixed(&b.encode_fixed_light())); - assert_eq!(c, FixedInt::decode_fixed(&c.encode_fixed_light())); - assert_eq!(d, FixedInt::decode_fixed(&d.encode_fixed_light())); - assert_eq!(e, FixedInt::decode_fixed(&e.encode_fixed_light())); - assert_eq!(f, FixedInt::decode_fixed(&f.encode_fixed_light())); + let a: u8 = 17; + let b: u16 = 17; + let c: u32 = 17; + let d: u64 = 17; + let e: i8 = -17; + let f: i16 = -17; + let g: i32 = -17; + let h: i64 = -17; + + assert_eq!(a, FixedInt::decode_fixed(&a.encode_fixed_light()).unwrap()); + assert_eq!(b, FixedInt::decode_fixed(&b.encode_fixed_light()).unwrap()); + assert_eq!(c, FixedInt::decode_fixed(&c.encode_fixed_light()).unwrap()); + assert_eq!(d, FixedInt::decode_fixed(&d.encode_fixed_light()).unwrap()); + assert_eq!(e, FixedInt::decode_fixed(&e.encode_fixed_light()).unwrap()); + assert_eq!(f, FixedInt::decode_fixed(&f.encode_fixed_light()).unwrap()); + assert_eq!(g, FixedInt::decode_fixed(&g.encode_fixed_light()).unwrap()); + assert_eq!(h, FixedInt::decode_fixed(&h.encode_fixed_light()).unwrap()); } #[test] @@ -96,12 +132,45 @@ mod tests { #[should_panic] #[test] fn test_invalid_decode_size() { - assert_eq!(33, u64::decode_fixed(&[1, 0, 0, 0, 0, 1])); + assert_eq!(33, u64::decode_fixed(&[1, 0, 0, 0, 0, 1]).unwrap()); } #[should_panic] #[test] fn test_invalid_encode_size() { let mut buf = [0 as u8; 4]; - (11 as u64).encode_fixed(&mut buf); + (11 as u64).encode_fixed(&mut buf).unwrap(); + } + + #[cfg(any(feature = "tokio_async", feature = "futures_async"))] + #[tokio::test] + async fn test_async_reader() { + let mut buf = Vec::with_capacity(128); + + let i1: u32 = 1; + let i2: u32 = 65532; + let i3: u32 = 4200123456; + let i4: i64 = i3 as i64 * 1000; + let i5: i32 = -32456; + let i6: i8 = -128; + let i7: u8 = 255; + + buf.write_fixedint_async(i1).await.unwrap(); + buf.write_fixedint_async(i2).await.unwrap(); + buf.write_fixedint_async(i3).await.unwrap(); + buf.write_fixedint_async(i4).await.unwrap(); + buf.write_fixedint_async(i5).await.unwrap(); + buf.write_fixedint_async(i6).await.unwrap(); + buf.write_fixedint_async(i7).await.unwrap(); + + let mut reader: &[u8] = buf.as_ref(); + + assert_eq!(i1, reader.read_fixedint_async().await.unwrap()); + assert_eq!(i2, reader.read_fixedint_async().await.unwrap()); + assert_eq!(i3, reader.read_fixedint_async().await.unwrap()); + assert_eq!(i4, reader.read_fixedint_async().await.unwrap()); + assert_eq!(i5, reader.read_fixedint_async().await.unwrap()); + assert_eq!(i6, reader.read_fixedint_async().await.unwrap()); + assert_eq!(i7, reader.read_fixedint_async().await.unwrap()); + assert!(reader.read_fixedint_async::().await.is_err()); } } diff --git a/src/lib.rs b/src/lib.rs index 561ba25..9b2f456 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,20 +1,28 @@ //! Fast serialization of integers. //! +//! This crate implements encoding and decoding of integer types to and from `FixedInt` (i.e. a +//! representation of integers similar or equal to how they are stored in memory) as well as +//! `VarInt` (encoding integers so that they only use as much memory as needed to represent their +//! magnitude). +//! +//! This is useful when (de)serializing data from and to binary representations. For example, +//! Protocol Buffers (by Google) use these kinds of encoding. +//! //! ``` //! use integer_encoding::*; //! //! fn main() { //! let a: u32 = 344; //! let encoded_byte_slice = a.encode_fixed_light(); -//! assert_eq!(a, u32::decode_fixed(encoded_byte_slice)); +//! assert_eq!(Some(a), u32::decode_fixed(&encoded_byte_slice)); //! assert_eq!(4, encoded_byte_slice.len()); //! //! let b: i32 = -111; //! let encoded_byte_vec = b.encode_var_vec(); -//! assert_eq!((b, 2), i32::decode_var(&encoded_byte_vec)); +//! assert_eq!(Some((b, 2)), i32::decode_var(&encoded_byte_vec)); //! } //! ``` - +#[forbid(unsafe_code)] mod fixed; mod fixed_tests; @@ -27,7 +35,16 @@ mod writer; pub use fixed::FixedInt; pub use varint::VarInt; +#[cfg(any(feature = "tokio_async", feature = "futures_async"))] +pub use reader::FixedIntAsyncReader; pub use reader::FixedIntReader; +#[cfg(any(feature = "tokio_async", feature = "futures_async"))] +pub use reader::VarIntAsyncReader; pub use reader::VarIntReader; + +#[cfg(any(feature = "tokio_async", feature = "futures_async"))] +pub use writer::FixedIntAsyncWriter; pub use writer::FixedIntWriter; +#[cfg(any(feature = "tokio_async", feature = "futures_async"))] +pub use writer::VarIntAsyncWriter; pub use writer::VarIntWriter; diff --git a/src/reader.rs b/src/reader.rs index b0bfaf6..0ed63ac 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -1,8 +1,14 @@ use std::io; use std::io::{Read, Result}; -use fixed::FixedInt; -use varint::{VarInt, MSB}; +use crate::fixed::FixedInt; +use crate::varint::{VarInt, VarIntMaxSize, MSB}; + +#[cfg(feature = "tokio_async")] +use tokio::io::{AsyncRead, AsyncReadExt}; + +#[cfg(feature = "futures_async")] +use futures_util::{io::AsyncRead, io::AsyncReadExt}; /// A trait for reading VarInts from any other `Reader`. /// @@ -17,37 +23,94 @@ pub trait VarIntReader { fn read_varint(&mut self) -> Result; } +#[cfg(any(feature = "tokio_async", feature = "futures_async"))] +/// Like a VarIntReader, but returns a future. +#[async_trait::async_trait(?Send)] +pub trait VarIntAsyncReader { + async fn read_varint_async(&mut self) -> Result; +} + +/// VarIntProcessor encapsulates the logic for decoding a VarInt byte-by-byte. +#[derive(Default)] +pub struct VarIntProcessor { + buf: [u8; 10], + maxsize: usize, + i: usize, +} + +impl VarIntProcessor { + fn new() -> VarIntProcessor { + VarIntProcessor { + maxsize: VI::varint_max_size(), + ..VarIntProcessor::default() + } + } + fn push(&mut self, b: u8) -> Result<()> { + if self.i >= self.maxsize { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "Unterminated varint", + )); + } + self.buf[self.i] = b; + self.i += 1; + Ok(()) + } + fn finished(&self) -> bool { + self.i > 0 && (self.buf[self.i - 1] & MSB == 0) + } + fn decode(&self) -> Option { + Some(VI::decode_var(&self.buf[0..self.i])?.0) + } +} + +#[cfg(any(feature = "tokio_async", feature = "futures_async"))] +#[async_trait::async_trait(?Send)] +impl VarIntAsyncReader for AR { + async fn read_varint_async(&mut self) -> Result { + let mut buf = [0_u8; 1]; + let mut p = VarIntProcessor::new::(); + + while !p.finished() { + let read = self.read(&mut buf).await?; + + // EOF + if read == 0 && p.i == 0 { + return Err(io::Error::new(io::ErrorKind::UnexpectedEof, "Reached EOF")); + } + if read == 0 { + break; + } + + p.push(buf[0])?; + } + + p.decode() + .ok_or_else(|| io::Error::new(io::ErrorKind::UnexpectedEof, "Reached EOF")) + } +} + impl VarIntReader for R { fn read_varint(&mut self) -> Result { - const BUFLEN: usize = 10; - let mut buf = [0 as u8; BUFLEN]; - let mut i = 0; - - loop { - if i >= BUFLEN { - return Err(io::Error::new( - io::ErrorKind::InvalidData, - "Unterminated varint", - )); - } + let mut buf = [0_u8; 1]; + let mut p = VarIntProcessor::new::(); - let read = try!(self.read(&mut buf[i..i + 1])); + while !p.finished() { + let read = self.read(&mut buf)?; // EOF - if read == 0 && i == 0 { + if read == 0 && p.i == 0 { return Err(io::Error::new(io::ErrorKind::UnexpectedEof, "Reached EOF")); } - - if buf[i] & MSB == 0 { + if read == 0 { break; } - i += 1; + p.push(buf[0])?; } - let (result, _) = VI::decode_var(&buf[0..i + 1]); - - Ok(result) + p.decode() + .ok_or_else(|| io::Error::new(io::ErrorKind::UnexpectedEof, "Reached EOF")) } } @@ -59,16 +122,28 @@ pub trait FixedIntReader { fn read_fixedint(&mut self) -> Result; } -impl FixedIntReader for R { - fn read_fixedint(&mut self) -> Result { - let mut buf = [0 as u8; 8]; - - let read = try!(self.read(&mut buf[0..FI::required_space()])); +/// Like FixedIntReader, but returns a future. +#[cfg(any(feature = "tokio_async", feature = "futures_async"))] +#[async_trait::async_trait(?Send)] +pub trait FixedIntAsyncReader { + async fn read_fixedint_async(&mut self) -> Result; +} - if read == 0 { - return Err(io::Error::new(io::ErrorKind::UnexpectedEof, "Reached EOF")); - } +#[cfg(any(feature = "tokio_async", feature = "futures_async"))] +#[async_trait::async_trait(?Send)] +impl FixedIntAsyncReader for AR { + async fn read_fixedint_async(&mut self) -> Result { + let mut buf = [0_u8; 8]; + self.read_exact(&mut buf[0..std::mem::size_of::()]) + .await?; + Ok(FI::decode_fixed(&buf[0..std::mem::size_of::()]).unwrap()) + } +} - Ok(FI::decode_fixed(&buf[0..read])) +impl FixedIntReader for R { + fn read_fixedint(&mut self) -> Result { + let mut buf = [0_u8; 8]; + self.read_exact(&mut buf[0..std::mem::size_of::()])?; + Ok(FI::decode_fixed(&buf[0..std::mem::size_of::()]).unwrap()) } } diff --git a/src/varint.rs b/src/varint.rs index 9df391b..61032d2 100644 --- a/src/varint.rs +++ b/src/varint.rs @@ -1,7 +1,12 @@ -pub const MSB: u8 = 0b10000000; -const DROP_MSB: u8 = 0b01111111; -const EXTRACT_SEVEN: u8 = DROP_MSB; +use std::mem::size_of; +/// Most-significant byte, == 0x80 +pub const MSB: u8 = 0b1000_0000; +/// All bits except for the most significant. Can be used as bitmask to drop the most-signficant +/// bit using `&` (binary-and). +const DROP_MSB: u8 = 0b0111_1111; + +/// How many bytes an integer uses when being encoded as a VarInt. #[inline] fn required_encoded_space_unsigned(mut v: u64) -> usize { if v == 0 { @@ -16,6 +21,7 @@ fn required_encoded_space_unsigned(mut v: u64) -> usize { logcounter } +/// How many bytes an integer uses when being encoded as a VarInt. #[inline] fn required_encoded_space_signed(v: i64) -> usize { required_encoded_space_unsigned(zigzag_encode(v)) @@ -23,6 +29,7 @@ fn required_encoded_space_signed(v: i64) -> usize { /// Varint (variable length integer) encoding, as described in /// https://developers.google.com/protocol-buffers/docs/encoding. +/// /// Uses zigzag encoding (also described there) for signed integer representation. pub trait VarInt: Sized + Copy { /// Returns the number of bytes this number needs in its encoded form. Note: This varies @@ -30,20 +37,16 @@ pub trait VarInt: Sized + Copy { fn required_space(self) -> usize; /// Decode a value from the slice. Returns the value and the number of bytes read from the /// slice (can be used to read several consecutive values from a big slice) - fn decode_var(&[u8]) -> (Self, usize); + /// return None if all bytes has MSB set. + fn decode_var(src: &[u8]) -> Option<(Self, usize)>; /// Encode a value into the slice. The slice must be at least `required_space()` bytes long. /// The number of bytes taken by the encoded integer is returned. - fn encode_var(self, &mut [u8]) -> usize; + fn encode_var(self, src: &mut [u8]) -> usize; - /// Helper: (bit useless) - Decode value from the Vec. - fn decode_var_vec(v: &Vec) -> (Self, usize) { - Self::decode_var(&v) - } /// Helper: Encode a value and return the encoded form as Vec. The Vec must be at least /// `required_space()` bytes long. fn encode_var_vec(self) -> Vec { - let mut v = Vec::new(); - v.resize(self.required_space(), 0); + let mut v = vec![0; self.required_space()]; self.encode_var(&mut v); v } @@ -62,6 +65,16 @@ fn zigzag_decode(from: u64) -> i64 { ((from >> 1) ^ (-((from & 1) as i64)) as u64) as i64 } +pub(crate) trait VarIntMaxSize { + fn varint_max_size() -> usize; +} + +impl VarIntMaxSize for VI { + fn varint_max_size() -> usize { + (size_of::() * 8 + 7) / 7 + } +} + macro_rules! impl_varint { ($t:ty, unsigned) => { impl VarInt for $t { @@ -69,9 +82,9 @@ macro_rules! impl_varint { required_encoded_space_unsigned(self as u64) } - fn decode_var(src: &[u8]) -> (Self, usize) { - let (n, s) = u64::decode_var(src); - (n as Self, s) + fn decode_var(src: &[u8]) -> Option<(Self, usize)> { + let (n, s) = u64::decode_var(src)?; + Some((n as Self, s)) } fn encode_var(self, dst: &mut [u8]) -> usize { @@ -85,9 +98,9 @@ macro_rules! impl_varint { required_encoded_space_signed(self as i64) } - fn decode_var(src: &[u8]) -> (Self, usize) { - let (n, s) = i64::decode_var(src); - (n as Self, s) + fn decode_var(src: &[u8]) -> Option<(Self, usize)> { + let (n, s) = i64::decode_var(src)?; + Some((n as Self, s)) } fn encode_var(self, dst: &mut [u8]) -> usize { @@ -115,40 +128,44 @@ impl VarInt for u64 { required_encoded_space_unsigned(self) } - fn decode_var(src: &[u8]) -> (Self, usize) { + #[inline] + fn decode_var(src: &[u8]) -> Option<(Self, usize)> { let mut result: u64 = 0; let mut shift = 0; + let mut success = false; for b in src.iter() { let msb_dropped = b & DROP_MSB; result |= (msb_dropped as u64) << shift; shift += 7; - if b & MSB == 0 || shift > (10 * 7) { + if b & MSB == 0 || shift > (9 * 7) { + success = b & MSB == 0; break; } } - (result, shift / 7 as usize) + if success { + Some((result, shift / 7)) + } else { + None + } } + + #[inline] fn encode_var(self, dst: &mut [u8]) -> usize { - assert!(dst.len() >= self.required_space()); + debug_assert!(dst.len() >= self.required_space()); let mut n = self; let mut i = 0; - if n > 0 { - while n > 0 { - dst[i] = MSB | (n as u8 & EXTRACT_SEVEN) as u8; - i += 1; - n >>= 7; - } - - dst[i - 1] = DROP_MSB & dst[i - 1]; - i - } else { - dst[0] = 0; - 1 + while n >= 0x80 { + dst[i] = MSB | (n as u8); + i += 1; + n >>= 7; } + + dst[i] = n as u8; + i + 1 } } @@ -157,40 +174,28 @@ impl VarInt for i64 { required_encoded_space_signed(self) } - fn decode_var(src: &[u8]) -> (Self, usize) { - let mut result: u64 = 0; - let mut shift = 0; - - for b in src.iter() { - let msb_dropped = b & DROP_MSB; - result |= (msb_dropped as u64) << shift; - shift += 7; - - if b & MSB == 0 || shift > (10 * 7) { - break; - } + #[inline] + fn decode_var(src: &[u8]) -> Option<(Self, usize)> { + if let Some((result, size)) = u64::decode_var(src) { + Some((zigzag_decode(result) as Self, size)) + } else { + None } - - (zigzag_decode(result) as Self, shift / 7 as usize) } + #[inline] fn encode_var(self, dst: &mut [u8]) -> usize { - assert!(dst.len() >= self.required_space()); - let mut n: u64 = zigzag_encode(self as i64); + debug_assert!(dst.len() >= self.required_space()); + let mut n: u64 = zigzag_encode(self); let mut i = 0; - if n > 0 { - while n > 0 { - dst[i] = MSB | (n as u8 & EXTRACT_SEVEN) as u8; - i += 1; - n >>= 7; - } - - dst[i - 1] = DROP_MSB & dst[i - 1]; - i - } else { - dst[0] = 0; - 1 + while n >= 0x80 { + dst[i] = MSB | (n as u8); + i += 1; + n >>= 7; } + + dst[i] = n as u8; + i + 1 } } diff --git a/src/varint_tests.rs b/src/varint_tests.rs index ce0f696..7a543a7 100644 --- a/src/varint_tests.rs +++ b/src/varint_tests.rs @@ -1,8 +1,13 @@ #[cfg(test)] mod tests { - use reader::VarIntReader; - use varint::VarInt; - use writer::VarIntWriter; + #[cfg(any(feature = "tokio_async", feature = "futures_async"))] + use crate::reader::VarIntAsyncReader; + #[cfg(any(feature = "tokio_async", feature = "futures_async"))] + use crate::writer::VarIntAsyncWriter; + + use crate::reader::VarIntReader; + use crate::varint::VarInt; + use crate::writer::VarIntWriter; #[test] fn test_required_space() { @@ -23,17 +28,26 @@ mod tests { #[test] fn test_identity_u64() { for i in 1 as u64..100 { - assert_eq!(u64::decode_var_vec(&i.encode_var_vec()), (i, 1)); + assert_eq!( + u64::decode_var(i.encode_var_vec().as_slice()).unwrap(), + (i, 1) + ); } for i in 16400 as u64..16500 { - assert_eq!(u64::decode_var_vec(&i.encode_var_vec()), (i, 3)); + assert_eq!( + u64::decode_var(i.encode_var_vec().as_slice()).unwrap(), + (i, 3) + ); } } #[test] fn test_decode_max_u64() { let max_vec_encoded = vec![0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01]; - assert_eq!(u64::decode_var_vec(&max_vec_encoded).0, u64::max_value()); + assert_eq!( + u64::decode_var(max_vec_encoded.as_slice()).unwrap().0, + u64::max_value() + ); } #[test] @@ -61,13 +75,19 @@ mod tests { #[test] fn test_decode_min_i64() { let min_vec_encoded = vec![0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01]; - assert_eq!(i64::decode_var_vec(&min_vec_encoded).0, i64::min_value()); + assert_eq!( + i64::decode_var(min_vec_encoded.as_slice()).unwrap().0, + i64::min_value() + ); } #[test] fn test_decode_max_i64() { let max_vec_encoded = vec![0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01]; - assert_eq!(i64::decode_var_vec(&max_vec_encoded).0, i64::max_value()); + assert_eq!( + i64::decode_var(max_vec_encoded.as_slice()).unwrap().0, + i64::max_value() + ); } #[test] @@ -105,4 +125,88 @@ mod tests { assert!(reader.read_varint::().is_err()); } + + #[cfg(any(feature = "tokio_async", feature = "futures_async"))] + #[tokio::test] + async fn test_async_reader() { + let mut buf = Vec::with_capacity(128); + + let i1: u32 = 1; + let i2: u32 = 65532; + let i3: u32 = 4200123456; + let i4: i64 = i3 as i64 * 1000; + let i5: i32 = -32456; + + buf.write_varint_async(i1).await.unwrap(); + buf.write_varint_async(i2).await.unwrap(); + buf.write_varint_async(i3).await.unwrap(); + buf.write_varint_async(i4).await.unwrap(); + buf.write_varint_async(i5).await.unwrap(); + + let mut reader: &[u8] = buf.as_ref(); + + assert_eq!(i1, reader.read_varint_async().await.unwrap()); + assert_eq!(i2, reader.read_varint_async().await.unwrap()); + assert_eq!(i3, reader.read_varint_async().await.unwrap()); + assert_eq!(i4, reader.read_varint_async().await.unwrap()); + assert_eq!(i5, reader.read_varint_async().await.unwrap()); + assert!(reader.read_varint_async::().await.is_err()); + } + + #[test] + fn test_unterminated_varint() { + let buf = vec![0xff as u8; 12]; + let mut read = buf.as_slice(); + assert!(read.read_varint::().is_err()); + } + + #[test] + fn test_unterminated_varint_2() { + let buf = [0xff, 0xff]; + let mut read = &buf[..]; + assert!(read.read_varint::().is_err()); + } + + #[test] + fn test_decode_extra_bytes_u64() { + let mut encoded = 0x12345u64.encode_var_vec(); + assert_eq!(u64::decode_var(&encoded[..]), Some((0x12345, 3))); + + encoded.push(0x99); + assert_eq!(u64::decode_var(&encoded[..]), Some((0x12345, 3))); + + let encoded = [0xFF, 0xFF, 0xFF]; + assert_eq!(u64::decode_var(&encoded[..]), None); + + // Overflow + let mut encoded = vec![0xFF; 64]; + encoded.push(0x00); + assert_eq!(u64::decode_var(&encoded[..]), None); + } + + #[test] + fn test_decode_extra_bytes_i64() { + let mut encoded = (-0x12345i64).encode_var_vec(); + assert_eq!(i64::decode_var(&encoded[..]), Some((-0x12345, 3))); + + encoded.push(0x99); + assert_eq!(i64::decode_var(&encoded[..]), Some((-0x12345, 3))); + + let encoded = [0xFF, 0xFF, 0xFF]; + assert_eq!(i64::decode_var(&encoded[..]), None); + + // Overflow + let mut encoded = vec![0xFF; 64]; + encoded.push(0x00); + assert_eq!(i64::decode_var(&encoded[..]), None); + } + + #[test] + fn test_regression_22() { + let encoded: Vec = (0x112233 as u64).encode_var_vec(); + assert_eq!( + encoded.as_slice().read_varint::().unwrap_err().kind(), + std::io::ErrorKind::InvalidData + ); + } } diff --git a/src/writer.rs b/src/writer.rs index 7657efd..3671980 100644 --- a/src/writer.rs +++ b/src/writer.rs @@ -1,19 +1,46 @@ use std::io::{Result, Write}; -use fixed::FixedInt; -use varint::VarInt; +use crate::fixed::FixedInt; +use crate::varint::VarInt; -/// A trait for writing integers in VarInt encoding to any `Write` type. +#[cfg(feature = "tokio_async")] +use tokio::io::{AsyncWrite, AsyncWriteExt}; + +#[cfg(feature = "futures_async")] +use futures_util::{io::AsyncWrite, io::AsyncWriteExt}; + +/// A trait for writing integers in VarInt encoding to any `Write` type. This packs encoding and +/// writing into one step. pub trait VarIntWriter { fn write_varint(&mut self, n: VI) -> Result; } +/// Like VarIntWriter, but asynchronous. +#[cfg(any(feature = "tokio_async", feature = "futures_async"))] +#[async_trait::async_trait(?Send)] +pub trait VarIntAsyncWriter { + /// Write a VarInt integer to an asynchronous writer. + async fn write_varint_async(&mut self, n: VI) -> Result; +} + +#[cfg(any(feature = "tokio_async", feature = "futures_async"))] +#[async_trait::async_trait(?Send)] +impl VarIntAsyncWriter for AW { + async fn write_varint_async(&mut self, n: VI) -> Result { + let mut buf = [0_u8; 10]; + let b = n.encode_var(&mut buf); + self.write_all(&buf[0..b]).await?; + Ok(b) + } +} + impl VarIntWriter for Inner { fn write_varint(&mut self, n: VI) -> Result { - let mut buf = [0 as u8; 10]; + let mut buf = [0_u8; 10]; let used = n.encode_var(&mut buf[..]); - self.write(&buf[0..used]) + self.write_all(&buf[0..used])?; + Ok(used) } } @@ -22,11 +49,29 @@ pub trait FixedIntWriter { fn write_fixedint(&mut self, n: FI) -> Result; } +#[cfg(any(feature = "tokio_async", feature = "futures_async"))] +#[async_trait::async_trait(?Send)] +pub trait FixedIntAsyncWriter { + async fn write_fixedint_async(&mut self, n: FI) -> Result; +} + +#[cfg(any(feature = "tokio_async", feature = "futures_async"))] +#[async_trait::async_trait(?Send)] +impl FixedIntAsyncWriter for AW { + async fn write_fixedint_async(&mut self, n: FI) -> Result { + let mut buf = [0_u8; 8]; + n.encode_fixed(&mut buf[..std::mem::size_of::()]); + self.write_all(&buf[..std::mem::size_of::()]).await?; + Ok(std::mem::size_of::()) + } +} + impl FixedIntWriter for W { fn write_fixedint(&mut self, n: FI) -> Result { - let mut buf = [0 as u8; 8]; - n.encode_fixed(&mut buf[0..FI::required_space()]); + let mut buf = [0_u8; 8]; + n.encode_fixed(&mut buf[..std::mem::size_of::()]); - self.write(&buf[0..FI::required_space()]) + self.write_all(&buf[..std::mem::size_of::()])?; + Ok(std::mem::size_of::()) } }