1//! `RLPx` disconnect reason sent to/received from peer
23use alloc::vec;
4use alloy_primitives::bytes::{Buf, BufMut};
5use alloy_rlp::{Decodable, Encodable, Header};
6use derive_more::Display;
7use reth_codecs_derive::add_arbitrary_tests;
8use thiserror::Error;
910/// RLPx disconnect reason.
11#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Display)]
12#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
13#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
14#[add_arbitrary_tests(rlp)]
15pub enum DisconnectReason {
16/// Disconnect requested by the local node or remote peer.
17#[default]
18 #[display("disconnect requested")]
19DisconnectRequested = 0x00,
20/// TCP related error
21#[display("TCP sub-system error")]
22TcpSubsystemError = 0x01,
23/// Breach of protocol at the transport or p2p level
24#[display("breach of protocol, e.g. a malformed message, bad RLP, etc.")]
25ProtocolBreach = 0x02,
26/// Node has no matching protocols.
27#[display("useless peer")]
28UselessPeer = 0x03,
29/// Either the remote or local node has too many peers.
30#[display("too many peers")]
31TooManyPeers = 0x04,
32/// Already connected to the peer.
33#[display("already connected")]
34AlreadyConnected = 0x05,
35/// `p2p` protocol version is incompatible
36#[display("incompatible P2P protocol version")]
37IncompatibleP2PProtocolVersion = 0x06,
38/// Received a null node identity.
39#[display("null node identity received - this is automatically invalid")]
40NullNodeIdentity = 0x07,
41/// Reason when the client is shutting down.
42#[display("client quitting")]
43ClientQuitting = 0x08,
44/// When the received handshake's identify is different from what is expected.
45#[display("unexpected identity in handshake")]
46UnexpectedHandshakeIdentity = 0x09,
47/// The node is connected to itself
48#[display("identity is the same as this node (i.e. connected to itself)")]
49ConnectedToSelf = 0x0a,
50/// Peer or local node did not respond to a ping in time.
51#[display("ping timeout")]
52PingTimeout = 0x0b,
53/// Peer or local node violated a subprotocol-specific rule.
54#[display("some other reason specific to a subprotocol")]
55SubprotocolSpecific = 0x10,
56}
5758impl TryFrom<u8> for DisconnectReason {
59// This error type should not be used to crash the node, but rather to log the error and
60 // disconnect the peer.
61type Error = UnknownDisconnectReason;
6263fn try_from(value: u8) -> Result<Self, Self::Error> {
64match value {
650x00 => Ok(Self::DisconnectRequested),
660x01 => Ok(Self::TcpSubsystemError),
670x02 => Ok(Self::ProtocolBreach),
680x03 => Ok(Self::UselessPeer),
690x04 => Ok(Self::TooManyPeers),
700x05 => Ok(Self::AlreadyConnected),
710x06 => Ok(Self::IncompatibleP2PProtocolVersion),
720x07 => Ok(Self::NullNodeIdentity),
730x08 => Ok(Self::ClientQuitting),
740x09 => Ok(Self::UnexpectedHandshakeIdentity),
750x0a => Ok(Self::ConnectedToSelf),
760x0b => Ok(Self::PingTimeout),
770x10 => Ok(Self::SubprotocolSpecific),
78_ => Err(UnknownDisconnectReason(value)),
79 }
80 }
81}
8283impl Encodable for DisconnectReason {
84/// The [`Encodable`] implementation for [`DisconnectReason`] encodes the disconnect reason in
85 /// a single-element RLP list.
86fn encode(&self, out: &mut dyn BufMut) {
87vec![*self as u8].encode(out);
88 }
89fn length(&self) -> usize {
90vec![*self as u8].length()
91 }
92}
9394impl Decodable for DisconnectReason {
95/// The [`Decodable`] implementation for [`DisconnectReason`] supports either a disconnect
96 /// reason encoded a single byte or a RLP list containing the disconnect reason.
97fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
98if buf.is_empty() {
99return Err(alloy_rlp::Error::InputTooShort)
100 } else if buf.len() > 2 {
101return Err(alloy_rlp::Error::Overflow)
102 }
103104if buf.len() > 1 {
105// this should be a list, so decode the list header. this should advance the buffer so
106 // buf[0] is the first (and only) element of the list.
107let header = Header::decode(buf)?;
108109if !header.list {
110return Err(alloy_rlp::Error::UnexpectedString)
111 }
112113if header.payload_length != 1 {
114return Err(alloy_rlp::Error::ListLengthMismatch {
115 expected: 1,
116 got: header.payload_length,
117 })
118 }
119 }
120121// geth rlp encodes [`DisconnectReason::DisconnectRequested`] as 0x00 and not as empty
122 // string 0x80
123if buf[0] == 0x00 {
124buf.advance(1);
125Ok(Self::DisconnectRequested)
126 } else {
127Self::try_from(u8::decode(buf)?)
128 .map_err(|_| alloy_rlp::Error::Custom("unknown disconnect reason"))
129 }
130 }
131}
132133/// This represents an unknown disconnect reason with the given code.
134#[derive(Debug, Clone, Error)]
135#[error("unknown disconnect reason: {0}")]
136pub struct UnknownDisconnectReason(u8);