1use crate::{
2 capability::SharedCapabilities,
3 disconnect::CanDisconnect,
4 errors::{P2PHandshakeError, P2PStreamError},
5 pinger::{Pinger, PingerEvent},
6 protocol::ProtocolIngressLimits,
7 DisconnectReason, HelloMessage, HelloMessageWithProtocols,
8};
9use alloy_primitives::{
10 bytes::{Buf, BufMut, Bytes, BytesMut},
11 hex,
12};
13use alloy_rlp::{Decodable, Encodable, Error as RlpError, EMPTY_LIST_CODE};
14use futures::{Sink, SinkExt, StreamExt};
15use pin_project::pin_project;
16use reth_codecs::add_arbitrary_tests;
17use reth_metrics::metrics::counter;
18use reth_primitives_traits::GotExpected;
19use std::{
20 collections::VecDeque,
21 future::Future,
22 io,
23 pin::Pin,
24 task::{ready, Context, Poll},
25 time::{Duration, Instant},
26};
27use tokio_stream::Stream;
28use tracing::{debug, trace};
29
30#[cfg(feature = "serde")]
31use serde::{Deserialize, Serialize};
32
33pub const MAX_PAYLOAD_SIZE: usize = 16 * 1024 * 1024;
38
39const MAX_DISCONNECT_PAYLOAD_SIZE: usize = 2;
42
43const MAX_LOGGED_PAYLOAD_SIZE: usize = 64;
46
47pub const MAX_RESERVED_MESSAGE_ID: u8 = 0x0f;
50
51const MAX_P2P_MESSAGE_ID: u8 = P2PMessageID::Pong as u8;
53
54const SNAPPY_EMPTY_LIST_PAYLOAD: &[u8] = &[0x01, 0x00, EMPTY_LIST_CODE];
56
57const SNAPPY_PING_MESSAGE: &[u8] = &[0x02, 0x01, 0x00, EMPTY_LIST_CODE];
59
60const SNAPPY_PONG_MESSAGE: &[u8] = &[0x03, 0x01, 0x00, EMPTY_LIST_CODE];
62
63pub const HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(10);
66
67const PING_TIMEOUT: Duration = Duration::from_secs(15);
70
71const PING_INTERVAL: Duration = Duration::from_secs(60);
74
75const PING_TOKEN_BUCKET_CAPACITY: u8 = 5;
77
78const MAX_P2P_CAPACITY: usize = 2;
85
86const MAX_COMPRESS_SCRATCH_SIZE: usize = 256 * 1024;
93
94#[pin_project]
97#[derive(Debug)]
98pub struct UnauthedP2PStream<S> {
99 #[pin]
100 inner: S,
101 max_payload_size: usize,
103}
104
105impl<S> UnauthedP2PStream<S> {
106 pub const fn new(inner: S) -> Self {
108 Self { inner, max_payload_size: MAX_PAYLOAD_SIZE }
109 }
110
111 pub const fn with_max_payload_size(mut self, max_payload_size: usize) -> Self {
117 self.max_payload_size = max_payload_size;
118 self
119 }
120
121 pub const fn inner(&self) -> &S {
123 &self.inner
124 }
125}
126
127impl<S> UnauthedP2PStream<S>
128where
129 S: Stream<Item = io::Result<BytesMut>> + Sink<Bytes, Error = io::Error> + Unpin,
130{
131 pub async fn handshake(
134 mut self,
135 hello: HelloMessageWithProtocols,
136 ) -> Result<(P2PStream<S>, HelloMessage), P2PStreamError> {
137 trace!(?hello, "sending p2p hello to peer");
138
139 self.inner.send(alloy_rlp::encode(P2PMessage::Hello(hello.message())).into()).await?;
141
142 let first_message_bytes = tokio::time::timeout(HANDSHAKE_TIMEOUT, self.inner.next())
143 .await
144 .or(Err(P2PStreamError::HandshakeError(P2PHandshakeError::Timeout)))?
145 .ok_or(P2PStreamError::HandshakeError(P2PHandshakeError::NoResponse))??;
146
147 if first_message_bytes.len() > self.max_payload_size {
151 return Err(P2PStreamError::MessageTooBig {
152 message_size: first_message_bytes.len(),
153 max_size: self.max_payload_size,
154 })
155 }
156
157 let their_hello = match P2PMessage::decode(&mut &first_message_bytes[..]) {
164 Ok(P2PMessage::Hello(hello)) => Ok(hello),
165 Ok(P2PMessage::Disconnect(reason)) => {
166 if matches!(reason, DisconnectReason::TooManyPeers) {
167 trace!(%reason, "Disconnected by peer during handshake");
169 } else {
170 debug!(%reason, "Disconnected by peer during handshake");
171 };
172 counter!("p2pstream.disconnected_errors").increment(1);
173 Err(P2PStreamError::HandshakeError(P2PHandshakeError::Disconnected(reason)))
174 }
175 Err(err) => {
176 debug!(
177 %err,
178 len = first_message_bytes.len(),
179 msg = %hex::encode(log_prefix(&first_message_bytes)),
180 "Failed to decode first message from peer"
181 );
182 Err(P2PStreamError::HandshakeError(err.into()))
183 }
184 Ok(msg) => {
185 debug!(?msg, "expected hello message but received another message");
186 Err(P2PStreamError::HandshakeError(P2PHandshakeError::NonHelloMessageInHandshake))
187 }
188 }?;
189
190 trace!(
191 hello=?their_hello,
192 "validating incoming p2p hello from peer"
193 );
194
195 if (hello.protocol_version as u8) != their_hello.protocol_version as u8 {
196 self.send_disconnect(DisconnectReason::IncompatibleP2PProtocolVersion).await?;
198 return Err(P2PStreamError::MismatchedProtocolVersion(GotExpected {
199 got: their_hello.protocol_version,
200 expected: hello.protocol_version,
201 }))
202 }
203
204 let capability_res =
206 SharedCapabilities::try_new(hello.protocols, their_hello.capabilities.clone());
207
208 let shared_capability = match capability_res {
209 Err(err) => {
210 self.send_disconnect(DisconnectReason::UselessPeer).await?;
212 Err(err)
213 }
214 Ok(cap) => Ok(cap),
215 }?;
216
217 let mut stream = P2PStream::new(self.inner, shared_capability);
218 stream.set_max_payload_size(self.max_payload_size);
219
220 Ok((stream, their_hello))
221 }
222}
223
224impl<S> UnauthedP2PStream<S>
225where
226 S: Sink<Bytes, Error = io::Error> + Unpin,
227{
228 pub async fn send_disconnect(
230 &mut self,
231 reason: DisconnectReason,
232 ) -> Result<(), P2PStreamError> {
233 trace!(
234 %reason,
235 "Sending disconnect message during the handshake",
236 );
237 self.inner
238 .send(Bytes::from(alloy_rlp::encode(P2PMessage::Disconnect(reason))))
239 .await
240 .map_err(P2PStreamError::Io)
241 }
242}
243
244impl<S> CanDisconnect<Bytes> for P2PStream<S>
245where
246 S: Sink<Bytes, Error = io::Error> + Unpin + Send + Sync,
247{
248 fn disconnect(
249 &mut self,
250 reason: DisconnectReason,
251 ) -> Pin<Box<dyn Future<Output = Result<(), P2PStreamError>> + Send + '_>> {
252 Box::pin(async move { self.disconnect(reason).await })
253 }
254}
255
256#[pin_project]
289#[derive(Debug)]
290pub struct P2PStream<S> {
291 #[pin]
292 inner: S,
293
294 encoder: snap::raw::Encoder,
296
297 compress_scratch: Vec<u8>,
303
304 decoder: snap::raw::Decoder,
306
307 pinger: Pinger,
309
310 ping_token_bucket: PingTokenBucket,
312
313 shared_capabilities: SharedCapabilities,
315
316 inbound_protocol_limits: Vec<InboundProtocolLimit>,
318
319 max_payload_size: usize,
321
322 outgoing_messages: VecDeque<Bytes>,
324
325 outgoing_message_buffer_capacity: usize,
328
329 disconnecting: bool,
332
333 needs_flush: bool,
335
336 needs_control_flush: bool,
339}
340
341impl<S> P2PStream<S> {
342 pub fn new(inner: S, shared_capabilities: SharedCapabilities) -> Self {
346 Self {
347 inner,
348 encoder: snap::raw::Encoder::new(),
349 compress_scratch: Vec::new(),
350 decoder: snap::raw::Decoder::new(),
351 pinger: Pinger::new(PING_INTERVAL, PING_TIMEOUT),
352 ping_token_bucket: PingTokenBucket::new(Instant::now()),
353 shared_capabilities,
354 inbound_protocol_limits: Vec::new(),
355 max_payload_size: MAX_PAYLOAD_SIZE,
356 outgoing_messages: VecDeque::new(),
357 outgoing_message_buffer_capacity: MAX_P2P_CAPACITY,
358 disconnecting: false,
359 needs_flush: false,
360 needs_control_flush: false,
361 }
362 }
363
364 pub const fn inner(&self) -> &S {
366 &self.inner
367 }
368
369 pub const fn set_outgoing_message_buffer_capacity(&mut self, capacity: usize) {
375 assert!(capacity != 0);
376 self.outgoing_message_buffer_capacity = capacity;
377 }
378
379 pub const fn set_max_payload_size(&mut self, max_payload_size: usize) {
382 self.max_payload_size = max_payload_size;
383 }
384
385 pub const fn shared_capabilities(&self) -> &SharedCapabilities {
390 &self.shared_capabilities
391 }
392
393 pub(crate) fn set_protocol_ingress_limits(
394 &mut self,
395 capability: &crate::capability::SharedCapability,
396 limits: ProtocolIngressLimits,
397 ) {
398 let Some(max_frame_bytes) = limits.max_frame_bytes() else { return };
399 if capability.num_messages() == 0 {
400 return
401 }
402
403 let start = capability.message_id_offset();
404 let end = u16::from(start) + u16::from(capability.num_messages());
405 let configured = InboundProtocolLimit {
406 capability: capability.capability().into_owned(),
407 start,
408 end,
409 max_frame_bytes,
410 };
411
412 if let Some(current) = self
413 .inbound_protocol_limits
414 .iter_mut()
415 .find(|current| current.capability == configured.capability)
416 {
417 *current = configured;
418 } else {
419 self.inbound_protocol_limits.push(configured);
420 }
421 }
422
423 fn inbound_protocol_limit(&self, message_id: u8) -> Option<&InboundProtocolLimit> {
424 self.inbound_protocol_limits.iter().find(|limit| limit.contains(message_id))
425 }
426
427 fn has_outgoing_capacity(&self) -> bool {
429 self.outgoing_messages.len() < self.outgoing_message_buffer_capacity
430 }
431
432 fn send_pong(&mut self) {
434 self.outgoing_messages.push_back(Bytes::from_static(SNAPPY_PONG_MESSAGE));
435 self.needs_control_flush = true;
436 }
437
438 pub fn send_ping(&mut self) {
440 self.outgoing_messages.push_back(Bytes::from_static(SNAPPY_PING_MESSAGE));
441 self.needs_control_flush = true;
442 }
443}
444
445#[derive(Debug)]
447struct PingTokenBucket {
448 tokens: u8,
449 last_refill: Instant,
450}
451
452impl PingTokenBucket {
453 const fn new(now: Instant) -> Self {
454 Self { tokens: PING_TOKEN_BUCKET_CAPACITY, last_refill: now }
455 }
456
457 fn try_take(&mut self, now: Instant) -> bool {
458 let refill = now.saturating_duration_since(self.last_refill).as_secs();
459 if refill > 0 {
460 self.tokens = u64::from(self.tokens)
461 .saturating_add(refill)
462 .min(u64::from(PING_TOKEN_BUCKET_CAPACITY)) as u8;
463 self.last_refill += Duration::from_secs(refill);
464 }
465
466 if self.tokens == 0 {
467 return false
468 }
469
470 if self.tokens == PING_TOKEN_BUCKET_CAPACITY {
471 self.last_refill = now;
472 }
473 self.tokens -= 1;
474 true
475 }
476}
477
478#[derive(Debug)]
479struct InboundProtocolLimit {
480 capability: crate::Capability,
481 start: u8,
482 end: u16,
483 max_frame_bytes: usize,
484}
485
486impl InboundProtocolLimit {
487 const fn contains(&self, message_id: u8) -> bool {
488 message_id >= self.start && (message_id as u16) < self.end
489 }
490}
491
492pub trait DisconnectP2P {
495 fn start_disconnect(&mut self, reason: DisconnectReason) -> Result<(), P2PStreamError>;
497
498 fn is_disconnecting(&self) -> bool;
500}
501
502impl<S> DisconnectP2P for P2PStream<S> {
503 fn start_disconnect(&mut self, reason: DisconnectReason) -> Result<(), P2PStreamError> {
512 self.outgoing_messages.clear();
514 let disconnect = P2PMessage::Disconnect(reason);
515 let buf = alloy_rlp::encode(&disconnect);
516
517 let compressed =
520 compress_frame(&mut self.encoder, &mut self.compress_scratch, buf[0], &buf[1..])
521 .map_err(|err| {
522 debug!(
523 %err,
524 msg=%hex::encode(&buf[1..]),
525 "error compressing disconnect"
526 );
527 err
528 })?;
529
530 self.outgoing_messages.push_back(compressed);
531 self.needs_control_flush = true;
532 self.disconnecting = true;
533 Ok(())
534 }
535
536 fn is_disconnecting(&self) -> bool {
537 self.disconnecting
538 }
539}
540
541impl<S> P2PStream<S>
542where
543 S: Sink<Bytes, Error = io::Error> + Unpin + Send,
544{
545 pub async fn disconnect(&mut self, reason: DisconnectReason) -> Result<(), P2PStreamError> {
550 self.start_disconnect(reason)?;
551 self.close().await
552 }
553}
554
555impl<S> P2PStream<S>
556where
557 S: Sink<Bytes, Error = io::Error> + Unpin,
558{
559 fn poll_drain_outgoing(
561 mut self: Pin<&mut Self>,
562 cx: &mut Context<'_>,
563 ) -> Poll<Result<(), P2PStreamError>> {
564 let mut this = self.as_mut().project();
565 while !this.outgoing_messages.is_empty() {
566 ready!(this.inner.as_mut().poll_ready(cx))?;
567 let message = this.outgoing_messages.pop_front().expect("checked non-empty");
568 this.inner.as_mut().start_send(message)?;
569 *this.needs_flush = true;
570 }
571
572 Poll::Ready(Ok(()))
573 }
574}
575
576impl<S> Stream for P2PStream<S>
579where
580 S: Stream<Item = io::Result<BytesMut>> + Sink<Bytes, Error = io::Error> + Unpin,
581{
582 type Item = Result<BytesMut, P2PStreamError>;
583
584 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
585 let this = self.get_mut();
586
587 if this.disconnecting {
588 return Poll::Ready(None)
590 }
591
592 let mut ping_batch_time = None;
593
594 while let Poll::Ready(res) = this.inner.poll_next_unpin(cx) {
597 let bytes = match res {
598 Some(Ok(bytes)) => bytes,
599 Some(Err(err)) => return Poll::Ready(Some(Err(err.into()))),
600 None => return Poll::Ready(None),
601 };
602
603 if bytes.is_empty() {
604 return Poll::Ready(Some(Err(P2PStreamError::EmptyProtocolMessage)))
606 }
607
608 let id = bytes[0];
613 if id == P2PMessageID::Disconnect as u8 {
614 if let Ok(reason) = DisconnectReason::decode(&mut &bytes[1..]) {
626 return Poll::Ready(Some(Err(P2PStreamError::Disconnected(reason))))
627 }
628 }
629
630 if id == P2PMessageID::Ping as u8 || id == P2PMessageID::Pong as u8 {
631 validate_ping_pong_payload(&mut this.decoder, id, &bytes[1..])?;
632
633 if id == P2PMessageID::Ping as u8 {
634 let now = *ping_batch_time.get_or_insert_with(Instant::now);
638 if !this.ping_token_bucket.try_take(now) {
639 return Poll::Ready(Some(Err(P2PStreamError::TooManyPings)))
640 }
641
642 trace!("Received Ping, Sending Pong");
643 this.send_pong();
644 cx.waker().wake_by_ref();
647 } else {
648 this.pinger.on_pong()?;
650 }
651 continue
652 }
653
654 if id > MAX_RESERVED_MESSAGE_ID && this.shared_capabilities.find_by_offset(id).is_none()
655 {
656 return Poll::Ready(Some(Err(P2PStreamError::UnknownSubprotocolMessageId(id))))
657 }
658
659 let decompressed_len = snap::raw::decompress_len(&bytes[1..])?;
662 if decompressed_len > this.max_payload_size {
663 return Poll::Ready(Some(Err(P2PStreamError::MessageTooBig {
664 message_size: decompressed_len,
665 max_size: this.max_payload_size,
666 })))
667 }
668
669 if id == P2PMessageID::Disconnect as u8 &&
670 decompressed_len > MAX_DISCONNECT_PAYLOAD_SIZE
671 {
672 return Poll::Ready(Some(Err(P2PStreamError::MessageTooBig {
673 message_size: decompressed_len,
674 max_size: MAX_DISCONNECT_PAYLOAD_SIZE,
675 })))
676 }
677
678 let frame_len = decompressed_len + 1;
679 if let Some(limit) = this.inbound_protocol_limit(id) &&
680 frame_len > limit.max_frame_bytes
681 {
682 counter!("p2pstream.subprotocol_message_too_big").increment(1);
683 return Poll::Ready(Some(Err(P2PStreamError::SubprotocolMessageTooBig {
684 capability: limit.capability.clone(),
685 message_size: frame_len,
686 max_size: limit.max_frame_bytes,
687 })))
688 }
689
690 let mut decompress_buf = BytesMut::zeroed(frame_len);
693
694 this.decoder.decompress(&bytes[1..], &mut decompress_buf[1..]).map_err(|err| {
697 debug!(
698 %err,
699 len = bytes.len() - 1,
700 msg = %hex::encode(log_prefix(&bytes[1..])),
701 "error decompressing p2p message"
702 );
703 err
704 })?;
705
706 match id {
707 _ if id == P2PMessageID::Hello as u8 => {
708 return Poll::Ready(Some(Err(P2PStreamError::HandshakeError(
711 P2PHandshakeError::HelloNotInHandshake,
712 ))))
713 }
714 _ if id == P2PMessageID::Disconnect as u8 => {
715 let reason = DisconnectReason::decode(&mut &decompress_buf[1..]).inspect_err(|err| {
721 debug!(
722 %err, msg=%hex::encode(&decompress_buf[1..]), "Failed to decode disconnect message from peer"
723 );
724 })?;
725 return Poll::Ready(Some(Err(P2PStreamError::Disconnected(reason))))
726 }
727 _ if id > MAX_P2P_MESSAGE_ID && id <= MAX_RESERVED_MESSAGE_ID => {
728 return Poll::Ready(Some(Err(P2PStreamError::UnknownReservedMessageId(id))))
730 }
731 _ => {
732 decompress_buf[0] = bytes[0] - MAX_RESERVED_MESSAGE_ID - 1;
756
757 return Poll::Ready(Some(Ok(decompress_buf)))
758 }
759 }
760 }
761
762 Poll::Pending
763 }
764}
765
766pub(crate) fn log_prefix(bytes: &[u8]) -> &[u8] {
768 &bytes[..bytes.len().min(MAX_LOGGED_PAYLOAD_SIZE)]
769}
770
771fn validate_ping_pong_payload(
773 decoder: &mut snap::raw::Decoder,
774 message_id: u8,
775 compressed_payload: &[u8],
776) -> Result<(), P2PStreamError> {
777 if snap::raw::decompress_len(compressed_payload)? != 1 {
778 return Err(P2PStreamError::InvalidPingPongPayload(message_id))
779 }
780
781 let mut payload = [0u8; 1];
782 decoder.decompress(compressed_payload, &mut payload)?;
783 if payload != [EMPTY_LIST_CODE] {
784 return Err(P2PStreamError::InvalidPingPongPayload(message_id))
785 }
786
787 Ok(())
788}
789
790impl<S> Sink<Bytes> for P2PStream<S>
791where
792 S: Sink<Bytes, Error = io::Error> + Unpin,
793{
794 type Error = P2PStreamError;
795
796 fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
797 let this = self.as_mut().get_mut();
798
799 match this.pinger.poll_ping(cx) {
802 Poll::Pending => {}
803 Poll::Ready(Ok(PingerEvent::Ping)) => {
804 this.send_ping();
805 }
806 Poll::Ready(Ok(PingerEvent::Timeout) | Err(_)) => {
807 this.start_disconnect(DisconnectReason::PingTimeout)?;
808 }
809 }
810
811 if self.needs_control_flush {
816 ready!(self.as_mut().poll_flush(cx))?;
817 } else if !self.has_outgoing_capacity() {
818 ready!(self.as_mut().poll_drain_outgoing(cx))?;
819 }
820
821 debug_assert!(self.has_outgoing_capacity());
823 Poll::Ready(Ok(()))
824 }
825
826 fn start_send(self: Pin<&mut Self>, item: Bytes) -> Result<(), Self::Error> {
827 if item.len() > self.max_payload_size {
828 return Err(P2PStreamError::MessageTooBig {
829 message_size: item.len(),
830 max_size: self.max_payload_size,
831 })
832 }
833
834 if item.is_empty() {
835 return Err(P2PStreamError::EmptyProtocolMessage)
837 }
838
839 if !self.has_outgoing_capacity() {
841 return Err(P2PStreamError::SendBufferFull)
842 }
843
844 let this = self.project();
845
846 let compressed = compress_frame(
849 this.encoder,
850 this.compress_scratch,
851 item[0] + MAX_RESERVED_MESSAGE_ID + 1,
852 &item[1..],
853 )
854 .map_err(|err| {
855 debug!(
856 %err,
857 msg=%hex::encode(&item[1..]),
858 "error compressing p2p message"
859 );
860 err
861 })?;
862 this.outgoing_messages.push_back(compressed);
863
864 Ok(())
865 }
866
867 fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
869 ready!(self.as_mut().poll_drain_outgoing(cx))?;
870
871 let mut this = self.project();
872
873 if *this.needs_flush {
874 ready!(this.inner.as_mut().poll_flush(cx))?;
875 *this.needs_flush = false;
876 }
877 *this.needs_control_flush = false;
878
879 Poll::Ready(Ok(()))
880 }
881
882 fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
883 ready!(self.as_mut().poll_flush(cx))?;
884 ready!(self.project().inner.poll_close(cx))?;
885
886 Poll::Ready(Ok(()))
887 }
888}
889
890#[derive(Debug, Clone, PartialEq, Eq)]
892#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
893#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
894#[add_arbitrary_tests(rlp)]
895pub enum P2PMessage {
896 Hello(HelloMessage),
898
899 Disconnect(DisconnectReason),
902
903 Ping,
905
906 Pong,
908}
909
910impl P2PMessage {
911 pub const fn message_id(&self) -> P2PMessageID {
913 match self {
914 Self::Hello(_) => P2PMessageID::Hello,
915 Self::Disconnect(_) => P2PMessageID::Disconnect,
916 Self::Ping => P2PMessageID::Ping,
917 Self::Pong => P2PMessageID::Pong,
918 }
919 }
920}
921
922impl Encodable for P2PMessage {
923 fn encode(&self, out: &mut dyn BufMut) {
928 (self.message_id() as u8).encode(out);
929 match self {
930 Self::Hello(msg) => msg.encode(out),
931 Self::Disconnect(msg) => msg.encode(out),
932 Self::Ping => {
933 out.put_slice(SNAPPY_EMPTY_LIST_PAYLOAD);
935 }
936 Self::Pong => {
937 out.put_slice(SNAPPY_EMPTY_LIST_PAYLOAD);
939 }
940 }
941 }
942
943 fn length(&self) -> usize {
944 let payload_len = match self {
945 Self::Hello(msg) => msg.length(),
946 Self::Disconnect(msg) => msg.length(),
947 Self::Ping | Self::Pong => SNAPPY_EMPTY_LIST_PAYLOAD.len(),
949 };
950 payload_len + 1 }
952}
953
954impl Decodable for P2PMessage {
955 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
962 fn advance_snappy_ping_pong_payload(buf: &mut &[u8]) -> alloy_rlp::Result<()> {
964 if buf.len() < 3 {
965 return Err(RlpError::InputTooShort)
966 }
967 if buf[..3] != [0x01, 0x00, EMPTY_LIST_CODE] {
968 return Err(RlpError::Custom("expected snappy payload"))
969 }
970 buf.advance(3);
971 Ok(())
972 }
973
974 let message_id = u8::decode(&mut &buf[..])?;
975 let id = P2PMessageID::try_from(message_id)
976 .or(Err(RlpError::Custom("unknown p2p message id")))?;
977 buf.advance(1);
978 match id {
979 P2PMessageID::Hello => Ok(Self::Hello(HelloMessage::decode(buf)?)),
980 P2PMessageID::Disconnect => Ok(Self::Disconnect(DisconnectReason::decode(buf)?)),
981 P2PMessageID::Ping => {
982 advance_snappy_ping_pong_payload(buf)?;
983 Ok(Self::Ping)
984 }
985 P2PMessageID::Pong => {
986 advance_snappy_ping_pong_payload(buf)?;
987 Ok(Self::Pong)
988 }
989 }
990 }
991}
992
993#[derive(Debug, Copy, Clone, Eq, PartialEq)]
995pub enum P2PMessageID {
996 Hello = 0x00,
998
999 Disconnect = 0x01,
1001
1002 Ping = 0x02,
1004
1005 Pong = 0x03,
1007}
1008
1009impl From<P2PMessage> for P2PMessageID {
1010 fn from(msg: P2PMessage) -> Self {
1011 match msg {
1012 P2PMessage::Hello(_) => Self::Hello,
1013 P2PMessage::Disconnect(_) => Self::Disconnect,
1014 P2PMessage::Ping => Self::Ping,
1015 P2PMessage::Pong => Self::Pong,
1016 }
1017 }
1018}
1019
1020impl TryFrom<u8> for P2PMessageID {
1021 type Error = P2PStreamError;
1022
1023 fn try_from(id: u8) -> Result<Self, Self::Error> {
1024 match id {
1025 0x00 => Ok(Self::Hello),
1026 0x01 => Ok(Self::Disconnect),
1027 0x02 => Ok(Self::Ping),
1028 0x03 => Ok(Self::Pong),
1029 _ => Err(P2PStreamError::UnknownReservedMessageId(id)),
1030 }
1031 }
1032}
1033
1034fn compress_frame(
1041 encoder: &mut snap::raw::Encoder,
1042 scratch: &mut Vec<u8>,
1043 wire_id: u8,
1044 payload: &[u8],
1045) -> Result<Bytes, snap::Error> {
1046 let needed = 1 + snap::raw::max_compress_len(payload.len());
1047
1048 if needed > MAX_COMPRESS_SCRATCH_SIZE {
1049 let mut compressed = vec![0u8; needed];
1050 let compressed_size = encoder.compress(payload, &mut compressed[1..])?;
1051 compressed[0] = wire_id;
1052 compressed.truncate(compressed_size + 1);
1053 return Ok(compressed.into())
1054 }
1055
1056 if scratch.len() < needed {
1057 scratch.resize(needed, 0);
1058 }
1059 let compressed_size = encoder.compress(payload, &mut scratch[1..])?;
1060 scratch[0] = wire_id;
1061 Ok(Bytes::copy_from_slice(&scratch[..compressed_size + 1]))
1062}
1063
1064#[cfg(test)]
1065mod tests {
1066 use super::*;
1067 use crate::{
1068 capability::SharedCapability, protocol::Protocol, test_utils::eth_hello, Capability,
1069 EthVersion, ProtocolVersion,
1070 };
1071 use futures::task::noop_waker_ref;
1072 use tokio::net::{TcpListener, TcpStream};
1073 use tokio_util::codec::Decoder;
1074
1075 #[derive(Default)]
1077 struct FlushCountingTransport {
1078 incoming: VecDeque<io::Result<BytesMut>>,
1079 sent: Vec<Bytes>,
1080 flushes: usize,
1081 }
1082
1083 #[derive(Default)]
1084 struct InboundTransport {
1085 incoming: VecDeque<BytesMut>,
1086 }
1087
1088 impl Stream for InboundTransport {
1089 type Item = io::Result<BytesMut>;
1090
1091 fn poll_next(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Option<Self::Item>> {
1092 Poll::Ready(self.incoming.pop_front().map(Ok))
1093 }
1094 }
1095
1096 impl Sink<Bytes> for InboundTransport {
1097 type Error = io::Error;
1098
1099 fn poll_ready(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
1100 Poll::Ready(Ok(()))
1101 }
1102
1103 fn start_send(self: Pin<&mut Self>, _item: Bytes) -> Result<(), Self::Error> {
1104 Ok(())
1105 }
1106
1107 fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
1108 Poll::Ready(Ok(()))
1109 }
1110
1111 fn poll_close(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
1112 Poll::Ready(Ok(()))
1113 }
1114 }
1115
1116 impl Stream for FlushCountingTransport {
1117 type Item = io::Result<BytesMut>;
1118
1119 fn poll_next(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Option<Self::Item>> {
1120 match self.incoming.pop_front() {
1121 Some(item) => Poll::Ready(Some(item)),
1122 None => Poll::Pending,
1123 }
1124 }
1125 }
1126
1127 impl Sink<Bytes> for FlushCountingTransport {
1128 type Error = io::Error;
1129
1130 fn poll_ready(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
1131 Poll::Ready(Ok(()))
1132 }
1133
1134 fn start_send(mut self: Pin<&mut Self>, item: Bytes) -> Result<(), Self::Error> {
1135 self.sent.push(item);
1136 Ok(())
1137 }
1138
1139 fn poll_flush(
1140 mut self: Pin<&mut Self>,
1141 _: &mut Context<'_>,
1142 ) -> Poll<Result<(), Self::Error>> {
1143 self.flushes += 1;
1144 Poll::Ready(Ok(()))
1145 }
1146
1147 fn poll_close(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
1148 Poll::Ready(Ok(()))
1149 }
1150 }
1151
1152 fn eth_shared_capabilities() -> SharedCapabilities {
1153 SharedCapabilities::try_new(vec![EthVersion::Eth68.into()], vec![Capability::eth_68()])
1154 .unwrap()
1155 }
1156
1157 fn stream_with_incoming(frame: BytesMut) -> P2PStream<FlushCountingTransport> {
1158 let mut transport = FlushCountingTransport::default();
1159 transport.incoming.push_back(Ok(frame));
1160 P2PStream::new(transport, eth_shared_capabilities())
1161 }
1162
1163 fn stream_with_incoming_pings(count: usize) -> P2PStream<FlushCountingTransport> {
1164 let mut transport = FlushCountingTransport::default();
1165 transport.incoming.extend((0..count).map(|_| Ok(BytesMut::from(SNAPPY_PING_MESSAGE))));
1166 P2PStream::new(transport, eth_shared_capabilities())
1167 }
1168
1169 fn compressed_p2p_message(message_id: P2PMessageID, payload: &[u8]) -> BytesMut {
1170 let mut encoder = snap::raw::Encoder::new();
1171 let mut scratch = Vec::new();
1172 let message =
1173 compress_frame(&mut encoder, &mut scratch, message_id as u8, payload).unwrap();
1174 BytesMut::from(message.as_ref())
1175 }
1176
1177 #[tokio::test]
1178 async fn rejects_ping_pong_with_oversized_payload_before_decompression() {
1179 const SIXTEEN_MIB_SNAPPY_HEADER: [u8; 4] = [0x80, 0x80, 0x80, 0x08];
1180
1181 for message_id in [P2PMessageID::Ping, P2PMessageID::Pong] {
1182 let frame = BytesMut::from(
1183 [
1184 message_id as u8,
1185 SIXTEEN_MIB_SNAPPY_HEADER[0],
1186 SIXTEEN_MIB_SNAPPY_HEADER[1],
1187 SIXTEEN_MIB_SNAPPY_HEADER[2],
1188 SIXTEEN_MIB_SNAPPY_HEADER[3],
1189 ]
1190 .as_slice(),
1191 );
1192 assert_eq!(snap::raw::decompress_len(&frame[1..]).unwrap(), MAX_PAYLOAD_SIZE);
1193
1194 let mut stream = stream_with_incoming(frame);
1195 let waker = noop_waker_ref();
1196 let mut cx = Context::from_waker(waker);
1197
1198 match Pin::new(&mut stream).poll_next(&mut cx) {
1199 Poll::Ready(Some(Err(P2PStreamError::InvalidPingPongPayload(id)))) => {
1200 assert_eq!(id, message_id as u8)
1201 }
1202 result => panic!("unexpected poll result: {result:?}"),
1203 }
1204 assert!(stream.outgoing_messages.is_empty());
1205 }
1206 }
1207
1208 #[tokio::test]
1209 async fn rejects_oversized_disconnect_before_decompression() {
1210 let frame =
1212 BytesMut::from([P2PMessageID::Disconnect as u8, 0x80, 0x80, 0x80, 0x08].as_slice());
1213 let mut stream = stream_with_incoming(frame);
1214
1215 assert!(matches!(
1216 stream.next().await.unwrap().unwrap_err(),
1217 P2PStreamError::MessageTooBig {
1218 message_size: MAX_PAYLOAD_SIZE,
1219 max_size: MAX_DISCONNECT_PAYLOAD_SIZE,
1220 }
1221 ));
1222 }
1223
1224 #[tokio::test]
1225 async fn rejects_ping_pong_with_non_list_payload() {
1226 for message_id in [P2PMessageID::Ping, P2PMessageID::Pong] {
1227 let frame = compressed_p2p_message(message_id, &[alloy_rlp::EMPTY_STRING_CODE]);
1228 let mut stream = stream_with_incoming(frame);
1229 let waker = noop_waker_ref();
1230 let mut cx = Context::from_waker(waker);
1231
1232 assert!(matches!(
1233 Pin::new(&mut stream).poll_next(&mut cx),
1234 Poll::Ready(Some(Err(P2PStreamError::InvalidPingPongPayload(id))))
1235 if id == message_id as u8
1236 ));
1237 assert!(stream.outgoing_messages.is_empty());
1238 }
1239 }
1240
1241 #[tokio::test]
1242 async fn accepts_ping_with_empty_list_payload() {
1243 let mut stream = stream_with_incoming(BytesMut::from(SNAPPY_PING_MESSAGE));
1244 let waker = noop_waker_ref();
1245 let mut cx = Context::from_waker(waker);
1246
1247 assert!(Pin::new(&mut stream).poll_next(&mut cx).is_pending());
1248 assert_eq!(stream.outgoing_messages.len(), 1);
1249 assert_eq!(stream.outgoing_messages.front().unwrap().as_ref(), SNAPPY_PONG_MESSAGE);
1250 }
1251
1252 #[test]
1253 fn ping_token_bucket_limits_bursts_and_refills() {
1254 let now = Instant::now();
1255 let mut bucket = PingTokenBucket::new(now);
1256
1257 for _ in 0..PING_TOKEN_BUCKET_CAPACITY {
1258 assert!(bucket.try_take(now));
1259 }
1260 assert!(!bucket.try_take(now));
1261
1262 let one_refill = now + Duration::from_secs(1);
1263 assert!(bucket.try_take(one_refill));
1264 assert!(!bucket.try_take(one_refill));
1265
1266 let full_refill = one_refill +
1269 Duration::from_secs(u64::from(PING_TOKEN_BUCKET_CAPACITY)) +
1270 Duration::from_millis(500);
1271 for _ in 0..PING_TOKEN_BUCKET_CAPACITY {
1272 assert!(bucket.try_take(full_refill));
1273 }
1274 assert!(!bucket.try_take(full_refill));
1275 assert!(!bucket.try_take(full_refill + Duration::from_millis(999)));
1276 assert!(bucket.try_take(full_refill + Duration::from_secs(1)));
1277 }
1278
1279 #[tokio::test]
1280 async fn accepts_ping_burst_at_token_bucket_capacity() {
1281 let mut stream = stream_with_incoming_pings(usize::from(PING_TOKEN_BUCKET_CAPACITY));
1282 let waker = noop_waker_ref();
1283 let mut cx = Context::from_waker(waker);
1284
1285 assert!(Pin::new(&mut stream).poll_next(&mut cx).is_pending());
1286 assert_eq!(stream.outgoing_messages.len(), usize::from(PING_TOKEN_BUCKET_CAPACITY));
1287 }
1288
1289 #[tokio::test]
1290 async fn rejects_ping_burst_over_token_bucket_capacity() {
1291 let mut stream = stream_with_incoming_pings(usize::from(PING_TOKEN_BUCKET_CAPACITY) + 1);
1292 let waker = noop_waker_ref();
1293 let mut cx = Context::from_waker(waker);
1294
1295 assert!(matches!(
1296 Pin::new(&mut stream).poll_next(&mut cx),
1297 Poll::Ready(Some(Err(P2PStreamError::TooManyPings)))
1298 ));
1299 assert_eq!(stream.outgoing_messages.len(), usize::from(PING_TOKEN_BUCKET_CAPACITY));
1300 }
1301
1302 #[tokio::test]
1303 async fn rejects_subprotocol_frame_before_decompression_when_declared_size_exceeds_limit() {
1304 let cap = Capability::new_static("test", 1);
1305 let shared_capabilities =
1306 SharedCapabilities::try_new(vec![Protocol::new(cap.clone(), 1)], vec![cap.clone()])
1307 .unwrap();
1308 let shared_capability = shared_capabilities.find(&cap).unwrap().clone();
1309 let wire_id = shared_capability.message_id_offset();
1310 let mut encoder = snap::raw::Encoder::new();
1311 let mut scratch = Vec::new();
1312 let accepted = compress_frame(&mut encoder, &mut scratch, wire_id, &[0; 3]).unwrap();
1313 let oversized = compress_frame(&mut encoder, &mut scratch, wire_id, &[0; 4]).unwrap();
1314 let transport = InboundTransport {
1315 incoming: [accepted, oversized]
1316 .into_iter()
1317 .map(|frame| BytesMut::from(frame.as_ref()))
1318 .collect(),
1319 };
1320 let mut stream = P2PStream::new(transport, shared_capabilities);
1321 stream.set_protocol_ingress_limits(&shared_capability, ProtocolIngressLimits::new(4));
1322
1323 let frame = stream.next().await.unwrap().unwrap();
1324 assert_eq!(frame.len(), 4);
1325
1326 assert!(matches!(
1327 stream.next().await.unwrap().unwrap_err(),
1328 P2PStreamError::SubprotocolMessageTooBig {
1329 capability,
1330 message_size: 5,
1331 max_size: 4,
1332 } if capability == cap
1333 ));
1334 }
1335
1336 #[tokio::test]
1337 async fn zero_message_protocol_does_not_replace_neighboring_frame_limit() {
1338 let zero = Capability::new_static("aaa", 1);
1339 let limited = Capability::new_static("bbb", 1);
1340 let shared_capabilities = SharedCapabilities::try_new(
1341 vec![Protocol::new(zero.clone(), 0), Protocol::new(limited.clone(), 1)],
1342 vec![zero.clone(), limited.clone()],
1343 )
1344 .unwrap();
1345 let zero_shared = shared_capabilities.find(&zero).unwrap().clone();
1346 let limited_shared = shared_capabilities.find(&limited).unwrap().clone();
1347 assert_eq!(zero_shared.message_id_offset(), limited_shared.message_id_offset());
1348
1349 let mut encoder = snap::raw::Encoder::new();
1350 let mut scratch = Vec::new();
1351 let oversized =
1352 compress_frame(&mut encoder, &mut scratch, limited_shared.message_id_offset(), &[0; 4])
1353 .unwrap();
1354 let transport =
1355 InboundTransport { incoming: VecDeque::from([BytesMut::from(oversized.as_ref())]) };
1356 let mut stream = P2PStream::new(transport, shared_capabilities);
1357 stream.set_protocol_ingress_limits(&limited_shared, ProtocolIngressLimits::new(4));
1358 stream.set_protocol_ingress_limits(&zero_shared, ProtocolIngressLimits::new(1));
1359
1360 assert!(matches!(
1361 stream.next().await.unwrap().unwrap_err(),
1362 P2PStreamError::SubprotocolMessageTooBig {
1363 capability,
1364 message_size: 5,
1365 max_size: 4,
1366 } if capability == limited
1367 ));
1368 }
1369
1370 #[tokio::test]
1371 async fn poll_ready_drains_full_subprotocol_queue_without_flushing_inner() {
1372 let mut stream =
1373 P2PStream::new(FlushCountingTransport::default(), eth_shared_capabilities());
1374 stream.set_outgoing_message_buffer_capacity(1);
1375 Pin::new(&mut stream).start_send(Bytes::from_static(&[0x00, EMPTY_LIST_CODE])).unwrap();
1376
1377 let waker = noop_waker_ref();
1378 let mut cx = Context::from_waker(waker);
1379 assert!(Pin::new(&mut stream).poll_ready(&mut cx).is_ready());
1380
1381 assert_eq!(stream.inner().sent.len(), 1);
1383 assert_eq!(stream.inner().flushes, 0);
1384
1385 assert!(Pin::new(&mut stream).poll_flush(&mut cx).is_ready());
1387 assert_eq!(stream.inner().flushes, 1);
1388
1389 assert!(Pin::new(&mut stream).poll_flush(&mut cx).is_ready());
1391 assert_eq!(stream.inner().flushes, 1);
1392 }
1393
1394 #[tokio::test]
1395 async fn poll_ready_flushes_queued_control_messages() {
1396 let mut stream =
1397 P2PStream::new(FlushCountingTransport::default(), eth_shared_capabilities());
1398 stream.send_ping();
1399
1400 let waker = noop_waker_ref();
1401 let mut cx = Context::from_waker(waker);
1402 assert!(Pin::new(&mut stream).poll_ready(&mut cx).is_ready());
1403
1404 assert_eq!(stream.inner().sent.len(), 1);
1406 assert_eq!(stream.inner().flushes, 1);
1407 }
1408
1409 #[tokio::test]
1410 async fn test_can_disconnect() {
1411 reth_tracing::init_test_tracing();
1412 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
1413 let local_addr = listener.local_addr().unwrap();
1414
1415 let expected_disconnect = DisconnectReason::UselessPeer;
1416
1417 let handle = tokio::spawn(async move {
1418 let (incoming, _) = listener.accept().await.unwrap();
1420 let stream = crate::PassthroughCodec::default().framed(incoming);
1421
1422 let (server_hello, _) = eth_hello();
1423
1424 let (mut p2p_stream, _) =
1425 UnauthedP2PStream::new(stream).handshake(server_hello).await.unwrap();
1426
1427 p2p_stream.disconnect(expected_disconnect).await.unwrap();
1428 });
1429
1430 let outgoing = TcpStream::connect(local_addr).await.unwrap();
1431 let sink = crate::PassthroughCodec::default().framed(outgoing);
1432
1433 let (client_hello, _) = eth_hello();
1434
1435 let (mut p2p_stream, _) =
1436 UnauthedP2PStream::new(sink).handshake(client_hello).await.unwrap();
1437
1438 let err = p2p_stream.next().await.unwrap().unwrap_err();
1439 match err {
1440 P2PStreamError::Disconnected(reason) => assert_eq!(reason, expected_disconnect),
1441 e => panic!("unexpected err: {e}"),
1442 }
1443
1444 handle.await.unwrap();
1445 }
1446
1447 #[tokio::test]
1448 async fn test_can_disconnect_weird_disconnect_encoding() {
1449 reth_tracing::init_test_tracing();
1450 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
1451 let local_addr = listener.local_addr().unwrap();
1452
1453 let expected_disconnect = DisconnectReason::SubprotocolSpecific;
1454
1455 let handle = tokio::spawn(async move {
1456 let (incoming, _) = listener.accept().await.unwrap();
1458 let stream = crate::PassthroughCodec::default().framed(incoming);
1459
1460 let (server_hello, _) = eth_hello();
1461
1462 let (mut p2p_stream, _) =
1463 UnauthedP2PStream::new(stream).handshake(server_hello).await.unwrap();
1464
1465 p2p_stream.outgoing_messages.clear();
1467
1468 p2p_stream.outgoing_messages.push_back(Bytes::from(alloy_rlp::encode(
1469 P2PMessage::Disconnect(DisconnectReason::SubprotocolSpecific),
1470 )));
1471 p2p_stream.disconnecting = true;
1472 p2p_stream.close().await.unwrap();
1473 });
1474
1475 let outgoing = TcpStream::connect(local_addr).await.unwrap();
1476 let sink = crate::PassthroughCodec::default().framed(outgoing);
1477
1478 let (client_hello, _) = eth_hello();
1479
1480 let (mut p2p_stream, _) =
1481 UnauthedP2PStream::new(sink).handshake(client_hello).await.unwrap();
1482
1483 let err = p2p_stream.next().await.unwrap().unwrap_err();
1484 match err {
1485 P2PStreamError::Disconnected(reason) => assert_eq!(reason, expected_disconnect),
1486 e => panic!("unexpected err: {e}"),
1487 }
1488
1489 handle.await.unwrap();
1490 }
1491
1492 #[tokio::test]
1493 async fn test_handshake_passthrough() {
1494 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
1497 let local_addr = listener.local_addr().unwrap();
1498
1499 let handle = tokio::spawn(async move {
1500 let (incoming, _) = listener.accept().await.unwrap();
1502 let stream = crate::PassthroughCodec::default().framed(incoming);
1503
1504 let (server_hello, _) = eth_hello();
1505
1506 let unauthed_stream = UnauthedP2PStream::new(stream);
1507 let (p2p_stream, _) = unauthed_stream.handshake(server_hello).await.unwrap();
1508
1509 assert_eq!(
1511 *p2p_stream.shared_capabilities.iter_caps().next().unwrap(),
1512 SharedCapability::Eth {
1513 version: EthVersion::Eth67,
1514 offset: MAX_RESERVED_MESSAGE_ID + 1
1515 }
1516 );
1517 });
1518
1519 let outgoing = TcpStream::connect(local_addr).await.unwrap();
1520 let sink = crate::PassthroughCodec::default().framed(outgoing);
1521
1522 let (client_hello, _) = eth_hello();
1523
1524 let unauthed_stream = UnauthedP2PStream::new(sink);
1525 let (p2p_stream, _) = unauthed_stream.handshake(client_hello).await.unwrap();
1526
1527 assert_eq!(
1529 *p2p_stream.shared_capabilities.iter_caps().next().unwrap(),
1530 SharedCapability::Eth {
1531 version: EthVersion::Eth67,
1532 offset: MAX_RESERVED_MESSAGE_ID + 1
1533 }
1534 );
1535
1536 handle.await.unwrap();
1538 }
1539
1540 fn oversized_eth_message() -> Vec<u8> {
1543 vec![0u8; MAX_PAYLOAD_SIZE + 4 * 1024 * 1024]
1544 }
1545
1546 #[tokio::test]
1547 async fn max_payload_size_bounds_both_directions() {
1548 let message = oversized_eth_message();
1549 let mut encoder = snap::raw::Encoder::new();
1550 let wire_id = MAX_RESERVED_MESSAGE_ID + 1;
1551 let frame = compress_frame(&mut encoder, &mut Vec::new(), wire_id, &message[1..])
1552 .map(|frame| BytesMut::from(frame.as_ref()))
1553 .unwrap();
1554 let payload_len = message.len() - 1;
1555
1556 let mut default_stream = stream_with_incoming(frame.clone());
1557 assert!(matches!(
1558 default_stream.next().await.unwrap().unwrap_err(),
1559 P2PStreamError::MessageTooBig { message_size, max_size: MAX_PAYLOAD_SIZE }
1560 if message_size == payload_len
1561 ));
1562 assert!(matches!(
1563 Pin::new(&mut default_stream).start_send(Bytes::from(message.clone())).unwrap_err(),
1564 P2PStreamError::MessageTooBig { max_size: MAX_PAYLOAD_SIZE, .. }
1565 ));
1566
1567 let mut raised_stream = stream_with_incoming(frame);
1568 raised_stream.set_max_payload_size(2 * MAX_PAYLOAD_SIZE);
1569 assert!(raised_stream.next().await.unwrap().unwrap().as_ref() == message.as_slice());
1570 Pin::new(&mut raised_stream).start_send(Bytes::from(message)).unwrap();
1571 }
1572
1573 #[tokio::test]
1574 async fn handshake_carries_max_payload_size_into_the_stream() {
1575 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
1576 let local_addr = listener.local_addr().unwrap();
1577 let message = oversized_eth_message();
1578 let expected = message.clone();
1579
1580 let handle = tokio::spawn(async move {
1581 let (incoming, _) = listener.accept().await.unwrap();
1582 let stream = crate::PassthroughCodec::default().framed(incoming);
1583 let (server_hello, _) = eth_hello();
1584 let (mut p2p_stream, _) = UnauthedP2PStream::new(stream)
1585 .with_max_payload_size(2 * MAX_PAYLOAD_SIZE)
1586 .handshake(server_hello)
1587 .await
1588 .unwrap();
1589 assert!(p2p_stream.next().await.unwrap().unwrap().as_ref() == expected.as_slice());
1590 });
1591
1592 let outgoing = TcpStream::connect(local_addr).await.unwrap();
1593 let sink = crate::PassthroughCodec::default().framed(outgoing);
1594 let (client_hello, _) = eth_hello();
1595 let (mut p2p_stream, _) = UnauthedP2PStream::new(sink)
1596 .with_max_payload_size(2 * MAX_PAYLOAD_SIZE)
1597 .handshake(client_hello)
1598 .await
1599 .unwrap();
1600 p2p_stream.send(Bytes::from(message)).await.unwrap();
1601
1602 handle.await.unwrap();
1603 }
1604
1605 #[tokio::test]
1606 async fn test_handshake_disconnect() {
1607 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
1610 let local_addr = listener.local_addr().unwrap();
1611
1612 let handle = tokio::spawn(async move {
1613 let (incoming, _) = listener.accept().await.unwrap();
1615 let stream = crate::PassthroughCodec::default().framed(incoming);
1616
1617 let (server_hello, _) = eth_hello();
1618
1619 let unauthed_stream = UnauthedP2PStream::new(stream);
1620 match unauthed_stream.handshake(server_hello.clone()).await {
1621 Ok((_, hello)) => {
1622 panic!("expected handshake to fail, instead got a successful Hello: {hello:?}")
1623 }
1624 Err(P2PStreamError::MismatchedProtocolVersion(GotExpected { got, expected })) => {
1625 assert_ne!(expected, got);
1626 assert_eq!(expected, server_hello.protocol_version);
1627 }
1628 Err(other_err) => {
1629 panic!("expected mismatched protocol version error, got {other_err:?}")
1630 }
1631 }
1632 });
1633
1634 let outgoing = TcpStream::connect(local_addr).await.unwrap();
1635 let sink = crate::PassthroughCodec::default().framed(outgoing);
1636
1637 let (mut client_hello, _) = eth_hello();
1638
1639 client_hello.protocol_version = ProtocolVersion::V4;
1641
1642 let unauthed_stream = UnauthedP2PStream::new(sink);
1643 match unauthed_stream.handshake(client_hello.clone()).await {
1644 Ok((_, hello)) => {
1645 panic!("expected handshake to fail, instead got a successful Hello: {hello:?}")
1646 }
1647 Err(P2PStreamError::MismatchedProtocolVersion(GotExpected { got, expected })) => {
1648 assert_ne!(expected, got);
1649 assert_eq!(expected, client_hello.protocol_version);
1650 }
1651 Err(other_err) => {
1652 panic!("expected mismatched protocol version error, got {other_err:?}")
1653 }
1654 }
1655
1656 handle.await.unwrap();
1658 }
1659
1660 #[test]
1661 fn snappy_ping_pong_consts_match_rlp_encoding() {
1662 assert_eq!(alloy_rlp::encode(P2PMessage::Ping).as_slice(), SNAPPY_PING_MESSAGE);
1663 assert_eq!(alloy_rlp::encode(P2PMessage::Pong).as_slice(), SNAPPY_PONG_MESSAGE);
1664 }
1665
1666 #[test]
1667 fn snappy_decode_encode_ping() {
1668 let snappy_ping = b"\x02\x01\0\xc0";
1669 let ping = P2PMessage::decode(&mut &snappy_ping[..]).unwrap();
1670 assert!(matches!(ping, P2PMessage::Ping));
1671 assert_eq!(alloy_rlp::encode(ping), &snappy_ping[..]);
1672 }
1673
1674 #[test]
1675 fn snappy_decode_encode_pong() {
1676 let snappy_pong = b"\x03\x01\0\xc0";
1677 let pong = P2PMessage::decode(&mut &snappy_pong[..]).unwrap();
1678 assert!(matches!(pong, P2PMessage::Pong));
1679 assert_eq!(alloy_rlp::encode(pong), &snappy_pong[..]);
1680 }
1681}