From bac3e0308b0ced574477793e63b268b9291cc6ce Mon Sep 17 00:00:00 2001 From: Steven Roose Date: Tue, 3 Dec 2019 21:19:44 +0000 Subject: [PATCH 01/11] Add command method to NetworkMessage Also make the return type an &'static str --- src/network/message.rs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/network/message.rs b/src/network/message.rs index d87a6d4c..c59102f8 100644 --- a/src/network/message.rs +++ b/src/network/message.rs @@ -136,10 +136,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 command(&self) -> &'static str { + match *self { NetworkMessage::Version(_) => "version", NetworkMessage::Verack => "verack", NetworkMessage::Addr(_) => "addr", @@ -164,7 +164,14 @@ impl RawNetworkMessage { NetworkMessage::CFCheckpt(_) => "cfcheckpt", NetworkMessage::Alert(_) => "alert", NetworkMessage::Reject(_) => "reject", - }.to_owned() + } + } +} + +impl RawNetworkMessage { + /// Return the message command. This is useful for debug outputs. + pub fn command(&self) -> &'static str { + self.payload.command() } } @@ -193,7 +200,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 += CommandString(self.command().to_owned()).consensus_encode(&mut s)?; len += CheckedData(match self.payload { NetworkMessage::Version(ref dat) => serialize(dat), NetworkMessage::Addr(ref dat) => serialize(dat), From 36838b7918a6bff1ceb66dd44b752b54c3bcee98 Mon Sep 17 00:00:00 2001 From: Steven Roose Date: Tue, 3 Dec 2019 21:22:19 +0000 Subject: [PATCH 02/11] Make network::CommandString a Cow on 'static --- src/network/message.rs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/network/message.rs b/src/network/message.rs index c59102f8..36fa62f3 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,13 @@ use consensus::encode::MAX_VEC_SIZE; /// Serializer for command string #[derive(PartialEq, Eq, Clone, Debug)] -pub struct CommandString(pub String); +pub struct CommandString(pub 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 Encodable for CommandString { #[inline] @@ -200,7 +207,7 @@ impl Encodable for RawNetworkMessage { ) -> Result { let mut len = 0; len += self.magic.consensus_encode(&mut s)?; - len += CommandString(self.command().to_owned()).consensus_encode(&mut s)?; + len += CommandString(self.command().into()).consensus_encode(&mut s)?; len += CheckedData(match self.payload { NetworkMessage::Version(ref dat) => serialize(dat), NetworkMessage::Addr(ref dat) => serialize(dat), @@ -288,7 +295,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, @@ -304,7 +311,7 @@ mod test { #[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]); } @@ -312,7 +319,8 @@ mod 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(), CommandString("Andrew".into())); let short_cs: Result = deserialize(&[0x41u8, 0x6e, 0x64, 0x72, 0x65, 0x77, 0, 0, 0, 0, 0]); assert!(short_cs.is_err()); From 50a37f415e36ae82ca741a14bd97d8b8d76a0374 Mon Sep 17 00:00:00 2001 From: Steven Roose Date: Tue, 3 Dec 2019 22:20:45 +0000 Subject: [PATCH 03/11] Implement From and From<&'static str> for CommandString --- src/network/message.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/network/message.rs b/src/network/message.rs index 36fa62f3..cd123028 100644 --- a/src/network/message.rs +++ b/src/network/message.rs @@ -43,6 +43,18 @@ impl fmt::Display for CommandString { } } +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 Encodable for CommandString { #[inline] fn consensus_encode( @@ -320,7 +332,7 @@ mod test { let cs: Result = deserialize(&[0x41u8, 0x6e, 0x64, 0x72, 0x65, 0x77, 0, 0, 0, 0, 0, 0]); assert!(cs.is_ok()); assert_eq!(cs.as_ref().unwrap().to_string(), "Andrew".to_owned()); - assert_eq!(cs.unwrap(), CommandString("Andrew".into())); + 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()); From e2eed78964dfd34322236c26a2b4b7edc444ca3e Mon Sep 17 00:00:00 2001 From: Steven Roose Date: Tue, 3 Dec 2019 22:13:16 +0000 Subject: [PATCH 04/11] nit: Reject is implemented --- src/network/message.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/network/message.rs b/src/network/message.rs index cd123028..c8a5f4cd 100644 --- a/src/network/message.rs +++ b/src/network/message.rs @@ -135,7 +135,6 @@ pub enum NetworkMessage { Ping(u64), /// `pong` Pong(u64), - // TODO: reject, // TODO: bloom filtering /// BIP157 getcfilters GetCFilters(message_filter::GetCFilters), From e37fdb73196a318774ca34b9abefe76426b45cea Mon Sep 17 00:00:00 2001 From: Steven Roose Date: Tue, 3 Dec 2019 22:27:19 +0000 Subject: [PATCH 05/11] Also have getter for CommandString --- src/network/message.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/network/message.rs b/src/network/message.rs index c8a5f4cd..da252c57 100644 --- a/src/network/message.rs +++ b/src/network/message.rs @@ -156,7 +156,7 @@ pub enum NetworkMessage { impl NetworkMessage { /// Return the message command. This is useful for debug outputs. - pub fn command(&self) -> &'static str { + pub fn cmd(&self) -> &'static str { match *self { NetworkMessage::Version(_) => "version", NetworkMessage::Verack => "verack", @@ -184,11 +184,21 @@ impl NetworkMessage { NetworkMessage::Reject(_) => "reject", } } + + /// 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 command(&self) -> &'static str { + pub fn cmd(&self) -> &'static str { + self.payload.cmd() + } + + /// Return the CommandString for the message command. + pub fn command(&self) -> CommandString { self.payload.command() } } @@ -218,7 +228,7 @@ impl Encodable for RawNetworkMessage { ) -> Result { let mut len = 0; len += self.magic.consensus_encode(&mut s)?; - len += CommandString(self.command().into()).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), From c30d6d12ab64e758c0b69447f4695618777c4350 Mon Sep 17 00:00:00 2001 From: Steven Roose Date: Tue, 3 Dec 2019 22:36:27 +0000 Subject: [PATCH 06/11] Implement Encodable for Cow<'static, str> --- src/consensus/encode.rs | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/consensus/encode.rs b/src/consensus/encode.rs index 63a91227..a3cbb320 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; @@ -518,6 +515,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 { @@ -929,6 +946,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] From 5c84e9671f30cbd1ffa35559fa605a407139444c Mon Sep 17 00:00:00 2001 From: Steven Roose Date: Tue, 3 Dec 2019 22:39:04 +0000 Subject: [PATCH 07/11] Optimize Reject message --- src/network/message.rs | 3 +-- src/network/message_network.rs | 9 ++++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/network/message.rs b/src/network/message.rs index da252c57..cb4674be 100644 --- a/src/network/message.rs +++ b/src/network/message.rs @@ -61,9 +61,8 @@ 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"); } diff --git a/src/network/message_network.rs b/src/network/message_network.rs index 45056507..4eb22f98 100644 --- a/src/network/message_network.rs +++ b/src/network/message_network.rs @@ -18,11 +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::CommandString; use network::message_network::RejectReason::{MALFORMED, INVALID, OBSOLETE, DUPLICATE, NONSTANDARD, DUST, CHECKPOINT, FEE}; use hashes::sha256d; @@ -131,11 +134,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 } From 83f55b7f1d7739bf7c8816844a225ffb58e8a5c9 Mon Sep 17 00:00:00 2001 From: Steven Roose Date: Tue, 3 Dec 2019 22:42:09 +0000 Subject: [PATCH 08/11] Follow Rust std practice for RejectReason enum --- src/network/message_network.rs | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/src/network/message_network.rs b/src/network/message_network.rs index 4eb22f98..c3e5eab4 100644 --- a/src/network/message_network.rs +++ b/src/network/message_network.rs @@ -26,7 +26,6 @@ use network::constants::{self, ServiceFlags}; use consensus::{Encodable, Decodable, ReadExt}; use consensus::encode; use network::message::CommandString; -use network::message_network::RejectReason::{MALFORMED, INVALID, OBSOLETE, DUPLICATE, NONSTANDARD, DUST, CHECKPOINT, FEE}; use hashes::sha256d; /// Some simple messages @@ -90,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 { @@ -117,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")) }) } From 671b3173c8ec77522fbc21a6916ebe81922edf81 Mon Sep 17 00:00:00 2001 From: Steven Roose Date: Tue, 3 Dec 2019 22:44:42 +0000 Subject: [PATCH 09/11] Make internals for CommandString private The From traits and AsRef and Display implementations let you do all you want. --- src/network/message.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/network/message.rs b/src/network/message.rs index cb4674be..fc421476 100644 --- a/src/network/message.rs +++ b/src/network/message.rs @@ -35,7 +35,7 @@ use consensus::encode::MAX_VEC_SIZE; /// Serializer for command string #[derive(PartialEq, Eq, Clone, Debug)] -pub struct CommandString(pub Cow<'static, str>); +pub struct CommandString(Cow<'static, str>); impl fmt::Display for CommandString { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -55,6 +55,12 @@ impl From for CommandString { } } +impl AsRef for CommandString { + fn as_ref(&self) -> &str { + self.0.as_ref() + } +} + impl Encodable for CommandString { #[inline] fn consensus_encode( From fe3397399e1a3e07f053725873323c88cc99bc5d Mon Sep 17 00:00:00 2001 From: Steven Roose Date: Sun, 8 Dec 2019 20:56:18 +0000 Subject: [PATCH 10/11] Add Copy to InvType enum --- src/network/message_blockdata.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/network/message_blockdata.rs b/src/network/message_blockdata.rs index c484bc78..e048895f 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 From a8f14af24d28bb0fbfb0e25b2f4e9ba91ab3e237 Mon Sep 17 00:00:00 2001 From: Steven Roose Date: Tue, 10 Dec 2019 17:22:55 +0000 Subject: [PATCH 11/11] Prevent panic on oversized CommandString's --- src/network/message.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/network/message.rs b/src/network/message.rs index fc421476..85163673 100644 --- a/src/network/message.rs +++ b/src/network/message.rs @@ -70,7 +70,7 @@ impl Encodable for CommandString { let mut rawbytes = [0u8; 12]; 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]; @@ -332,13 +332,18 @@ 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".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]