1use crate::{
4 codec::ECIESCodec, error::ECIESErrorImpl, ECIESError, EgressECIESValue, IngressECIESValue,
5};
6use alloy_primitives::{
7 bytes::{Bytes, BytesMut},
8 B512 as PeerId,
9};
10use futures::{ready, Sink, SinkExt};
11use secp256k1::SecretKey;
12use std::{
13 fmt::Debug,
14 io,
15 pin::Pin,
16 task::{Context, Poll},
17 time::Duration,
18};
19use tokio::{
20 io::{AsyncRead, AsyncWrite},
21 time::timeout,
22};
23use tokio_stream::{Stream, StreamExt};
24use tokio_util::codec::{Decoder, Framed};
25use tracing::{instrument, trace};
26
27const HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(10);
28
29pub const DEFAULT_BACKPRESSURE_BOUNDARY: usize = 64 * 1024;
38
39#[derive(Debug)]
41#[pin_project::pin_project]
42pub struct ECIESStream<Io> {
43 #[pin]
44 stream: Framed<Io, ECIESCodec>,
45 remote_id: PeerId,
46}
47
48impl<Io> ECIESStream<Io>
49where
50 Io: AsyncRead + AsyncWrite + Unpin,
51{
52 #[instrument(level = "trace", target = "net::ecies", skip(transport, secret_key))]
54 pub async fn connect(
55 transport: Io,
56 secret_key: SecretKey,
57 remote_id: PeerId,
58 ) -> Result<Self, ECIESError> {
59 Self::connect_with_timeout(transport, secret_key, remote_id, HANDSHAKE_TIMEOUT).await
60 }
61
62 pub async fn connect_with_timeout(
64 transport: Io,
65 secret_key: SecretKey,
66 remote_id: PeerId,
67 timeout_limit: Duration,
68 ) -> Result<Self, ECIESError> {
69 timeout(timeout_limit, Self::connect_without_timeout(transport, secret_key, remote_id))
70 .await
71 .map_err(|_| ECIESError::from(ECIESErrorImpl::StreamTimeout))?
72 }
73
74 pub async fn connect_without_timeout(
76 transport: Io,
77 secret_key: SecretKey,
78 remote_id: PeerId,
79 ) -> Result<Self, ECIESError> {
80 let ecies = ECIESCodec::new_client(secret_key, remote_id)?;
81
82 let mut transport = ecies.framed(transport);
83 transport.set_backpressure_boundary(DEFAULT_BACKPRESSURE_BOUNDARY);
84
85 trace!("sending ecies auth ...");
86 transport.send(EgressECIESValue::Auth).await?;
87
88 trace!("waiting for ecies ack ...");
89
90 let msg = transport.try_next().await?;
91
92 let msg = msg.ok_or(ECIESErrorImpl::UnreadableStream)?;
96
97 trace!("parsing ecies ack ...");
98 if matches!(msg, IngressECIESValue::Ack) {
99 Ok(Self { stream: transport, remote_id })
100 } else {
101 Err(ECIESErrorImpl::InvalidHandshake {
102 expected: IngressECIESValue::Ack,
103 msg: Some(msg),
104 }
105 .into())
106 }
107 }
108
109 pub async fn incoming(transport: Io, secret_key: SecretKey) -> Result<Self, ECIESError> {
111 let ecies = ECIESCodec::new_server(secret_key)?;
112
113 trace!("incoming ecies stream");
114 let mut transport = ecies.framed(transport);
115 transport.set_backpressure_boundary(DEFAULT_BACKPRESSURE_BOUNDARY);
116 let msg = transport.try_next().await?;
117
118 trace!("receiving ecies auth");
119 let remote_id = match &msg {
120 Some(IngressECIESValue::AuthReceive(remote_id)) => *remote_id,
121 _ => {
122 return Err(ECIESErrorImpl::InvalidHandshake {
123 expected: IngressECIESValue::AuthReceive(Default::default()),
124 msg,
125 }
126 .into())
127 }
128 };
129
130 trace!("sending ecies ack");
131 transport.send(EgressECIESValue::Ack).await?;
132
133 Ok(Self { stream: transport, remote_id })
134 }
135
136 pub const fn remote_id(&self) -> PeerId {
138 self.remote_id
139 }
140}
141
142impl<Io> ECIESStream<Io> {
143 pub fn set_backpressure_boundary(&mut self, boundary: usize) {
149 self.stream.set_backpressure_boundary(boundary);
150 }
151}
152
153impl<Io> Stream for ECIESStream<Io>
154where
155 Io: AsyncRead + Unpin,
156{
157 type Item = Result<BytesMut, io::Error>;
158
159 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
160 match ready!(self.project().stream.poll_next(cx)) {
161 Some(Ok(IngressECIESValue::Message(body))) => Poll::Ready(Some(Ok(body))),
162 Some(other) => Poll::Ready(Some(Err(io::Error::other(format!(
163 "ECIES stream protocol error: expected message, received {other:?}"
164 ))))),
165 None => Poll::Ready(None),
166 }
167 }
168}
169
170impl<Io> Sink<Bytes> for ECIESStream<Io>
171where
172 Io: AsyncWrite + Unpin,
173{
174 type Error = io::Error;
175
176 fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
177 self.project().stream.poll_ready(cx)
178 }
179
180 fn start_send(self: Pin<&mut Self>, item: Bytes) -> Result<(), Self::Error> {
181 self.project().stream.start_send(EgressECIESValue::Message(item))?;
182 Ok(())
183 }
184
185 fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
186 self.project().stream.poll_flush(cx)
187 }
188
189 fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
190 self.project().stream.poll_close(cx)
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197 use reth_network_peers::pk2id;
198 use secp256k1::SECP256K1;
199 use tokio::net::{TcpListener, TcpStream};
200
201 #[tokio::test]
202 async fn can_write_and_read() {
203 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
204 let addr = listener.local_addr().unwrap();
205 let server_key = SecretKey::new(&mut rand_08::thread_rng());
206
207 let handle = tokio::spawn(async move {
208 let (incoming, _) = listener.accept().await.unwrap();
210 let mut stream = ECIESStream::incoming(incoming, server_key).await.unwrap();
211
212 let message = stream.next().await.unwrap().unwrap();
214 assert_eq!(message, Bytes::from("hello"));
215 });
216
217 let server_id = pk2id(&server_key.public_key(SECP256K1));
219
220 let client_key = SecretKey::new(&mut rand_08::thread_rng());
221 let outgoing = TcpStream::connect(addr).await.unwrap();
222 let mut client_stream =
223 ECIESStream::connect(outgoing, client_key, server_id).await.unwrap();
224 client_stream.send(Bytes::from("hello")).await.unwrap();
225
226 handle.await.unwrap();
228 }
229
230 #[tokio::test]
231 async fn connection_should_timeout() {
232 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
233 let addr = listener.local_addr().unwrap();
234 let server_key = SecretKey::new(&mut rand_08::thread_rng());
235
236 let _handle = tokio::spawn(async move {
237 tokio::time::sleep(Duration::from_secs(11)).await;
239 let (incoming, _) = listener.accept().await.unwrap();
240 let mut stream = ECIESStream::incoming(incoming, server_key).await.unwrap();
241
242 let message = stream.next().await.unwrap().unwrap();
244 assert_eq!(message, Bytes::from("hello"));
245 });
246
247 let server_id = pk2id(&server_key.public_key(SECP256K1));
249
250 let client_key = SecretKey::new(&mut rand_08::thread_rng());
251 let outgoing = TcpStream::connect(addr).await.unwrap();
252
253 let connect_result = ECIESStream::connect_with_timeout(
255 outgoing,
256 client_key,
257 server_id,
258 Duration::from_secs(1),
259 )
260 .await;
261
262 assert!(
264 matches!(connect_result, Err(e) if e.to_string() == ECIESErrorImpl::StreamTimeout.to_string())
265 );
266 }
267}