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}