X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Futil%2Fser.rs;h=3de03ea5a0d5efd93b4c3a22e4d88845656dbf23;hb=c06ab02900d454d48f75a54c8ab5c80785acbf8e;hp=8fb72a8f9fe643f56cfc259e5b7214b56a4fb761;hpb=4bb81ff5942749077613827d6807b64230ecbcd5;p=rust-lightning diff --git a/lightning/src/util/ser.rs b/lightning/src/util/ser.rs index 8fb72a8f..3de03ea5 100644 --- a/lightning/src/util/ser.rs +++ b/lightning/src/util/ser.rs @@ -16,10 +16,12 @@ use io_extras::{copy, sink}; use core::hash::Hash; use sync::Mutex; use core::cmp; +use core::convert::TryFrom; +use core::ops::Deref; -use bitcoin::secp256k1::Signature; -use bitcoin::secp256k1::key::{PublicKey, SecretKey}; +use bitcoin::secp256k1::{PublicKey, SecretKey}; use bitcoin::secp256k1::constants::{PUBLIC_KEY_SIZE, SECRET_KEY_SIZE, COMPACT_SIGNATURE_SIZE}; +use bitcoin::secp256k1::ecdsa::Signature; use bitcoin::blockdata::script::Script; use bitcoin::blockdata::transaction::{OutPoint, Transaction, TxOut}; use bitcoin::consensus; @@ -134,6 +136,13 @@ impl Read for FixedLengthReader { } } +impl LengthRead for FixedLengthReader { + #[inline] + fn total_bytes(&self) -> u64 { + self.total_bytes + } +} + /// A Read which tracks whether any bytes have been read at all. This allows us to distinguish /// between "EOF reached before we started" and "EOF reached mid-read". pub(crate) struct ReadTrackingReader { @@ -174,6 +183,7 @@ pub trait Writeable { } /// Writes self out to a Vec + #[cfg(test)] fn encode_with_len(&self) -> Vec { let mut msg = VecWriter(Vec::new()); 0u16.write(&mut msg).unwrap(); @@ -219,6 +229,29 @@ pub trait ReadableArgs

fn read(reader: &mut R, params: P) -> Result; } +/// A std::io::Read that also provides the total bytes available to read. +pub(crate) trait LengthRead: Read { + /// The total number of bytes available to read. + fn total_bytes(&self) -> u64; +} + +/// A trait that various higher-level rust-lightning types implement allowing them to be read in +/// from a Read given some additional set of arguments which is required to deserialize, requiring +/// the implementer to provide the total length of the read. +pub(crate) trait LengthReadableArgs

