p2p: Implement `encoding` traits for `UserAgent`
What changed, and why it matters
This commit adds new serialization/deserialization code for the Bitcoin P2P `UserAgent` field. It is a routine refactor that introduces a new internal encoding trait implementation alongside the existing one. There is no indication of a security bug in the change itself.
No security action required. Treat as normal code review for a refactor.
Security signals we found
No security-relevant signals in the diff or commit message.
UTF-8 validation is performed during decoding, which is correct behavior.
The change is additive and does not remove or alter existing consensus encoding logic.
Evidence from the diff
The patch implements the project’s newer encoding::{Encodable, Decodable} traits for UserAgent while keeping the existing impl_consensus_encoding! macro-based consensus encoding. It adds UserAgentEncoder, UserAgentDecoder, and UserAgentDecoderError, with UTF-8 validation during decoding. The change is additive and preparatory for a larger VersionMessage refactor.
Changed components
rust-bitcoin p2p cratep2p/src/message_network.rsUserAgent encoding/decodingInspect captured patch +87 / −0
diff --git a/p2p/src/message_network.rs b/p2p/src/message_network.rs
index 7ff700f1..ac79ed48 100644
--- a/p2p/src/message_network.rs
+++ b/p2p/src/message_network.rs
@@ -9,11 +9,15 @@ use alloc::borrow::Cow;
use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
+use core::convert::Infallible;
+use core::fmt;
#[cfg(feature = "arbitrary")]
use arbitrary::{Arbitrary, Unstructured};
use bitcoin::consensus::{encode, Decodable, Encodable, ReadExt, WriteExt};
+use encoding::{ByteVecDecoder, BytesEncoder, CompactSizeEncoder, Encoder2};
use hashes::sha256d;
+use internals::write_err;
use io::{BufRead, Write};
use crate::address::Address;
@@ -103,6 +107,89 @@ pub struct UserAgent {
user_agent: String,
}
+encoding::encoder_newtype! {
+ /// The encoder for a [`UserAgent`] string.
+ pub struct UserAgentEncoder<'e>(Encoder2<CompactSizeEncoder, BytesEncoder<'e>>);
+}
+
+impl encoding::Encodable for UserAgent {
+ type Encoder<'e> = UserAgentEncoder<'e>;
+
+ fn encoder(&self) -> Self::Encoder<'_> {
+ UserAgentEncoder(
+ Encoder2::new(
+ CompactSizeEncoder::new(self.user_agent.len()),
+ BytesEncoder::without_length_prefix(self.user_agent.as_bytes())
+ )
+ )
+ }
+}
+
+type UserAgentInnerDecoder = ByteVecDecoder;
+
+/// The decoder for the [`UserAgent`] message.
+pub struct UserAgentDecoder(UserAgentInnerDecoder);
+
+impl encoding::Decoder for UserAgentDecoder {
+ type Output = UserAgent;
+ type Error = UserAgentDecoderError;
+
+ #[inline]
+ fn push_bytes(&mut self, bytes: &mut &[u8]) -> Result<bool, Self::Error> {
+ self.0.push_bytes(bytes).map_err(UserAgentDecoderError::Decoder)
+ }
+
+ #[inline]
+ fn end(self) -> Result<Self::Output, Self::Error> {
+ let bytes = self.0.end().map_err(UserAgentDecoderError::Decoder)?;
+ let user_agent = String::from_utf8(bytes).map_err(|_| UserAgentDecoderError::InvalidUtf8)?;
+ Ok(UserAgent { user_agent })
+ }
+
+ #[inline]
+ fn read_limit(&self) -> usize { self.0.read_limit() }
+}
+
+impl encoding::Decodable for UserAgent {
+ type Decoder = UserAgentDecoder;
+
+ fn decoder() -> Self::Decoder {
+ UserAgentDecoder(UserAgentInnerDecoder::new())
+ }
+}
+
+/// An error decoding a [`UserAgent`] message.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum UserAgentDecoderError {
+ /// Inner decoder error.
+ Decoder(<UserAgentInnerDecoder as encoding::Decoder>::Error),
+ /// The string did not contain valid UTF-8.
+ InvalidUtf8,
+}
+
+impl From<Infallible> for UserAgentDecoderError {
+ fn from(never: Infallible) -> Self { match never {} }
+}
+
+impl fmt::Display for UserAgentDecoderError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Decoder(d) => write_err!(f, "useragent error"; d),
+ Self::InvalidUtf8 => write!(f, "invalid utf-8."),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for UserAgentDecoderError {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ match self {
+ Self::Decoder(d) => Some(d),
+ Self::InvalidUtf8 => None,
+ }
+ }
+}
+
impl_consensus_encoding!(UserAgent, user_agent);
impl UserAgent {
Why this scored 18/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.