diff --git a/src/consensus/encode.rs b/src/consensus/encode.rs index b409c88b..a7fa71b7 100644 --- a/src/consensus/encode.rs +++ b/src/consensus/encode.rs @@ -29,11 +29,8 @@ //! big-endian decimals, etc.) //! -use std::{mem, u32}; - -use std::error; -use std::fmt; -use std::io; +use std::{fmt, error, io, mem, u32}; +use std::borrow::Cow; use std::io::{Cursor, Read, Write}; use hashes::hex::ToHex; @@ -483,6 +480,26 @@ impl Decodable for String { } } +// Cow<'static, str> +impl Encodable for Cow<'static, str> { + #[inline] + fn consensus_encode(&self, mut s: S) -> Result { + let b = self.as_bytes(); + let vi_len = VarInt(b.len() as u64).consensus_encode(&mut s)?; + s.emit_slice(&b)?; + Ok(vi_len + b.len()) + } +} + +impl Decodable for Cow<'static, str> { + #[inline] + fn consensus_decode(d: D) -> Result, Error> { + String::from_utf8(Decodable::consensus_decode(d)?) + .map_err(|_| self::Error::ParseFailed("String was not valid UTF8")) + .map(Cow::Owned) + } +} + // Arrays macro_rules! impl_array { @@ -894,6 +911,10 @@ mod tests { #[test] fn deserialize_strbuf_test() { assert_eq!(deserialize(&[6u8, 0x41, 0x6e, 0x64, 0x72, 0x65, 0x77]).ok(), Some("Andrew".to_string())); + assert_eq!( + deserialize(&[6u8, 0x41, 0x6e, 0x64, 0x72, 0x65, 0x77]).ok(), + Some(::std::borrow::Cow::Borrowed("Andrew")) + ); } #[test] diff --git a/src/network/message.rs b/src/network/message.rs index d87a6d4c..85163673 100644 --- a/src/network/message.rs +++ b/src/network/message.rs @@ -19,7 +19,8 @@ //! also defines (de)serialization routines for many primitives. //! -use std::{io, iter, mem}; +use std::{io, iter, mem, fmt}; +use std::borrow::Cow; use std::io::Cursor; use blockdata::block; @@ -34,7 +35,31 @@ use consensus::encode::MAX_VEC_SIZE; /// Serializer for command string #[derive(PartialEq, Eq, Clone, Debug)] -pub struct CommandString(pub String); +pub struct CommandString(Cow<'static, str>); + +impl fmt::Display for CommandString { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(self.0.as_ref()) + } +} + +impl From<&'static str> for CommandString { + fn from(f: &'static str) -> Self { + CommandString(f.into()) + } +} + +impl From for CommandString { + fn from(f: String) -> Self { + CommandString(f.into()) + } +} + +impl AsRef for CommandString { + fn as_ref(&self) -> &str { + self.0.as_ref() + } +} impl Encodable for CommandString { #[inline] @@ -42,11 +67,10 @@ impl Encodable for CommandString { &self, s: S, ) -> Result { - let &CommandString(ref inner_str) = self; let mut rawbytes = [0u8; 12]; - let strbytes = inner_str.as_bytes(); + let strbytes = self.0.as_bytes(); if strbytes.len() > 12 { - panic!("Command string longer than 12 bytes"); + return Err(encode::Error::UnrecognizedNetworkCommand(self.0.clone().into_owned())); } for x in 0..strbytes.len() { rawbytes[x] = strbytes[x]; @@ -116,7 +140,6 @@ pub enum NetworkMessage { Ping(u64), /// `pong` Pong(u64), - // TODO: reject, // TODO: bloom filtering /// BIP157 getcfilters GetCFilters(message_filter::GetCFilters), @@ -136,10 +159,10 @@ pub enum NetworkMessage { Reject(message_network::Reject) } -impl RawNetworkMessage { +impl NetworkMessage { /// Return the message command. This is useful for debug outputs. - pub fn command(&self) -> String { - match self.payload { + pub fn cmd(&self) -> &'static str { + match *self { NetworkMessage::Version(_) => "version", NetworkMessage::Verack => "verack", NetworkMessage::Addr(_) => "addr", @@ -164,7 +187,24 @@ impl RawNetworkMessage { NetworkMessage::CFCheckpt(_) => "cfcheckpt", NetworkMessage::Alert(_) => "alert", NetworkMessage::Reject(_) => "reject", - }.to_owned() + } + } + + /// Return the CommandString for the message command. + pub fn command(&self) -> CommandString { + self.cmd().into() + } +} + +impl RawNetworkMessage { + /// Return the message command. This is useful for debug outputs. + pub fn cmd(&self) -> &'static str { + self.payload.cmd() + } + + /// Return the CommandString for the message command. + pub fn command(&self) -> CommandString { + self.payload.command() } } @@ -193,7 +233,7 @@ impl Encodable for RawNetworkMessage { ) -> Result { let mut len = 0; len += self.magic.consensus_encode(&mut s)?; - len += CommandString(self.command()).consensus_encode(&mut s)?; + len += self.command().consensus_encode(&mut s)?; len += CheckedData(match self.payload { NetworkMessage::Version(ref dat) => serialize(dat), NetworkMessage::Addr(ref dat) => serialize(dat), @@ -281,7 +321,7 @@ impl Decodable for RawNetworkMessage { "cfcheckpt" => NetworkMessage::CFCheckpt(Decodable::consensus_decode(&mut mem_d)?), "reject" => NetworkMessage::Reject(Decodable::consensus_decode(&mut mem_d)?), "alert" => NetworkMessage::Alert(Decodable::consensus_decode(&mut mem_d)?), - _ => return Err(encode::Error::UnrecognizedNetworkCommand(cmd)), + _ => return Err(encode::Error::UnrecognizedNetworkCommand(cmd.into_owned())), }; Ok(RawNetworkMessage { magic: magic, @@ -292,20 +332,26 @@ impl Decodable for RawNetworkMessage { #[cfg(test)] mod test { + use std::io; use super::{RawNetworkMessage, NetworkMessage, CommandString}; - use consensus::encode::{deserialize, deserialize_partial, serialize}; + use consensus::encode::{Encodable, deserialize, deserialize_partial, serialize}; #[test] fn serialize_commandstring_test() { - let cs = CommandString("Andrew".to_owned()); + let cs = CommandString("Andrew".into()); assert_eq!(serialize(&cs), vec![0x41u8, 0x6e, 0x64, 0x72, 0x65, 0x77, 0, 0, 0, 0, 0, 0]); + + // Test oversized one. + let mut encoder = io::Cursor::new(vec![]); + assert!(CommandString("AndrewAndrewA".into()).consensus_encode(&mut encoder).is_err()); } #[test] fn deserialize_commandstring_test() { let cs: Result = deserialize(&[0x41u8, 0x6e, 0x64, 0x72, 0x65, 0x77, 0, 0, 0, 0, 0, 0]); assert!(cs.is_ok()); - assert_eq!(cs.unwrap(), CommandString("Andrew".to_owned())); + assert_eq!(cs.as_ref().unwrap().to_string(), "Andrew".to_owned()); + assert_eq!(cs.unwrap(), "Andrew".into()); let short_cs: Result = deserialize(&[0x41u8, 0x6e, 0x64, 0x72, 0x65, 0x77, 0, 0, 0, 0, 0]); assert!(short_cs.is_err()); diff --git a/src/network/message_blockdata.rs b/src/network/message_blockdata.rs index c49d0072..bbf43a74 100644 --- a/src/network/message_blockdata.rs +++ b/src/network/message_blockdata.rs @@ -24,7 +24,7 @@ use hashes::sha256d; use std::io; -#[derive(PartialEq, Eq, Clone, Debug)] +#[derive(PartialEq, Eq, Clone, Debug, Copy)] /// The type of an inventory object pub enum InvType { /// Error --- these inventories can be ignored diff --git a/src/network/message_network.rs b/src/network/message_network.rs index 45056507..c3e5eab4 100644 --- a/src/network/message_network.rs +++ b/src/network/message_network.rs @@ -18,12 +18,14 @@ //! capabilities //! +use std::io; +use std::borrow::Cow; + use network::address::Address; use network::constants::{self, ServiceFlags}; use consensus::{Encodable, Decodable, ReadExt}; use consensus::encode; -use std::io; -use network::message_network::RejectReason::{MALFORMED, INVALID, OBSOLETE, DUPLICATE, NONSTANDARD, DUST, CHECKPOINT, FEE}; +use network::message::CommandString; use hashes::sha256d; /// Some simple messages @@ -87,21 +89,21 @@ impl_consensus_encoding!(VersionMessage, version, services, timestamp, /// message rejection reason as a code pub enum RejectReason { /// malformed message - MALFORMED = 0x01, + Malformed = 0x01, /// invalid message - INVALID = 0x10, + Invalid = 0x10, /// obsolete message - OBSOLETE = 0x11, + Obsolete = 0x11, /// duplicate message - DUPLICATE = 0x12, + Duplicate = 0x12, /// nonstandard transaction - NONSTANDARD = 0x40, + NonStandard = 0x40, /// an output is below dust limit - DUST = 0x41, + Dust = 0x41, /// insufficient fee - FEE = 0x42, + Fee = 0x42, /// checkpoint - CHECKPOINT = 0x43 + Checkpoint = 0x43 } impl Encodable for RejectReason { @@ -114,14 +116,14 @@ impl Encodable for RejectReason { impl Decodable for RejectReason { fn consensus_decode(mut d: D) -> Result { Ok(match d.read_u8()? { - 0x01 => MALFORMED, - 0x10 => INVALID, - 0x11 => OBSOLETE, - 0x12 => DUPLICATE, - 0x40 => NONSTANDARD, - 0x41 => DUST, - 0x42 => FEE, - 0x43 => CHECKPOINT, + 0x01 => RejectReason::Malformed, + 0x10 => RejectReason::Invalid, + 0x11 => RejectReason::Obsolete, + 0x12 => RejectReason::Duplicate, + 0x40 => RejectReason::NonStandard, + 0x41 => RejectReason::Dust, + 0x42 => RejectReason::Fee, + 0x43 => RejectReason::Checkpoint, _ => return Err(encode::Error::ParseFailed("unknown reject code")) }) } @@ -131,11 +133,11 @@ impl Decodable for RejectReason { #[derive(PartialEq, Eq, Clone, Debug)] pub struct Reject { /// message type rejected - pub message: String, + pub message: CommandString, /// reason of rejection as code pub ccode: RejectReason, /// reason of rejectection - pub reason: String, + pub reason: Cow<'static, str>, /// reference to rejected item pub hash: sha256d::Hash }