1use crate::alloc::string::ToString;
4use alloc::string::String;
5use alloy_rlp::{Decodable, Encodable, Error as RlpError};
6use bytes::BufMut;
7use core::{fmt, str::FromStr};
8use derive_more::Display;
9use reth_codecs_derive::add_arbitrary_tests;
10
11#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
13#[error("Unknown eth protocol version: {0}")]
14pub struct ParseVersionError(String);
15
16#[repr(u8)]
18#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Display)]
19#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
20#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
21pub enum EthVersion {
22 Eth66 = 66,
24 Eth67 = 67,
26 Eth68 = 68,
28 Eth69 = 69,
30}
31
32impl EthVersion {
33 pub const LATEST: Self = Self::Eth68;
35
36 pub const ALL_VERSIONS: &'static [Self] = &[Self::Eth69, Self::Eth68, Self::Eth67, Self::Eth66];
38
39 pub const fn is_eth66(&self) -> bool {
41 matches!(self, Self::Eth66)
42 }
43
44 pub const fn is_eth67(&self) -> bool {
46 matches!(self, Self::Eth67)
47 }
48
49 pub const fn is_eth68(&self) -> bool {
51 matches!(self, Self::Eth68)
52 }
53
54 pub const fn is_eth69(&self) -> bool {
56 matches!(self, Self::Eth69)
57 }
58}
59
60impl Encodable for EthVersion {
62 fn encode(&self, out: &mut dyn BufMut) {
63 (*self as u8).encode(out)
64 }
65
66 fn length(&self) -> usize {
67 (*self as u8).length()
68 }
69}
70
71impl Decodable for EthVersion {
74 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
75 let version = u8::decode(buf)?;
76 Self::try_from(version).map_err(|_| RlpError::Custom("invalid eth version"))
77 }
78}
79
80impl TryFrom<&str> for EthVersion {
90 type Error = ParseVersionError;
91
92 #[inline]
93 fn try_from(s: &str) -> Result<Self, Self::Error> {
94 match s {
95 "66" => Ok(Self::Eth66),
96 "67" => Ok(Self::Eth67),
97 "68" => Ok(Self::Eth68),
98 "69" => Ok(Self::Eth69),
99 _ => Err(ParseVersionError(s.to_string())),
100 }
101 }
102}
103
104impl TryFrom<u8> for EthVersion {
114 type Error = ParseVersionError;
115
116 #[inline]
117 fn try_from(u: u8) -> Result<Self, Self::Error> {
118 match u {
119 66 => Ok(Self::Eth66),
120 67 => Ok(Self::Eth67),
121 68 => Ok(Self::Eth68),
122 69 => Ok(Self::Eth69),
123 _ => Err(ParseVersionError(u.to_string())),
124 }
125 }
126}
127
128impl FromStr for EthVersion {
129 type Err = ParseVersionError;
130
131 #[inline]
132 fn from_str(s: &str) -> Result<Self, Self::Err> {
133 Self::try_from(s)
134 }
135}
136
137impl From<EthVersion> for u8 {
138 #[inline]
139 fn from(v: EthVersion) -> Self {
140 v as Self
141 }
142}
143
144impl From<EthVersion> for &'static str {
145 #[inline]
146 fn from(v: EthVersion) -> &'static str {
147 match v {
148 EthVersion::Eth66 => "66",
149 EthVersion::Eth67 => "67",
150 EthVersion::Eth68 => "68",
151 EthVersion::Eth69 => "69",
152 }
153 }
154}
155
156#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
158#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
159#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
160#[add_arbitrary_tests(rlp)]
161pub enum ProtocolVersion {
162 V4 = 4,
164 #[default]
166 V5 = 5,
167}
168
169impl fmt::Display for ProtocolVersion {
170 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171 write!(f, "v{}", *self as u8)
172 }
173}
174
175impl Encodable for ProtocolVersion {
176 fn encode(&self, out: &mut dyn BufMut) {
177 (*self as u8).encode(out)
178 }
179 fn length(&self) -> usize {
180 (*self as u8).length()
182 }
183}
184
185impl Decodable for ProtocolVersion {
186 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
187 let version = u8::decode(buf)?;
188 match version {
189 4 => Ok(Self::V4),
190 5 => Ok(Self::V5),
191 _ => Err(RlpError::Custom("unknown p2p protocol version")),
192 }
193 }
194}
195
196#[cfg(test)]
197mod tests {
198 use super::{EthVersion, ParseVersionError};
199 use alloy_rlp::{Decodable, Encodable, Error as RlpError};
200 use bytes::BytesMut;
201
202 #[test]
203 fn test_eth_version_try_from_str() {
204 assert_eq!(EthVersion::Eth66, EthVersion::try_from("66").unwrap());
205 assert_eq!(EthVersion::Eth67, EthVersion::try_from("67").unwrap());
206 assert_eq!(EthVersion::Eth68, EthVersion::try_from("68").unwrap());
207 assert_eq!(EthVersion::Eth69, EthVersion::try_from("69").unwrap());
208 assert_eq!(Err(ParseVersionError("70".to_string())), EthVersion::try_from("70"));
209 }
210
211 #[test]
212 fn test_eth_version_from_str() {
213 assert_eq!(EthVersion::Eth66, "66".parse().unwrap());
214 assert_eq!(EthVersion::Eth67, "67".parse().unwrap());
215 assert_eq!(EthVersion::Eth68, "68".parse().unwrap());
216 assert_eq!(EthVersion::Eth69, "69".parse().unwrap());
217 assert_eq!(Err(ParseVersionError("70".to_string())), "70".parse::<EthVersion>());
218 }
219
220 #[test]
221 fn test_eth_version_rlp_encode() {
222 let versions = [EthVersion::Eth66, EthVersion::Eth67, EthVersion::Eth68, EthVersion::Eth69];
223
224 for version in versions {
225 let mut encoded = BytesMut::new();
226 version.encode(&mut encoded);
227
228 assert_eq!(encoded.len(), 1);
229 assert_eq!(encoded[0], version as u8);
230 }
231 }
232 #[test]
233 fn test_eth_version_rlp_decode() {
234 let test_cases = [
235 (66_u8, Ok(EthVersion::Eth66)),
236 (67_u8, Ok(EthVersion::Eth67)),
237 (68_u8, Ok(EthVersion::Eth68)),
238 (69_u8, Ok(EthVersion::Eth69)),
239 (70_u8, Err(RlpError::Custom("invalid eth version"))),
240 (65_u8, Err(RlpError::Custom("invalid eth version"))),
241 ];
242
243 for (input, expected) in test_cases {
244 let mut encoded = BytesMut::new();
245 input.encode(&mut encoded);
246
247 let mut slice = encoded.as_ref();
248 let result = EthVersion::decode(&mut slice);
249 assert_eq!(result, expected);
250 }
251 }
252}