Skip to main content

reth_eth_wire_types/
disconnect_reason.rs

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