where Self: Sized +{ + /// Reads a Self in from the given LengthRead + fn read(reader: &mut R, params: P) -> Result; +} + +/// A trait that various higher-level rust-lightning types implement allowing them to be read in +/// from a Read, requiring the implementer to provide the total length of the read. +pub(crate) trait LengthReadable where Self: Sized +{ + /// Reads a Self in from the given LengthRead + fn read(reader: &mut R) -> Result; +} + /// A trait that various rust-lightning types implement allowing them to (maybe) be read in from a Read /// /// (C-not exported) as we only export serialization to/from byte arrays instead @@ -243,6 +276,12 @@ impl Readable for OptionDeserWrapper { Ok(Self(Some(Readable::read(reader)?))) } } +/// When handling default_values, we want to map the default-value T directly +/// to a OptionDeserWrapper in a way that works for `field: T = t;` as +/// well. Thus, we assume `Into for T` does nothing and use that. +impl From for OptionDeserWrapper { + fn from(t: T) -> OptionDeserWrapper { OptionDeserWrapper(Some(t)) } +} /// Wrapper to write each element of a Vec with no length prefix pub(crate) struct VecWriteWrapper<'a, T: Writeable>(pub &'a Vec); @@ -300,7 +339,7 @@ impl Readable for U48 { /// encoded in several different ways, which we must check for at deserialization-time. Thus, if /// you're looking for an example of a variable-length integer to use for your own project, move /// along, this is a rather poor design. -pub(crate) struct BigSize(pub u64); +pub struct BigSize(pub u64); impl Writeable for BigSize { #[inline] fn write(&self, writer: &mut W) -> Result<(), io::Error> { @@ -360,8 +399,8 @@ impl Readable for BigSize { /// In TLV we occasionally send fields which only consist of, or potentially end with, a /// variable-length integer which is simply truncated by skipping high zero bytes. This type /// encapsulates such integers implementing Readable/Writeable for them. -#[cfg_attr(test, derive(PartialEq, Debug))] -pub(crate) struct HighZeroBytesDroppedVarInt(pub T); +#[cfg_attr(test, derive(PartialEq, Eq, Debug))] +pub(crate) struct HighZeroBytesDroppedBigSize(pub T); macro_rules! impl_writeable_primitive { ($val_type:ty, $len: expr) => { @@ -371,7 +410,7 @@ macro_rules! impl_writeable_primitive { writer.write_all(&self.to_be_bytes()) } } - impl Writeable for HighZeroBytesDroppedVarInt<$val_type> { + impl Writeable for HighZeroBytesDroppedBigSize<$val_type> { #[inline] fn write(&self, writer: &mut W) -> Result<(), io::Error> { // Skip any full leading 0 bytes when writing (in BE): @@ -386,9 +425,9 @@ macro_rules! impl_writeable_primitive { Ok(<$val_type>::from_be_bytes(buf)) } } - impl Readable for HighZeroBytesDroppedVarInt<$val_type> { + impl Readable for HighZeroBytesDroppedBigSize<$val_type> { #[inline] - fn read(reader: &mut R) -> Result, DecodeError> { + fn read(reader: &mut R) -> Result, DecodeError> { // We need to accept short reads (read_len == 0) as "EOF" and handle them as simply // the high bytes being dropped. To do so, we start reading into the middle of buf // and then convert the appropriate number of bytes with extra high bytes out of @@ -404,7 +443,7 @@ macro_rules! impl_writeable_primitive { let first_byte = $len - ($len - total_read_len); let mut bytes = [0; $len]; bytes.copy_from_slice(&buf[first_byte..first_byte + $len]); - Ok(HighZeroBytesDroppedVarInt(<$val_type>::from_be_bytes(bytes))) + Ok(HighZeroBytesDroppedBigSize(<$val_type>::from_be_bytes(bytes))) } else { // If the encoding had extra zero bytes, return a failure even though we know // what they meant (as the TLV test vectors require this) @@ -475,16 +514,38 @@ macro_rules! impl_array { ); } -//TODO: performance issue with [u8; size] with impl_array!() impl_array!(3); // for rgb impl_array!(4); // for IPv4 -impl_array!(10); // for OnionV2 +impl_array!(12); // for OnionV2 impl_array!(16); // for IPv6 impl_array!(32); // for channel id & hmac impl_array!(PUBLIC_KEY_SIZE); // for PublicKey impl_array!(COMPACT_SIGNATURE_SIZE); // for Signature impl_array!(1300); // for OnionPacket.hop_data +impl Writeable for [u16; 8] { + #[inline] + fn write(&self, w: &mut W) -> Result<(), io::Error> { + for v in self.iter() { + w.write_all(&v.to_be_bytes())? + } + Ok(()) + } +} + +impl Readable for [u16; 8] { + #[inline] + fn read(r: &mut R) -> Result { + let mut buf = [0u8; 16]; + r.read_exact(&mut buf)?; + let mut res = [0u16; 8]; + for (idx, v) in res.iter_mut().enumerate() { + *v = (buf[idx] as u16) << 8 | (buf[idx + 1] as u16) + } + Ok(res) + } +} + // HashMap impl Writeable for HashMap where K: Writeable + Eq + Hash, @@ -822,7 +883,7 @@ macro_rules! impl_consensus_ser { ($bitcoin_type: ty) => { impl Writeable for $bitcoin_type { fn write(&self, writer: &mut W) -> Result<(), io::Error> { - match self.consensus_encode(WriterWriteAdaptor(writer)) { + match self.consensus_encode(&mut WriterWriteAdaptor(writer)) { Ok(_) => Ok(()), Err(e) => Err(e), } @@ -913,6 +974,75 @@ impl Readable for String { } } +/// Represents a hostname for serialization purposes. +/// Only the character set and length will be validated. +/// The character set consists of ASCII alphanumeric characters, hyphens, and periods. +/// Its length is guaranteed to be representable by a single byte. +/// This serialization is used by BOLT 7 hostnames. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Hostname(String); +impl Hostname { + /// Returns the length of the hostname. + pub fn len(&self) -> u8 { + (&self.0).len() as u8 + } +} +impl Deref for Hostname { + type Target = String; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl From for String { + fn from(hostname: Hostname) -> Self { + hostname.0 + } +} +impl TryFrom> for Hostname { + type Error = (); + + fn try_from(bytes: Vec) -> Result { + if let Ok(s) = String::from_utf8(bytes) { + Hostname::try_from(s) + } else { + Err(()) + } + } +} +impl TryFrom for Hostname { + type Error = (); + + fn try_from(s: String) -> Result { + if s.len() <= 255 && s.chars().all(|c| + c.is_ascii_alphanumeric() || + c == '.' || + c == '-' + ) { + Ok(Hostname(s)) + } else { + Err(()) + } + } +} +impl Writeable for Hostname { + #[inline] + fn write(&self, w: &mut W) -> Result<(), io::Error> { + self.len().write(w)?; + w.write_all(self.as_bytes()) + } +} +impl Readable for Hostname { + #[inline] + fn read(r: &mut R) -> Result { + let len: u8 = Readable::read(r)?; + let mut vec = Vec::with_capacity(len.into()); + vec.resize(len.into(), 0); + r.read_exact(&mut vec)?; + Hostname::try_from(vec).map_err(|_| DecodeError::InvalidValue) + } +} + impl Writeable for Duration { #[inline] fn write(&self, w: &mut W) -> Result<(), io::Error> { @@ -928,3 +1058,29 @@ impl Readable for Duration { Ok(Duration::new(secs, nanos)) } } + +#[cfg(test)] +mod tests { + use core::convert::TryFrom; + use util::ser::{Readable, Hostname, Writeable}; + + #[test] + fn hostname_conversion() { + assert_eq!(Hostname::try_from(String::from("a-test.com")).unwrap().as_str(), "a-test.com"); + + assert!(Hostname::try_from(String::from("\"")).is_err()); + assert!(Hostname::try_from(String::from("$")).is_err()); + assert!(Hostname::try_from(String::from("⚡")).is_err()); + let mut large_vec = Vec::with_capacity(256); + large_vec.resize(256, b'A'); + assert!(Hostname::try_from(String::from_utf8(large_vec).unwrap()).is_err()); + } + + #[test] + fn hostname_serialization() { + let hostname = Hostname::try_from(String::from("test")).unwrap(); + let mut buf: Vec = Vec::new(); + hostname.write(&mut buf).unwrap(); + assert_eq!(Hostname::read(&mut buf.as_slice()).unwrap().as_str(), "test"); + } +}