|
5 | 5 | //! This module defines the `NetworkMessage` and `RawNetworkMessage` types that |
6 | 6 | //! are used for (de)serializing Bitcoin objects for transmission on the network. |
7 | 7 |
|
8 | | -use core::{fmt, iter}; |
| 8 | +use core::fmt; |
9 | 9 | use std::borrow::{Cow, ToOwned}; |
10 | 10 | use std::boxed::Box; |
11 | 11 |
|
@@ -114,14 +114,15 @@ impl Decodable for CommandString { |
114 | 114 | #[inline] |
115 | 115 | fn consensus_decode<R: BufRead + ?Sized>(r: &mut R) -> Result<Self, encode::Error> { |
116 | 116 | let rawbytes: [u8; 12] = Decodable::consensus_decode(r)?; |
117 | | - let rv = iter::FromIterator::from_iter(rawbytes.iter().filter_map(|&u| { |
118 | | - if u > 0 { |
119 | | - Some(u as char) |
120 | | - } else { |
121 | | - None |
122 | | - } |
123 | | - })); |
124 | | - Ok(CommandString(rv)) |
| 117 | + |
| 118 | + // Find the last non-null byte and trim null padding from the end |
| 119 | + let trimmed = &rawbytes[..rawbytes.iter().rposition(|&b| b != 0).map_or(0, |i| i + 1)]; |
| 120 | + |
| 121 | + if !trimmed.is_ascii() { |
| 122 | + return Err(crate::consensus::parse_failed_error("Command string must be ASCII")); |
| 123 | + } |
| 124 | + |
| 125 | + Ok(CommandString(Cow::Owned(unsafe { String::from_utf8_unchecked(trimmed.to_vec()) }))) |
125 | 126 | } |
126 | 127 | } |
127 | 128 |
|
@@ -892,9 +893,18 @@ mod test { |
892 | 893 | assert_eq!(cs.as_ref().unwrap().to_string(), "Andrew".to_owned()); |
893 | 894 | assert_eq!(cs.unwrap(), CommandString::try_from_static("Andrew").unwrap()); |
894 | 895 |
|
895 | | - let short_cs: Result<CommandString, _> = |
896 | | - deserialize(&[0x41u8, 0x6e, 0x64, 0x72, 0x65, 0x77, 0, 0, 0, 0, 0]); |
897 | | - assert!(short_cs.is_err()); |
| 896 | + // Test that embedded null bytes are preserved while trailing nulls are trimmed |
| 897 | + let cs: Result<CommandString, _> = |
| 898 | + deserialize(&[0, 0x41u8, 0x6e, 0x64, 0, 0x72, 0x65, 0x77, 0, 0, 0, 0]); |
| 899 | + assert!(cs.is_ok()); |
| 900 | + assert_eq!(cs.as_ref().unwrap().to_string(), "\0And\0rew".to_owned()); |
| 901 | + assert_eq!(cs.unwrap(), CommandString::try_from_static("\0And\0rew").unwrap()); |
| 902 | + |
| 903 | + // Invalid CommandString, must be ASCII |
| 904 | + assert!(deserialize::<CommandString>(&[0, 0x41u8, 0x6e, 0xa4, 0, 0x72, 0x65, 0x77, 0, 0, 0, 0]).is_err()); |
| 905 | + |
| 906 | + // Invalid CommandString, must be 12 bytes |
| 907 | + assert!(deserialize::<CommandString>(&[0x41u8, 0x6e, 0x64, 0x72, 0x65, 0x77, 0, 0, 0, 0, 0]).is_err()); |
898 | 908 | } |
899 | 909 |
|
900 | 910 | #[test] |
|
0 commit comments