1use crate::{
4 errors::{P2PHandshakeError, P2PStreamError},
5 p2pstream::MAX_RESERVED_MESSAGE_ID,
6 protocol::{ProtoVersion, Protocol},
7 version::ParseVersionError,
8 Capability, EthMessageID, EthVersion,
9};
10use derive_more::{Deref, DerefMut};
11use std::{
12 borrow::Cow,
13 collections::{BTreeSet, HashMap},
14};
15
16#[derive(Debug, Clone, PartialEq, Eq, Hash)]
23pub enum SharedCapability {
24 Eth {
26 version: EthVersion,
28 offset: u8,
33 },
34 UnknownCapability {
36 cap: Capability,
38 offset: u8,
43 messages: u8,
46 },
47}
48
49impl SharedCapability {
50 pub(crate) fn new(
55 name: &str,
56 version: u8,
57 offset: u8,
58 messages: u8,
59 ) -> Result<Self, SharedCapabilityError> {
60 if offset <= MAX_RESERVED_MESSAGE_ID {
61 return Err(SharedCapabilityError::ReservedMessageIdOffset(offset))
62 }
63
64 match name {
65 "eth" => Ok(Self::eth(EthVersion::try_from(version)?, offset)),
66 _ => Ok(Self::UnknownCapability {
67 cap: Capability::new(name.to_string(), version as usize),
68 offset,
69 messages,
70 }),
71 }
72 }
73
74 pub(crate) const fn eth(version: EthVersion, offset: u8) -> Self {
76 Self::Eth { version, offset }
77 }
78
79 pub const fn capability(&self) -> Cow<'_, Capability> {
81 match self {
82 Self::Eth { version, .. } => Cow::Owned(Capability::eth(*version)),
83 Self::UnknownCapability { cap, .. } => Cow::Borrowed(cap),
84 }
85 }
86
87 #[inline]
89 pub fn name(&self) -> &str {
90 match self {
91 Self::Eth { .. } => "eth",
92 Self::UnknownCapability { cap, .. } => cap.name.as_ref(),
93 }
94 }
95
96 #[inline]
98 pub const fn is_eth(&self) -> bool {
99 matches!(self, Self::Eth { .. })
100 }
101
102 pub const fn version(&self) -> u8 {
104 match self {
105 Self::Eth { version, .. } => *version as u8,
106 Self::UnknownCapability { cap, .. } => cap.version as u8,
107 }
108 }
109
110 pub const fn eth_version(&self) -> Option<EthVersion> {
112 match self {
113 Self::Eth { version, .. } => Some(*version),
114 _ => None,
115 }
116 }
117
118 pub const fn message_id_offset(&self) -> u8 {
123 match self {
124 Self::Eth { offset, .. } | Self::UnknownCapability { offset, .. } => *offset,
125 }
126 }
127
128 pub const fn relative_message_id_offset(&self) -> u8 {
131 self.message_id_offset() - MAX_RESERVED_MESSAGE_ID - 1
132 }
133
134 pub const fn num_messages(&self) -> u8 {
136 match self {
137 Self::Eth { version, .. } => EthMessageID::message_count(*version),
138 Self::UnknownCapability { messages, .. } => *messages,
139 }
140 }
141}
142
143#[derive(Debug, Clone, Deref, DerefMut, PartialEq, Eq)]
147pub struct SharedCapabilities(Vec<SharedCapability>);
148
149impl SharedCapabilities {
150 #[inline]
152 pub fn try_new(
153 local_protocols: Vec<Protocol>,
154 peer_capabilities: Vec<Capability>,
155 ) -> Result<Self, P2PStreamError> {
156 shared_capability_offsets(local_protocols, peer_capabilities).map(Self)
157 }
158
159 #[inline]
161 pub fn iter_caps(&self) -> impl Iterator<Item = &SharedCapability> {
162 self.0.iter()
163 }
164
165 #[inline]
167 pub fn eth(&self) -> Result<&SharedCapability, P2PStreamError> {
168 self.iter_caps().find(|c| c.is_eth()).ok_or(P2PStreamError::CapabilityNotShared)
169 }
170
171 #[inline]
173 pub fn eth_version(&self) -> Result<EthVersion, P2PStreamError> {
174 self.iter_caps()
175 .find_map(SharedCapability::eth_version)
176 .ok_or(P2PStreamError::CapabilityNotShared)
177 }
178
179 #[inline]
182 pub fn is_exact_eth_snap_v2(&self) -> bool {
183 self.len() == 2 && self.ensure_matching_capability(&Capability::snap_2()).is_ok()
184 }
185
186 #[inline]
188 pub fn contains(&self, cap: &Capability) -> bool {
189 self.find(cap).is_some()
190 }
191
192 #[inline]
194 pub fn find(&self, cap: &Capability) -> Option<&SharedCapability> {
195 self.0.iter().find(|c| c.version() == cap.version as u8 && c.name() == cap.name)
196 }
197
198 #[inline]
204 pub fn relative_message_id(&self, cap: &Capability, message_id: u8) -> Option<u8> {
205 let shared = self.find(cap)?;
206 if message_id >= shared.num_messages() {
207 return None
208 }
209
210 shared.relative_message_id_offset().checked_add(message_id)
211 }
212
213 #[inline]
218 pub fn capability_message_id(&self, cap: &Capability, relative_message_id: u8) -> Option<u8> {
219 let shared = self.find(cap)?;
220 let start = shared.relative_message_id_offset();
221 let end = start.checked_add(shared.num_messages())?;
222
223 (start..end).contains(&relative_message_id).then(|| relative_message_id - start)
224 }
225
226 #[inline]
235 pub fn find_by_relative_offset(&self, offset: u8) -> Option<&SharedCapability> {
236 self.find_by_offset(offset.saturating_add(MAX_RESERVED_MESSAGE_ID + 1))
237 }
238
239 #[inline]
247 pub fn find_by_offset(&self, offset: u8) -> Option<&SharedCapability> {
248 let mut iter = self.0.iter();
249 let mut cap = iter.next()?;
250 if offset < cap.message_id_offset() {
251 return None
253 }
254
255 for next in iter {
256 if offset < next.message_id_offset() {
257 return Some(cap)
258 }
259 cap = next
260 }
261
262 let end = u16::from(cap.message_id_offset()) + u16::from(cap.num_messages());
263 (u16::from(offset) < end).then_some(cap)
264 }
265
266 #[inline]
268 pub fn ensure_matching_capability(
269 &self,
270 cap: &Capability,
271 ) -> Result<&SharedCapability, UnsupportedCapabilityError> {
272 self.find(cap).ok_or_else(|| UnsupportedCapabilityError { capability: cap.clone() })
273 }
274
275 #[inline]
277 pub const fn len(&self) -> usize {
278 self.0.len()
279 }
280
281 #[inline]
283 pub const fn is_empty(&self) -> bool {
284 self.0.is_empty()
285 }
286}
287
288#[inline]
297pub fn shared_capability_offsets(
298 local_protocols: Vec<Protocol>,
299 peer_capabilities: Vec<Capability>,
300) -> Result<Vec<SharedCapability>, P2PStreamError> {
301 let our_capabilities =
303 local_protocols.into_iter().map(Protocol::split).collect::<HashMap<_, _>>();
304
305 let mut shared_capabilities: HashMap<_, ProtoVersion> = HashMap::default();
307
308 let mut shared_capability_names = BTreeSet::new();
320
321 for peer_capability in peer_capabilities {
323 if let Some(messages) = our_capabilities.get(&peer_capability).copied() {
325 if shared_capabilities
328 .get(&peer_capability.name)
329 .is_none_or(|v| peer_capability.version > v.version)
330 {
331 shared_capabilities.insert(
332 peer_capability.name.clone(),
333 ProtoVersion { version: peer_capability.version, messages },
334 );
335 shared_capability_names.insert(peer_capability.name);
336 }
337 }
338 }
339
340 if shared_capabilities.is_empty() {
342 return Err(P2PStreamError::HandshakeError(P2PHandshakeError::NoSharedCapabilities))
343 }
344
345 let mut shared_with_offsets = Vec::new();
348
349 let mut offset = u16::from(MAX_RESERVED_MESSAGE_ID) + 1;
356 for name in shared_capability_names {
357 let proto_version = &shared_capabilities[&name];
358 let start = u8::try_from(offset).map_err(|_| SharedCapabilityError::MessageIdOverflow)?;
359 let shared_capability = SharedCapability::new(
360 &name,
361 proto_version.version as u8,
362 start,
363 proto_version.messages,
364 )?;
365 offset += u16::from(shared_capability.num_messages());
366 if offset > u16::from(u8::MAX) + 1 {
367 return Err(SharedCapabilityError::MessageIdOverflow.into())
368 }
369 shared_with_offsets.push(shared_capability);
370 }
371
372 if shared_with_offsets.is_empty() {
373 return Err(P2PStreamError::HandshakeError(P2PHandshakeError::NoSharedCapabilities))
374 }
375
376 Ok(shared_with_offsets)
377}
378
379#[derive(Debug, thiserror::Error)]
381pub enum SharedCapabilityError {
382 #[error(transparent)]
384 UnsupportedVersion(#[from] ParseVersionError),
385 #[error("message id offset `{0}` is reserved")]
388 ReservedMessageIdOffset(u8),
389 #[error("shared capabilities exceed the message id space")]
392 MessageIdOverflow,
393}
394
395#[derive(Debug, thiserror::Error)]
397#[error("unsupported capability {capability}")]
398pub struct UnsupportedCapabilityError {
399 capability: Capability,
400}
401
402impl UnsupportedCapabilityError {
403 pub const fn new(capability: Capability) -> Self {
405 Self { capability }
406 }
407}
408
409#[cfg(test)]
410mod tests {
411 use super::*;
412 use crate::{Capabilities, Capability, SnapVersion};
413 use alloy_primitives::bytes::Bytes;
414 use alloy_rlp::{Decodable, Encodable};
415 use reth_eth_wire_types::RawCapabilityMessage;
416
417 #[test]
418 fn from_eth_68() {
419 let capability = SharedCapability::new("eth", 68, MAX_RESERVED_MESSAGE_ID + 1, 13).unwrap();
420
421 assert_eq!(capability.name(), "eth");
422 assert_eq!(capability.version(), 68);
423 assert_eq!(
424 capability,
425 SharedCapability::Eth {
426 version: EthVersion::Eth68,
427 offset: MAX_RESERVED_MESSAGE_ID + 1
428 }
429 );
430 }
431
432 #[test]
433 fn from_eth_67() {
434 let capability = SharedCapability::new("eth", 67, MAX_RESERVED_MESSAGE_ID + 1, 13).unwrap();
435
436 assert_eq!(capability.name(), "eth");
437 assert_eq!(capability.version(), 67);
438 assert_eq!(
439 capability,
440 SharedCapability::Eth {
441 version: EthVersion::Eth67,
442 offset: MAX_RESERVED_MESSAGE_ID + 1
443 }
444 );
445 }
446
447 #[test]
448 fn from_eth_66() {
449 let capability = SharedCapability::new("eth", 66, MAX_RESERVED_MESSAGE_ID + 1, 15).unwrap();
450
451 assert_eq!(capability.name(), "eth");
452 assert_eq!(capability.version(), 66);
453 assert_eq!(
454 capability,
455 SharedCapability::Eth {
456 version: EthVersion::Eth66,
457 offset: MAX_RESERVED_MESSAGE_ID + 1
458 }
459 );
460 }
461
462 #[test]
463 fn capabilities_supports_eth() {
464 let capabilities: Capabilities = vec![
465 Capability::new_static("eth", 66),
466 Capability::new_static("eth", 67),
467 Capability::new_static("eth", 68),
468 Capability::new_static("eth", 69),
469 Capability::new_static("eth", 70),
470 ]
471 .into();
472
473 assert!(capabilities.supports_eth());
474 assert!(capabilities.supports_eth_v66());
475 assert!(capabilities.supports_eth_v67());
476 assert!(capabilities.supports_eth_v68());
477 assert!(capabilities.supports_eth_v69());
478 assert!(capabilities.supports_eth_v70());
479 }
480
481 #[test]
482 fn lookup_rejects_message_ids_past_last_capability() {
483 let cap = Capability::new_static("test", 1);
484 let shared =
485 SharedCapabilities::try_new(vec![Protocol::new(cap.clone(), 1)], vec![cap.clone()])
486 .unwrap();
487 let offset = shared.find(&cap).unwrap().message_id_offset();
488
489 assert_eq!(shared.find_by_offset(offset).unwrap().capability().as_ref(), &cap);
490 assert!(shared.find_by_offset(offset + 1).is_none());
491 }
492
493 #[test]
494 fn test_peer_capability_version_zero() {
495 let cap = Capability::new_static("TestName", 0);
496 let local_capabilities: Vec<Protocol> =
497 vec![Protocol::new(cap.clone(), 0), EthVersion::Eth67.into(), EthVersion::Eth68.into()];
498 let peer_capabilities = vec![cap.clone()];
499
500 let shared = shared_capability_offsets(local_capabilities, peer_capabilities).unwrap();
501 assert_eq!(shared.len(), 1);
502 assert_eq!(shared[0], SharedCapability::UnknownCapability { cap, offset: 16, messages: 0 })
503 }
504
505 #[test]
506 fn test_peer_lower_capability_version() {
507 let local_capabilities: Vec<Protocol> =
508 vec![EthVersion::Eth66.into(), EthVersion::Eth67.into(), EthVersion::Eth68.into()];
509 let peer_capabilities: Vec<Capability> = vec![EthVersion::Eth66.into()];
510
511 let shared_capability =
512 shared_capability_offsets(local_capabilities, peer_capabilities).unwrap()[0].clone();
513
514 assert_eq!(
515 shared_capability,
516 SharedCapability::Eth {
517 version: EthVersion::Eth66,
518 offset: MAX_RESERVED_MESSAGE_ID + 1
519 }
520 )
521 }
522
523 #[test]
524 fn test_peer_capability_version_too_low() {
525 let local: Vec<Protocol> = vec![EthVersion::Eth67.into()];
526 let peer_capabilities: Vec<Capability> = vec![EthVersion::Eth66.into()];
527
528 let shared_capability = shared_capability_offsets(local, peer_capabilities);
529
530 assert!(matches!(
531 shared_capability,
532 Err(P2PStreamError::HandshakeError(P2PHandshakeError::NoSharedCapabilities))
533 ))
534 }
535
536 #[test]
537 fn test_peer_capability_version_too_high() {
538 let local_capabilities = vec![EthVersion::Eth66.into()];
539 let peer_capabilities = vec![EthVersion::Eth67.into()];
540
541 let shared_capability = shared_capability_offsets(local_capabilities, peer_capabilities);
542
543 assert!(matches!(
544 shared_capability,
545 Err(P2PStreamError::HandshakeError(P2PHandshakeError::NoSharedCapabilities))
546 ))
547 }
548
549 #[test]
550 fn test_find_by_offset() {
551 let local_capabilities = vec![EthVersion::Eth66.into()];
552 let peer_capabilities = vec![EthVersion::Eth66.into()];
553
554 let shared = SharedCapabilities::try_new(local_capabilities, peer_capabilities).unwrap();
555
556 let shared_eth = shared.find_by_relative_offset(0).unwrap();
557 assert_eq!(shared_eth.name(), "eth");
558
559 let shared_eth = shared.find_by_offset(MAX_RESERVED_MESSAGE_ID + 1).unwrap();
560 assert_eq!(shared_eth.name(), "eth");
561
562 assert!(shared.find_by_offset(MAX_RESERVED_MESSAGE_ID).is_none());
564 }
565
566 #[test]
567 fn test_find_by_offset_many() {
568 let cap = Capability::new_static("aaa", 1);
569 let proto = Protocol::new(cap.clone(), 5);
570 let local_capabilities = vec![proto.clone(), EthVersion::Eth66.into()];
571 let peer_capabilities = vec![cap, EthVersion::Eth66.into()];
572
573 let shared = SharedCapabilities::try_new(local_capabilities, peer_capabilities).unwrap();
574
575 let shared_eth = shared.find_by_relative_offset(0).unwrap();
576 assert_eq!(shared_eth.name(), proto.cap.name);
577
578 let shared_eth = shared.find_by_offset(MAX_RESERVED_MESSAGE_ID + 1).unwrap();
579 assert_eq!(shared_eth.name(), proto.cap.name);
580
581 let shared_eth = shared.find_by_relative_offset(4).unwrap();
583 assert_eq!(shared_eth.name(), proto.cap.name);
584 let shared_eth = shared.find_by_offset(MAX_RESERVED_MESSAGE_ID + 5).unwrap();
585 assert_eq!(shared_eth.name(), proto.cap.name);
586
587 let shared_eth = shared.find_by_relative_offset(1 + proto.messages()).unwrap();
589 assert_eq!(shared_eth.name(), "eth");
590 }
591
592 fn negotiate_all(
594 protocols: &[(&'static str, u8)],
595 ) -> Result<Vec<SharedCapability>, P2PStreamError> {
596 let local_protocols: Vec<_> = protocols
597 .iter()
598 .map(|&(name, messages)| Protocol::new(Capability::new_static(name, 1), messages))
599 .collect();
600 let peer_capabilities = local_protocols.iter().map(|p| p.cap.clone()).collect();
601 shared_capability_offsets(local_protocols, peer_capabilities)
602 }
603
604 #[test]
605 fn shared_capability_offsets_reject_last_capability_past_message_id_space() {
606 assert!(matches!(
608 negotiate_all(&[("aaa", 120), ("bbb", 121)]),
609 Err(P2PStreamError::ParseSharedCapability(SharedCapabilityError::MessageIdOverflow))
610 ));
611 }
612
613 #[test]
614 fn shared_capability_offsets_reject_capability_after_full_message_id_space() {
615 assert!(matches!(
617 negotiate_all(&[("aaa", 120), ("bbb", 120), ("ccc", 1)]),
618 Err(P2PStreamError::ParseSharedCapability(SharedCapabilityError::MessageIdOverflow))
619 ));
620 }
621
622 #[test]
623 fn shared_capability_offsets_fill_message_id_space() {
624 let shared = SharedCapabilities(negotiate_all(&[("aaa", 120), ("bbb", 120)]).unwrap());
626
627 assert_eq!(shared[1].message_id_offset(), MAX_RESERVED_MESSAGE_ID + 1 + 120);
628 assert_eq!(shared.find_by_offset(u8::MAX).unwrap().name(), "bbb");
629 }
630
631 #[test]
632 fn relative_message_id_accounts_for_intermediate_capabilities() {
633 let intermediate_cap = Capability::new_static("foo", 1);
634 let intermediate = Protocol::new(intermediate_cap.clone(), 3);
635 let snap = Capability::snap_2();
636 let eth = Capability::eth_69();
637 let local_capabilities = vec![EthVersion::Eth69.into(), intermediate, Protocol::snap_2()];
638 let peer_capabilities = vec![eth, intermediate_cap, snap.clone()];
639
640 let shared = SharedCapabilities::try_new(local_capabilities, peer_capabilities).unwrap();
641 let snap_id = shared.relative_message_id(&snap, 2).unwrap();
642
643 assert_eq!(snap_id, EthMessageID::message_count(EthVersion::Eth69) + 3 + 2);
644 assert_eq!(shared.capability_message_id(&snap, snap_id), Some(2));
645 }
646
647 #[test]
648 fn capability_message_id_rejects_other_capability_range() {
649 let intermediate_cap = Capability::new_static("foo", 1);
650 let intermediate = Protocol::new(intermediate_cap.clone(), 3);
651 let snap = Capability::snap_2();
652 let local_capabilities = vec![EthVersion::Eth69.into(), intermediate, Protocol::snap_2()];
653 let peer_capabilities = vec![Capability::eth_69(), intermediate_cap.clone(), snap.clone()];
654
655 let shared = SharedCapabilities::try_new(local_capabilities, peer_capabilities).unwrap();
656 let intermediate_id = shared.relative_message_id(&intermediate_cap, 1).unwrap();
657
658 assert_eq!(shared.capability_message_id(&snap, intermediate_id), None);
659 assert_eq!(shared.relative_message_id(&snap, SnapVersion::V2.message_count()), None);
660 }
661
662 #[test]
663 fn test_raw_capability_rlp() {
664 let msg = RawCapabilityMessage { id: 1, payload: Bytes::from(vec![0x01, 0x02, 0x03]) };
665
666 let mut encoded = Vec::new();
668 msg.encode(&mut encoded);
669
670 let decoded = RawCapabilityMessage::decode(&mut &encoded[..]).unwrap();
672
673 assert_eq!(msg, decoded);
675 }
676
677 #[test]
678 fn is_exact_eth_snap_v2_accepts_eth_and_snap() {
679 let shared = SharedCapabilities::try_new(
680 vec![EthVersion::Eth68.into(), Protocol::snap_2()],
681 vec![EthVersion::Eth68.into(), Capability::snap_2()],
682 )
683 .unwrap();
684 assert!(shared.is_exact_eth_snap_v2());
685 }
686
687 #[test]
688 fn is_exact_eth_snap_v2_rejects_eth_only() {
689 let shared = SharedCapabilities::try_new(
690 vec![EthVersion::Eth68.into()],
691 vec![EthVersion::Eth68.into()],
692 )
693 .unwrap();
694 assert!(!shared.is_exact_eth_snap_v2());
695 }
696
697 #[test]
698 fn is_exact_eth_snap_v2_rejects_eth_without_snap() {
699 let cap = Capability::new_static("les", 1);
701 let shared = SharedCapabilities::try_new(
702 vec![EthVersion::Eth68.into(), Protocol::new(cap.clone(), 5)],
703 vec![EthVersion::Eth68.into(), cap],
704 )
705 .unwrap();
706 assert!(!shared.is_exact_eth_snap_v2());
707 }
708
709 #[test]
710 fn is_exact_eth_snap_v2_rejects_eth_snap_plus_extra() {
711 let cap = Capability::new_static("les", 1);
713 let shared = SharedCapabilities::try_new(
714 vec![EthVersion::Eth68.into(), Protocol::snap_2(), Protocol::new(cap.clone(), 5)],
715 vec![EthVersion::Eth68.into(), Capability::snap_2(), cap],
716 )
717 .unwrap();
718 assert!(!shared.is_exact_eth_snap_v2());
719 }
720}