chain: split serialization.rs into files
The code is unchanged except for removing an unneeded clippy annotation in proptests.rs.
This commit is contained in:
parent
0d1f56ad2f
commit
9f31e551c9
|
@ -6,370 +6,17 @@
|
|||
//! `ReadZcashExt`, extension traits for `io::Read` and `io::Write` with utility functions
|
||||
//! for reading and writing data (e.g., the Bitcoin variable-integer format).
|
||||
|
||||
use std::io;
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
mod error;
|
||||
mod read_zcash;
|
||||
mod write_zcash;
|
||||
mod zcash_deserialize;
|
||||
mod zcash_serialize;
|
||||
|
||||
use byteorder::{BigEndian, LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||
use thiserror::Error;
|
||||
|
||||
/// A serialization error.
|
||||
// XXX refine error types -- better to use boxed errors?
|
||||
#[derive(Error, Debug)]
|
||||
pub enum SerializationError {
|
||||
/// An io error that prevented deserialization
|
||||
#[error("unable to deserialize type")]
|
||||
Io(#[from] io::Error),
|
||||
/// The data to be deserialized was malformed.
|
||||
// XXX refine errors
|
||||
#[error("parse error: {0}")]
|
||||
Parse(&'static str),
|
||||
/// An error caused when validating a zatoshi `Amount`
|
||||
#[error("input couldn't be parsed as a zatoshi `Amount`")]
|
||||
Amount {
|
||||
/// The source error indicating how the num failed to validate
|
||||
#[from]
|
||||
source: crate::types::amount::Error,
|
||||
},
|
||||
}
|
||||
|
||||
/// Consensus-critical serialization for Zcash.
|
||||
///
|
||||
/// This trait provides a generic serialization for consensus-critical
|
||||
/// formats, such as network messages, transactions, blocks, etc. It is intended
|
||||
/// for use only in consensus-critical contexts; in other contexts, such as
|
||||
/// internal storage, it would be preferable to use Serde.
|
||||
pub trait ZcashSerialize: Sized {
|
||||
/// Write `self` to the given `writer` using the canonical format.
|
||||
///
|
||||
/// This function has a `zcash_` prefix to alert the reader that the
|
||||
/// serialization in use is consensus-critical serialization, rather than
|
||||
/// some other kind of serialization.
|
||||
///
|
||||
/// Notice that the error type is [`std::io::Error`]; this indicates that
|
||||
/// serialization MUST be infallible up to errors in the underlying writer.
|
||||
/// In other words, any type implementing `ZcashSerialize` must make illegal
|
||||
/// states unrepresentable.
|
||||
fn zcash_serialize<W: io::Write>(&self, writer: W) -> Result<(), io::Error>;
|
||||
|
||||
/// Helper function to construct a vec to serialize the current struct into
|
||||
fn zcash_serialize_to_vec(&self) -> Result<Vec<u8>, io::Error> {
|
||||
let mut data = Vec::new();
|
||||
self.zcash_serialize(&mut data)?;
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
|
||||
/// Consensus-critical serialization for Zcash.
|
||||
///
|
||||
/// This trait provides a generic deserialization for consensus-critical
|
||||
/// formats, such as network messages, transactions, blocks, etc. It is intended
|
||||
/// for use only in consensus-critical contexts; in other contexts, such as
|
||||
/// internal storage, it would be preferable to use Serde.
|
||||
pub trait ZcashDeserialize: Sized {
|
||||
/// Try to read `self` from the given `reader`.
|
||||
///
|
||||
/// This function has a `zcash_` prefix to alert the reader that the
|
||||
/// serialization in use is consensus-critical serialization, rather than
|
||||
/// some other kind of serialization.
|
||||
fn zcash_deserialize<R: io::Read>(reader: R) -> Result<Self, SerializationError>;
|
||||
}
|
||||
|
||||
impl<T: ZcashSerialize> ZcashSerialize for Vec<T> {
|
||||
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
|
||||
writer.write_compactsize(self.len() as u64)?;
|
||||
for x in self {
|
||||
x.zcash_serialize(&mut writer)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ZcashDeserialize> ZcashDeserialize for Vec<T> {
|
||||
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
|
||||
let len = reader.read_compactsize()?;
|
||||
// We're given len, so we could preallocate. But blindly preallocating
|
||||
// without a size bound can allow DOS attacks, and there's no way to
|
||||
// pass a size bound in a ZcashDeserialize impl, so instead we allocate
|
||||
// as we read from the reader. (The maximum block and transaction sizes
|
||||
// limit the eventual size of these allocations.)
|
||||
let mut vec = Vec::new();
|
||||
for _ in 0..len {
|
||||
vec.push(T::zcash_deserialize(&mut reader)?);
|
||||
}
|
||||
Ok(vec)
|
||||
}
|
||||
}
|
||||
|
||||
/// Extends [`Write`] with methods for writing Zcash/Bitcoin types.
|
||||
///
|
||||
/// [`Write`]: https://doc.rust-lang.org/std/io/trait.Write.html
|
||||
pub trait WriteZcashExt: io::Write {
|
||||
/// Writes a `u64` using the Bitcoin `CompactSize` encoding.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use zebra_chain::serialization::WriteZcashExt;
|
||||
///
|
||||
/// let mut buf = Vec::new();
|
||||
/// buf.write_compactsize(0x12).unwrap();
|
||||
/// assert_eq!(buf, b"\x12");
|
||||
///
|
||||
/// let mut buf = Vec::new();
|
||||
/// buf.write_compactsize(0xfd).unwrap();
|
||||
/// assert_eq!(buf, b"\xfd\xfd\x00");
|
||||
///
|
||||
/// let mut buf = Vec::new();
|
||||
/// buf.write_compactsize(0xaafd).unwrap();
|
||||
/// assert_eq!(buf, b"\xfd\xfd\xaa");
|
||||
///
|
||||
/// let mut buf = Vec::new();
|
||||
/// buf.write_compactsize(0xbbaafd).unwrap();
|
||||
/// assert_eq!(buf, b"\xfe\xfd\xaa\xbb\x00");
|
||||
///
|
||||
/// let mut buf = Vec::new();
|
||||
/// buf.write_compactsize(0x22ccbbaafd).unwrap();
|
||||
/// assert_eq!(buf, b"\xff\xfd\xaa\xbb\xcc\x22\x00\x00\x00");
|
||||
/// ```
|
||||
#[inline]
|
||||
fn write_compactsize(&mut self, n: u64) -> io::Result<()> {
|
||||
match n {
|
||||
0x0000_0000..=0x0000_00fc => self.write_u8(n as u8),
|
||||
0x0000_00fd..=0x0000_ffff => {
|
||||
self.write_u8(0xfd)?;
|
||||
self.write_u16::<LittleEndian>(n as u16)
|
||||
}
|
||||
0x0001_0000..=0xffff_ffff => {
|
||||
self.write_u8(0xfe)?;
|
||||
self.write_u32::<LittleEndian>(n as u32)
|
||||
}
|
||||
_ => {
|
||||
self.write_u8(0xff)?;
|
||||
self.write_u64::<LittleEndian>(n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Write an `IpAddr` in Bitcoin format.
|
||||
#[inline]
|
||||
fn write_ip_addr(&mut self, addr: IpAddr) -> io::Result<()> {
|
||||
use std::net::IpAddr::*;
|
||||
let v6_addr = match addr {
|
||||
V4(ref v4) => v4.to_ipv6_mapped(),
|
||||
V6(v6) => v6,
|
||||
};
|
||||
self.write_all(&v6_addr.octets())
|
||||
}
|
||||
|
||||
/// Write a `SocketAddr` in Bitcoin format.
|
||||
#[inline]
|
||||
fn write_socket_addr(&mut self, addr: SocketAddr) -> io::Result<()> {
|
||||
self.write_ip_addr(addr.ip())?;
|
||||
self.write_u16::<BigEndian>(addr.port())
|
||||
}
|
||||
|
||||
/// Write a string in Bitcoin format.
|
||||
#[inline]
|
||||
fn write_string(&mut self, string: &str) -> io::Result<()> {
|
||||
self.write_compactsize(string.len() as u64)?;
|
||||
self.write_all(string.as_bytes())
|
||||
}
|
||||
|
||||
/// Convenience method to write exactly 32 u8's.
|
||||
#[inline]
|
||||
fn write_32_bytes(&mut self, bytes: &[u8; 32]) -> io::Result<()> {
|
||||
self.write_all(bytes)
|
||||
}
|
||||
|
||||
/// Convenience method to write exactly 64 u8's.
|
||||
#[inline]
|
||||
fn write_64_bytes(&mut self, bytes: &[u8; 64]) -> io::Result<()> {
|
||||
self.write_all(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark all types implementing `Write` as implementing the extension.
|
||||
impl<W: io::Write + ?Sized> WriteZcashExt for W {}
|
||||
|
||||
/// Extends [`Read`] with methods for writing Zcash/Bitcoin types.
|
||||
///
|
||||
/// [`Read`]: https://doc.rust-lang.org/std/io/trait.Read.html
|
||||
pub trait ReadZcashExt: io::Read {
|
||||
/// Reads a `u64` using the Bitcoin `CompactSize` encoding.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use zebra_chain::serialization::ReadZcashExt;
|
||||
///
|
||||
/// use std::io::Cursor;
|
||||
/// assert_eq!(
|
||||
/// 0x12,
|
||||
/// Cursor::new(b"\x12")
|
||||
/// .read_compactsize().unwrap()
|
||||
/// );
|
||||
/// assert_eq!(
|
||||
/// 0xfd,
|
||||
/// Cursor::new(b"\xfd\xfd\x00")
|
||||
/// .read_compactsize().unwrap()
|
||||
/// );
|
||||
/// assert_eq!(
|
||||
/// 0xaafd,
|
||||
/// Cursor::new(b"\xfd\xfd\xaa")
|
||||
/// .read_compactsize().unwrap()
|
||||
/// );
|
||||
/// assert_eq!(
|
||||
/// 0xbbaafd,
|
||||
/// Cursor::new(b"\xfe\xfd\xaa\xbb\x00")
|
||||
/// .read_compactsize().unwrap()
|
||||
/// );
|
||||
/// assert_eq!(
|
||||
/// 0x22ccbbaafd,
|
||||
/// Cursor::new(b"\xff\xfd\xaa\xbb\xcc\x22\x00\x00\x00")
|
||||
/// .read_compactsize().unwrap()
|
||||
/// );
|
||||
/// ```
|
||||
#[inline]
|
||||
fn read_compactsize(&mut self) -> Result<u64, SerializationError> {
|
||||
use SerializationError::Parse;
|
||||
let flag_byte = self.read_u8()?;
|
||||
match flag_byte {
|
||||
n @ 0x00..=0xfc => Ok(n as u64),
|
||||
0xfd => match self.read_u16::<LittleEndian>()? {
|
||||
n @ 0x0000_00fd..=0x0000_ffff => Ok(n as u64),
|
||||
_ => Err(Parse("non-canonical compactsize")),
|
||||
},
|
||||
0xfe => match self.read_u32::<LittleEndian>()? {
|
||||
n @ 0x0001_0000..=0xffff_ffff => Ok(n as u64),
|
||||
_ => Err(Parse("non-canonical compactsize")),
|
||||
},
|
||||
0xff => match self.read_u64::<LittleEndian>()? {
|
||||
n @ 0x1_0000_0000..=0xffff_ffff_ffff_ffff => Ok(n),
|
||||
_ => Err(Parse("non-canonical compactsize")),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Read an IP address in Bitcoin format.
|
||||
#[inline]
|
||||
fn read_ip_addr(&mut self) -> io::Result<IpAddr> {
|
||||
use std::net::{IpAddr::*, Ipv6Addr};
|
||||
|
||||
let mut octets = [0u8; 16];
|
||||
self.read_exact(&mut octets)?;
|
||||
let v6_addr = Ipv6Addr::from(octets);
|
||||
|
||||
match v6_addr.to_ipv4() {
|
||||
Some(v4_addr) => Ok(V4(v4_addr)),
|
||||
None => Ok(V6(v6_addr)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Read a Bitcoin-encoded `SocketAddr`.
|
||||
#[inline]
|
||||
fn read_socket_addr(&mut self) -> io::Result<SocketAddr> {
|
||||
let ip_addr = self.read_ip_addr()?;
|
||||
let port = self.read_u16::<BigEndian>()?;
|
||||
Ok(SocketAddr::new(ip_addr, port))
|
||||
}
|
||||
|
||||
/// Read a Bitcoin-encoded UTF-8 string.
|
||||
#[inline]
|
||||
fn read_string(&mut self) -> Result<String, SerializationError> {
|
||||
let len = self.read_compactsize()?;
|
||||
let mut buf = vec![0; len as usize];
|
||||
self.read_exact(&mut buf)?;
|
||||
String::from_utf8(buf).map_err(|_| SerializationError::Parse("invalid utf-8"))
|
||||
}
|
||||
|
||||
/// Convenience method to read a `[u8; 4]`.
|
||||
#[inline]
|
||||
fn read_4_bytes(&mut self) -> io::Result<[u8; 4]> {
|
||||
let mut bytes = [0; 4];
|
||||
self.read_exact(&mut bytes)?;
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
/// Convenience method to read a `[u8; 12]`.
|
||||
#[inline]
|
||||
fn read_12_bytes(&mut self) -> io::Result<[u8; 12]> {
|
||||
let mut bytes = [0; 12];
|
||||
self.read_exact(&mut bytes)?;
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
/// Convenience method to read a `[u8; 32]`.
|
||||
#[inline]
|
||||
fn read_32_bytes(&mut self) -> io::Result<[u8; 32]> {
|
||||
let mut bytes = [0; 32];
|
||||
self.read_exact(&mut bytes)?;
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
/// Convenience method to read a `[u8; 64]`.
|
||||
#[inline]
|
||||
fn read_64_bytes(&mut self) -> io::Result<[u8; 64]> {
|
||||
let mut bytes = [0; 64];
|
||||
self.read_exact(&mut bytes)?;
|
||||
Ok(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark all types implementing `Read` as implementing the extension.
|
||||
impl<R: io::Read + ?Sized> ReadZcashExt for R {}
|
||||
|
||||
/// Helper for deserializing more succinctly via type inference
|
||||
pub trait ZcashDeserializeInto {
|
||||
/// Deserialize based on type inference
|
||||
fn zcash_deserialize_into<T>(self) -> Result<T, SerializationError>
|
||||
where
|
||||
T: ZcashDeserialize;
|
||||
}
|
||||
|
||||
impl<R: io::Read> ZcashDeserializeInto for R {
|
||||
fn zcash_deserialize_into<T>(self) -> Result<T, SerializationError>
|
||||
where
|
||||
T: ZcashDeserialize,
|
||||
{
|
||||
T::zcash_deserialize(self)
|
||||
}
|
||||
}
|
||||
pub use error::SerializationError;
|
||||
pub use read_zcash::ReadZcashExt;
|
||||
pub use write_zcash::WriteZcashExt;
|
||||
pub use zcash_deserialize::{ZcashDeserialize, ZcashDeserializeInto};
|
||||
pub use zcash_serialize::ZcashSerialize;
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unnecessary_operation)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use proptest::prelude::*;
|
||||
|
||||
use std::io::Cursor;
|
||||
|
||||
proptest! {
|
||||
// The tests below are cheap so we can run them a lot.
|
||||
#![proptest_config(ProptestConfig::with_cases(100_000))]
|
||||
|
||||
#[test]
|
||||
fn compactsize_write_then_read_round_trip(s in 0u64..0x2_0000u64) {
|
||||
// Maximum encoding size of a compactsize is 9 bytes.
|
||||
let mut buf = [0u8; 8+1];
|
||||
Cursor::new(&mut buf[..]).write_compactsize(s).unwrap();
|
||||
let expect_s = Cursor::new(&buf[..]).read_compactsize().unwrap();
|
||||
prop_assert_eq!(s, expect_s);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compactsize_read_then_write_round_trip(bytes in prop::array::uniform9(0u8..)) {
|
||||
// Only do the test if the bytes were valid.
|
||||
if let Ok(s) = Cursor::new(&bytes[..]).read_compactsize() {
|
||||
// The compactsize encoding is variable-length, so we may not even
|
||||
// read all of the input bytes, and therefore we can't expect that
|
||||
// the encoding will reproduce bytes that were never read. Instead,
|
||||
// copy the input bytes, and overwrite them with the encoding of s,
|
||||
// so that if the encoding is different, we'll catch it on the part
|
||||
// that's written.
|
||||
let mut expect_bytes = bytes;
|
||||
Cursor::new(&mut expect_bytes[..]).write_compactsize(s).unwrap();
|
||||
prop_assert_eq!(bytes, expect_bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
mod proptests;
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
use std::io;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
/// A serialization error.
|
||||
// XXX refine error types -- better to use boxed errors?
|
||||
#[derive(Error, Debug)]
|
||||
pub enum SerializationError {
|
||||
/// An io error that prevented deserialization
|
||||
#[error("unable to deserialize type")]
|
||||
Io(#[from] io::Error),
|
||||
/// The data to be deserialized was malformed.
|
||||
// XXX refine errors
|
||||
#[error("parse error: {0}")]
|
||||
Parse(&'static str),
|
||||
/// An error caused when validating a zatoshi `Amount`
|
||||
#[error("input couldn't be parsed as a zatoshi `Amount`")]
|
||||
Amount {
|
||||
/// The source error indicating how the num failed to validate
|
||||
#[from]
|
||||
source: crate::types::amount::Error,
|
||||
},
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
//! Property-based tests for basic serialization primitives.
|
||||
|
||||
use super::*;
|
||||
use proptest::prelude::*;
|
||||
|
||||
use std::io::Cursor;
|
||||
|
||||
proptest! {
|
||||
// The tests below are cheap so we can run them a lot.
|
||||
#![proptest_config(ProptestConfig::with_cases(100_000))]
|
||||
|
||||
#[test]
|
||||
fn compactsize_write_then_read_round_trip(s in 0u64..0x2_0000u64) {
|
||||
// Maximum encoding size of a compactsize is 9 bytes.
|
||||
let mut buf = [0u8; 8+1];
|
||||
Cursor::new(&mut buf[..]).write_compactsize(s).unwrap();
|
||||
let expect_s = Cursor::new(&buf[..]).read_compactsize().unwrap();
|
||||
prop_assert_eq!(s, expect_s);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compactsize_read_then_write_round_trip(bytes in prop::array::uniform9(0u8..)) {
|
||||
// Only do the test if the bytes were valid.
|
||||
if let Ok(s) = Cursor::new(&bytes[..]).read_compactsize() {
|
||||
// The compactsize encoding is variable-length, so we may not even
|
||||
// read all of the input bytes, and therefore we can't expect that
|
||||
// the encoding will reproduce bytes that were never read. Instead,
|
||||
// copy the input bytes, and overwrite them with the encoding of s,
|
||||
// so that if the encoding is different, we'll catch it on the part
|
||||
// that's written.
|
||||
let mut expect_bytes = bytes;
|
||||
Cursor::new(&mut expect_bytes[..]).write_compactsize(s).unwrap();
|
||||
prop_assert_eq!(bytes, expect_bytes);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
use std::io;
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
|
||||
use byteorder::{BigEndian, LittleEndian, ReadBytesExt};
|
||||
|
||||
use super::SerializationError;
|
||||
|
||||
/// Extends [`Read`] with methods for writing Zcash/Bitcoin types.
|
||||
///
|
||||
/// [`Read`]: https://doc.rust-lang.org/std/io/trait.Read.html
|
||||
pub trait ReadZcashExt: io::Read {
|
||||
/// Reads a `u64` using the Bitcoin `CompactSize` encoding.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use zebra_chain::serialization::ReadZcashExt;
|
||||
///
|
||||
/// use std::io::Cursor;
|
||||
/// assert_eq!(
|
||||
/// 0x12,
|
||||
/// Cursor::new(b"\x12")
|
||||
/// .read_compactsize().unwrap()
|
||||
/// );
|
||||
/// assert_eq!(
|
||||
/// 0xfd,
|
||||
/// Cursor::new(b"\xfd\xfd\x00")
|
||||
/// .read_compactsize().unwrap()
|
||||
/// );
|
||||
/// assert_eq!(
|
||||
/// 0xaafd,
|
||||
/// Cursor::new(b"\xfd\xfd\xaa")
|
||||
/// .read_compactsize().unwrap()
|
||||
/// );
|
||||
/// assert_eq!(
|
||||
/// 0xbbaafd,
|
||||
/// Cursor::new(b"\xfe\xfd\xaa\xbb\x00")
|
||||
/// .read_compactsize().unwrap()
|
||||
/// );
|
||||
/// assert_eq!(
|
||||
/// 0x22ccbbaafd,
|
||||
/// Cursor::new(b"\xff\xfd\xaa\xbb\xcc\x22\x00\x00\x00")
|
||||
/// .read_compactsize().unwrap()
|
||||
/// );
|
||||
/// ```
|
||||
#[inline]
|
||||
fn read_compactsize(&mut self) -> Result<u64, SerializationError> {
|
||||
use SerializationError::Parse;
|
||||
let flag_byte = self.read_u8()?;
|
||||
match flag_byte {
|
||||
n @ 0x00..=0xfc => Ok(n as u64),
|
||||
0xfd => match self.read_u16::<LittleEndian>()? {
|
||||
n @ 0x0000_00fd..=0x0000_ffff => Ok(n as u64),
|
||||
_ => Err(Parse("non-canonical compactsize")),
|
||||
},
|
||||
0xfe => match self.read_u32::<LittleEndian>()? {
|
||||
n @ 0x0001_0000..=0xffff_ffff => Ok(n as u64),
|
||||
_ => Err(Parse("non-canonical compactsize")),
|
||||
},
|
||||
0xff => match self.read_u64::<LittleEndian>()? {
|
||||
n @ 0x1_0000_0000..=0xffff_ffff_ffff_ffff => Ok(n),
|
||||
_ => Err(Parse("non-canonical compactsize")),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Read an IP address in Bitcoin format.
|
||||
#[inline]
|
||||
fn read_ip_addr(&mut self) -> io::Result<IpAddr> {
|
||||
use std::net::{IpAddr::*, Ipv6Addr};
|
||||
|
||||
let mut octets = [0u8; 16];
|
||||
self.read_exact(&mut octets)?;
|
||||
let v6_addr = Ipv6Addr::from(octets);
|
||||
|
||||
match v6_addr.to_ipv4() {
|
||||
Some(v4_addr) => Ok(V4(v4_addr)),
|
||||
None => Ok(V6(v6_addr)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Read a Bitcoin-encoded `SocketAddr`.
|
||||
#[inline]
|
||||
fn read_socket_addr(&mut self) -> io::Result<SocketAddr> {
|
||||
let ip_addr = self.read_ip_addr()?;
|
||||
let port = self.read_u16::<BigEndian>()?;
|
||||
Ok(SocketAddr::new(ip_addr, port))
|
||||
}
|
||||
|
||||
/// Read a Bitcoin-encoded UTF-8 string.
|
||||
#[inline]
|
||||
fn read_string(&mut self) -> Result<String, SerializationError> {
|
||||
let len = self.read_compactsize()?;
|
||||
let mut buf = vec![0; len as usize];
|
||||
self.read_exact(&mut buf)?;
|
||||
String::from_utf8(buf).map_err(|_| SerializationError::Parse("invalid utf-8"))
|
||||
}
|
||||
|
||||
/// Convenience method to read a `[u8; 4]`.
|
||||
#[inline]
|
||||
fn read_4_bytes(&mut self) -> io::Result<[u8; 4]> {
|
||||
let mut bytes = [0; 4];
|
||||
self.read_exact(&mut bytes)?;
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
/// Convenience method to read a `[u8; 12]`.
|
||||
#[inline]
|
||||
fn read_12_bytes(&mut self) -> io::Result<[u8; 12]> {
|
||||
let mut bytes = [0; 12];
|
||||
self.read_exact(&mut bytes)?;
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
/// Convenience method to read a `[u8; 32]`.
|
||||
#[inline]
|
||||
fn read_32_bytes(&mut self) -> io::Result<[u8; 32]> {
|
||||
let mut bytes = [0; 32];
|
||||
self.read_exact(&mut bytes)?;
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
/// Convenience method to read a `[u8; 64]`.
|
||||
#[inline]
|
||||
fn read_64_bytes(&mut self) -> io::Result<[u8; 64]> {
|
||||
let mut bytes = [0; 64];
|
||||
self.read_exact(&mut bytes)?;
|
||||
Ok(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark all types implementing `Read` as implementing the extension.
|
||||
impl<R: io::Read + ?Sized> ReadZcashExt for R {}
|
|
@ -0,0 +1,95 @@
|
|||
use std::io;
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
|
||||
use byteorder::{BigEndian, LittleEndian, WriteBytesExt};
|
||||
|
||||
/// Extends [`Write`] with methods for writing Zcash/Bitcoin types.
|
||||
///
|
||||
/// [`Write`]: https://doc.rust-lang.org/std/io/trait.Write.html
|
||||
pub trait WriteZcashExt: io::Write {
|
||||
/// Writes a `u64` using the Bitcoin `CompactSize` encoding.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use zebra_chain::serialization::WriteZcashExt;
|
||||
///
|
||||
/// let mut buf = Vec::new();
|
||||
/// buf.write_compactsize(0x12).unwrap();
|
||||
/// assert_eq!(buf, b"\x12");
|
||||
///
|
||||
/// let mut buf = Vec::new();
|
||||
/// buf.write_compactsize(0xfd).unwrap();
|
||||
/// assert_eq!(buf, b"\xfd\xfd\x00");
|
||||
///
|
||||
/// let mut buf = Vec::new();
|
||||
/// buf.write_compactsize(0xaafd).unwrap();
|
||||
/// assert_eq!(buf, b"\xfd\xfd\xaa");
|
||||
///
|
||||
/// let mut buf = Vec::new();
|
||||
/// buf.write_compactsize(0xbbaafd).unwrap();
|
||||
/// assert_eq!(buf, b"\xfe\xfd\xaa\xbb\x00");
|
||||
///
|
||||
/// let mut buf = Vec::new();
|
||||
/// buf.write_compactsize(0x22ccbbaafd).unwrap();
|
||||
/// assert_eq!(buf, b"\xff\xfd\xaa\xbb\xcc\x22\x00\x00\x00");
|
||||
/// ```
|
||||
#[inline]
|
||||
fn write_compactsize(&mut self, n: u64) -> io::Result<()> {
|
||||
match n {
|
||||
0x0000_0000..=0x0000_00fc => self.write_u8(n as u8),
|
||||
0x0000_00fd..=0x0000_ffff => {
|
||||
self.write_u8(0xfd)?;
|
||||
self.write_u16::<LittleEndian>(n as u16)
|
||||
}
|
||||
0x0001_0000..=0xffff_ffff => {
|
||||
self.write_u8(0xfe)?;
|
||||
self.write_u32::<LittleEndian>(n as u32)
|
||||
}
|
||||
_ => {
|
||||
self.write_u8(0xff)?;
|
||||
self.write_u64::<LittleEndian>(n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Write an `IpAddr` in Bitcoin format.
|
||||
#[inline]
|
||||
fn write_ip_addr(&mut self, addr: IpAddr) -> io::Result<()> {
|
||||
use std::net::IpAddr::*;
|
||||
let v6_addr = match addr {
|
||||
V4(ref v4) => v4.to_ipv6_mapped(),
|
||||
V6(v6) => v6,
|
||||
};
|
||||
self.write_all(&v6_addr.octets())
|
||||
}
|
||||
|
||||
/// Write a `SocketAddr` in Bitcoin format.
|
||||
#[inline]
|
||||
fn write_socket_addr(&mut self, addr: SocketAddr) -> io::Result<()> {
|
||||
self.write_ip_addr(addr.ip())?;
|
||||
self.write_u16::<BigEndian>(addr.port())
|
||||
}
|
||||
|
||||
/// Write a string in Bitcoin format.
|
||||
#[inline]
|
||||
fn write_string(&mut self, string: &str) -> io::Result<()> {
|
||||
self.write_compactsize(string.len() as u64)?;
|
||||
self.write_all(string.as_bytes())
|
||||
}
|
||||
|
||||
/// Convenience method to write exactly 32 u8's.
|
||||
#[inline]
|
||||
fn write_32_bytes(&mut self, bytes: &[u8; 32]) -> io::Result<()> {
|
||||
self.write_all(bytes)
|
||||
}
|
||||
|
||||
/// Convenience method to write exactly 64 u8's.
|
||||
#[inline]
|
||||
fn write_64_bytes(&mut self, bytes: &[u8; 64]) -> io::Result<()> {
|
||||
self.write_all(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark all types implementing `Write` as implementing the extension.
|
||||
impl<W: io::Write + ?Sized> WriteZcashExt for W {}
|
|
@ -0,0 +1,51 @@
|
|||
use std::io;
|
||||
|
||||
use super::{ReadZcashExt, SerializationError};
|
||||
|
||||
/// Consensus-critical serialization for Zcash.
|
||||
///
|
||||
/// This trait provides a generic deserialization for consensus-critical
|
||||
/// formats, such as network messages, transactions, blocks, etc. It is intended
|
||||
/// for use only in consensus-critical contexts; in other contexts, such as
|
||||
/// internal storage, it would be preferable to use Serde.
|
||||
pub trait ZcashDeserialize: Sized {
|
||||
/// Try to read `self` from the given `reader`.
|
||||
///
|
||||
/// This function has a `zcash_` prefix to alert the reader that the
|
||||
/// serialization in use is consensus-critical serialization, rather than
|
||||
/// some other kind of serialization.
|
||||
fn zcash_deserialize<R: io::Read>(reader: R) -> Result<Self, SerializationError>;
|
||||
}
|
||||
|
||||
impl<T: ZcashDeserialize> ZcashDeserialize for Vec<T> {
|
||||
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
|
||||
let len = reader.read_compactsize()?;
|
||||
// We're given len, so we could preallocate. But blindly preallocating
|
||||
// without a size bound can allow DOS attacks, and there's no way to
|
||||
// pass a size bound in a ZcashDeserialize impl, so instead we allocate
|
||||
// as we read from the reader. (The maximum block and transaction sizes
|
||||
// limit the eventual size of these allocations.)
|
||||
let mut vec = Vec::new();
|
||||
for _ in 0..len {
|
||||
vec.push(T::zcash_deserialize(&mut reader)?);
|
||||
}
|
||||
Ok(vec)
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper for deserializing more succinctly via type inference
|
||||
pub trait ZcashDeserializeInto {
|
||||
/// Deserialize based on type inference
|
||||
fn zcash_deserialize_into<T>(self) -> Result<T, SerializationError>
|
||||
where
|
||||
T: ZcashDeserialize;
|
||||
}
|
||||
|
||||
impl<R: io::Read> ZcashDeserializeInto for R {
|
||||
fn zcash_deserialize_into<T>(self) -> Result<T, SerializationError>
|
||||
where
|
||||
T: ZcashDeserialize,
|
||||
{
|
||||
T::zcash_deserialize(self)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
use std::io;
|
||||
|
||||
use super::WriteZcashExt;
|
||||
|
||||
/// Consensus-critical serialization for Zcash.
|
||||
///
|
||||
/// This trait provides a generic serialization for consensus-critical
|
||||
/// formats, such as network messages, transactions, blocks, etc. It is intended
|
||||
/// for use only in consensus-critical contexts; in other contexts, such as
|
||||
/// internal storage, it would be preferable to use Serde.
|
||||
pub trait ZcashSerialize: Sized {
|
||||
/// Write `self` to the given `writer` using the canonical format.
|
||||
///
|
||||
/// This function has a `zcash_` prefix to alert the reader that the
|
||||
/// serialization in use is consensus-critical serialization, rather than
|
||||
/// some other kind of serialization.
|
||||
///
|
||||
/// Notice that the error type is [`std::io::Error`]; this indicates that
|
||||
/// serialization MUST be infallible up to errors in the underlying writer.
|
||||
/// In other words, any type implementing `ZcashSerialize` must make illegal
|
||||
/// states unrepresentable.
|
||||
fn zcash_serialize<W: io::Write>(&self, writer: W) -> Result<(), io::Error>;
|
||||
|
||||
/// Helper function to construct a vec to serialize the current struct into
|
||||
fn zcash_serialize_to_vec(&self) -> Result<Vec<u8>, io::Error> {
|
||||
let mut data = Vec::new();
|
||||
self.zcash_serialize(&mut data)?;
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ZcashSerialize> ZcashSerialize for Vec<T> {
|
||||
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
|
||||
writer.write_compactsize(self.len() as u64)?;
|
||||
for x in self {
|
||||
x.zcash_serialize(&mut writer)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue