Skip to main content

dig_pex/
error.rs

1//! The PEX error code space (SPEC §4.5) — the `code` carried by a [`pex_error`](crate::PexMessage)
2//! message, and the reasons an inbound peer entry is skipped by receiver-side validation (SPEC §3.3).
3
4/// The advisory `pex_error` code table (SPEC §4.5). `pex_error` is best-effort and never requires a
5/// reply; a receiver sends it alongside discarding a bad message / muting a direction.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7#[repr(u16)]
8pub enum PexErrorCode {
9    /// `1` — the message was not valid PEX JSON, or violated a structural MUST (e.g. a `peer_id`
10    /// appearing in both `added` and `dropped`).
11    BadMessage = 1,
12    /// `2` — the handshake `version` is not supported by the receiver.
13    UnsupportedVersion = 2,
14    /// `3` — data messages arrived faster than the enforced minimum interval (SPEC §6.4).
15    RateViolation = 3,
16    /// `4` — a frame exceeded `PEX_MAX_FRAME`, or a list exceeded its cap (SPEC §7).
17    Oversized = 4,
18    /// `5` — the handshake `network_id` differs from the receiver's.
19    NetworkMismatch = 5,
20    /// `6` — a state-machine violation (SPEC §5.3): data before handshake, a second snapshot, or a
21    /// delta before the snapshot.
22    ProtocolViolation = 6,
23}
24
25impl PexErrorCode {
26    /// The numeric code as it appears on the wire.
27    #[must_use]
28    pub fn as_u16(self) -> u16 {
29        self as u16
30    }
31
32    /// A short, stable human-readable message for the `pex_error.message` field.
33    #[must_use]
34    pub fn message(self) -> &'static str {
35        match self {
36            PexErrorCode::BadMessage => "bad message",
37            PexErrorCode::UnsupportedVersion => "unsupported version",
38            PexErrorCode::RateViolation => "rate violation",
39            PexErrorCode::Oversized => "oversized",
40            PexErrorCode::NetworkMismatch => "network mismatch",
41            PexErrorCode::ProtocolViolation => "protocol violation",
42        }
43    }
44
45    /// Whether this code represents peer **misbehavior** that counts a strike toward muting (SPEC
46    /// §11.2): bad-message (`1`), rate (`3`), oversize (`4`), and protocol (`6`). Version (`2`) and
47    /// network (`5`) mismatch mute the direction immediately but are NOT misbehavior — the peer is
48    /// simply on a different version/network, and the underlying connection MUST NOT be torn down
49    /// for that reason alone (SPEC §5.2).
50    #[must_use]
51    pub fn is_strike(self) -> bool {
52        matches!(
53            self,
54            PexErrorCode::BadMessage
55                | PexErrorCode::RateViolation
56                | PexErrorCode::Oversized
57                | PexErrorCode::ProtocolViolation
58        )
59    }
60}
61
62/// Why a single inbound peer entry was skipped by receiver-side validation (SPEC §3.3). Skipping is
63/// silent — no `pex_error`, no strike; entry-level junk is expected from honest-but-stale peers.
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub enum EntrySkip {
66    /// `peer_id` is not exactly 64 lowercase hex characters.
67    BadPeerId,
68    /// `peer_id` equals the receiver's own or the sender's `peer_id` (SPEC §5.4).
69    SelfOrPartner,
70    /// An address has an empty `host`, a `port` of 0, or an unrecognized `kind` token.
71    BadAddress,
72    /// `addresses` has more than `PEX_MAX_ADDRESSES` elements.
73    TooManyAddresses,
74    /// `flags` has more than `PEX_MAX_FLAGS` elements (or a token over `PEX_MAX_FLAG_LEN` chars).
75    TooManyFlags,
76    /// `network_id` differs from the link's network.
77    NetworkMismatch,
78    /// `via` is not one of the three registered provenance tokens.
79    BadVia,
80    /// `last_seen` is more than `PEX_MAX_ENTRY_AGE` seconds in the past by the receiver's clock.
81    TooOld,
82    /// The `payment` claim exceeded one of its field caps (SPEC §3.4.2). Note this is a *size*
83    /// verdict only: an entry whose claim is merely unverifiable is kept and stays dialable — it is
84    /// simply unpayable (SPEC §3.4.3).
85    OversizePayment,
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    #[test]
93    fn codes_match_spec_table() {
94        assert_eq!(PexErrorCode::BadMessage.as_u16(), 1);
95        assert_eq!(PexErrorCode::UnsupportedVersion.as_u16(), 2);
96        assert_eq!(PexErrorCode::RateViolation.as_u16(), 3);
97        assert_eq!(PexErrorCode::Oversized.as_u16(), 4);
98        assert_eq!(PexErrorCode::NetworkMismatch.as_u16(), 5);
99        assert_eq!(PexErrorCode::ProtocolViolation.as_u16(), 6);
100    }
101
102    #[test]
103    fn strike_classification() {
104        assert!(PexErrorCode::BadMessage.is_strike());
105        assert!(PexErrorCode::RateViolation.is_strike());
106        assert!(PexErrorCode::Oversized.is_strike());
107        assert!(PexErrorCode::ProtocolViolation.is_strike());
108        assert!(!PexErrorCode::UnsupportedVersion.is_strike());
109        assert!(!PexErrorCode::NetworkMismatch.is_strike());
110    }
111
112    #[test]
113    fn messages_are_stable() {
114        assert_eq!(PexErrorCode::RateViolation.message(), "rate violation");
115    }
116}