1use futures::{FutureExt, StreamExt};
2mod codec;
3
4use crate::behaviour::request_response::codec::Codec;
5use crate::error::{ConnexaResult, Error};
6use bytes::Bytes;
7use futures::channel::mpsc::Sender as MpscSender;
8use futures::channel::oneshot::Sender as OneshotSender;
9use futures::future::BoxFuture;
10use futures::stream::BoxStream;
11use futures_timer::Delay;
12use libp2p::core::Endpoint;
13use libp2p::core::transport::PortUse;
14use libp2p::request_response::{
15 InboundFailure, InboundRequestId, OutboundFailure, OutboundRequestId, ProtocolSupport,
16 ResponseChannel,
17};
18use libp2p::swarm::{
19 ConnectionDenied, ConnectionId, FromSwarm, NetworkBehaviour, THandler, THandlerInEvent,
20 THandlerOutEvent, ToSwarm,
21};
22use libp2p::{Multiaddr, PeerId, StreamProtocol, request_response};
23use pollable_map::futures::FutureMap;
24use pollable_map::optional::Optional;
25use std::collections::HashMap;
26use std::collections::hash_map::Entry;
27use std::io::{Error as IoError, ErrorKind as IoErrorKind};
28use std::task::{Context, Poll};
29use std::time::Duration;
30
31const CHANNEL_LIMIT: usize = 256;
32const TIMEOUT: Duration = Duration::from_secs(30);
33
34#[derive(Debug, Clone)]
35pub struct RequestResponseConfig {
36 pub protocol: String,
37 pub timeout: Option<Duration>,
38 pub max_request_size: usize,
39 pub max_response_size: usize,
40 pub concurrent_streams: Option<usize>,
41 pub channel_buffer: usize,
42 pub protocol_direction: RequestResponseDirection,
43}
44
45#[derive(Debug, Clone, Default)]
46pub enum RequestResponseDirection {
47 In,
49 Out,
51 #[default]
53 Both,
54}
55
56impl From<RequestResponseDirection> for ProtocolSupport {
57 fn from(direction: RequestResponseDirection) -> Self {
58 match direction {
59 RequestResponseDirection::In => ProtocolSupport::Inbound,
60 RequestResponseDirection::Out => ProtocolSupport::Outbound,
61 RequestResponseDirection::Both => ProtocolSupport::Full,
62 }
63 }
64}
65
66impl Default for RequestResponseConfig {
67 fn default() -> Self {
68 Self {
69 protocol: "/libp2p/request-response".into(),
70 timeout: None,
71 max_request_size: 512 * 1024,
72 max_response_size: 2 * 1024 * 1024,
73 concurrent_streams: None,
74 channel_buffer: 128,
75 protocol_direction: RequestResponseDirection::default(),
76 }
77 }
78}
79
80impl RequestResponseConfig {
81 pub fn new(protocol: impl Into<String>) -> Self {
82 let protocol = protocol.into();
83 RequestResponseConfig {
84 protocol,
85 ..Default::default()
86 }
87 }
88
89 pub fn set_timeout(mut self, timeout: Duration) -> Self {
91 self.timeout = Some(timeout);
92 self
93 }
94
95 pub fn set_max_request_size(mut self, size: usize) -> Self {
97 self.max_request_size = size;
98 self
99 }
100
101 pub fn set_max_response_size(mut self, size: usize) -> Self {
103 self.max_response_size = size;
104 self
105 }
106
107 pub fn set_concurrent_streams(mut self, size: usize) -> Self {
109 self.concurrent_streams = Some(size);
110 self
111 }
112
113 pub fn set_channel_buffer(mut self, size: usize) -> Self {
115 self.channel_buffer = size;
116 self
117 }
118
119 pub fn set_protocol_direction(mut self, direction: RequestResponseDirection) -> Self {
120 self.protocol_direction = direction;
121 self
122 }
123}
124
125pub struct Behaviour {
126 pending_request: HashMap<PeerId, HashMap<InboundRequestId, ResponseChannel<Bytes>>>,
127
128 pending_response:
129 HashMap<PeerId, HashMap<OutboundRequestId, OneshotSender<ConnexaResult<Bytes>>>>,
130 broadcast_request: Vec<MpscSender<(PeerId, InboundRequestId, Bytes)>>,
131 rr_behaviour: request_response::Behaviour<Codec>,
132
133 gc_timer: Optional<Delay>,
134
135 channel_buffer: usize,
136}
137
138impl Behaviour {
139 pub fn new(config: RequestResponseConfig) -> Self {
140 let mut cfg = request_response::Config::default()
141 .with_request_timeout(config.timeout.unwrap_or(Duration::from_secs(120)));
142 if let Some(size) = config.concurrent_streams {
143 cfg = cfg.with_max_concurrent_streams(size);
144 }
145
146 let protocol = config.protocol;
147
148 let st_protocol = StreamProtocol::try_from_owned(protocol).expect("valid protocol");
149
150 let protocol = vec![(st_protocol, config.protocol_direction.into())];
151
152 let codec = Codec::new(config.max_request_size, config.max_response_size);
153
154 let rr_behaviour = request_response::Behaviour::with_codec(codec, protocol, cfg);
155
156 Self {
157 pending_response: HashMap::new(),
158 pending_request: HashMap::new(),
159 broadcast_request: Vec::new(),
160 rr_behaviour,
161 gc_timer: Optional::default(),
162 channel_buffer: config.channel_buffer,
163 }
164 }
165
166 pub fn subscribe(
167 &mut self,
168 ) -> futures::channel::mpsc::Receiver<(PeerId, InboundRequestId, Bytes)> {
169 let (tx, rx) = futures::channel::mpsc::channel(self.channel_buffer);
170 self.broadcast_request.push(tx);
171 rx
172 }
173
174 pub fn send_request(
175 &mut self,
176 peer_id: PeerId,
177 request: impl Into<Bytes>,
178 ) -> BoxFuture<'static, ConnexaResult<Bytes>> {
179 let mut st = self.send_requests([peer_id], request);
181 Box::pin(async move {
182 match st.next().await {
183 Some((_, result)) => result,
186 None => Err(std::io::Error::from(std::io::ErrorKind::BrokenPipe).into()),
187 }
188 })
189 }
190
191 pub fn send_response(
192 &mut self,
193 peer_id: PeerId,
194 id: InboundRequestId,
195 response: impl Into<Bytes>,
196 ) -> ConnexaResult<()> {
197 let pending_list = self
198 .pending_request
199 .get_mut(&peer_id)
200 .ok_or_else(|| Error::NotFound("pending request".into()))?;
201
202 let ch = pending_list
203 .remove(&id)
204 .ok_or_else(|| Error::NotFound("pending request".into()))?;
205
206 let response = response.into();
207
208 if self.rr_behaviour.send_response(ch, response).is_err() {
209 return Err(IoError::new(
210 IoErrorKind::BrokenPipe,
211 "unable to send response. request either timed out, connection dropped, or unexpected behaviour occurred",
212 )
213 .into());
214 }
215
216 Ok(())
217 }
218
219 pub fn send_requests(
220 &mut self,
221 peers: impl IntoIterator<Item = PeerId>,
222 request: impl Into<Bytes>,
223 ) -> BoxStream<'static, (PeerId, ConnexaResult<Bytes>)> {
224 let request = request.into();
225 let mut oneshots = FutureMap::new();
226 for peer_id in peers {
227 let id = self.rr_behaviour.send_request(&peer_id, request.clone());
228 let (tx, rx) = futures::channel::oneshot::channel();
229 self.pending_response
230 .entry(peer_id)
231 .or_default()
232 .insert(id, tx);
233 oneshots.insert(
234 id,
235 rx.map(move |r| {
236 let result = match r {
237 Ok(Ok(bytes)) => Ok(bytes),
238 Ok(Err(e)) => Err(e),
239 Err(e) => {
240 Err(std::io::Error::new(std::io::ErrorKind::BrokenPipe, e).into())
241 }
242 };
243 (peer_id, result)
244 }),
245 );
246 }
247 oneshots.map(|(_id, item)| item).boxed()
248 }
249
250 fn process_request(
251 &mut self,
252 id: InboundRequestId,
253 peer_id: PeerId,
254 request: Bytes,
255 response_channel: ResponseChannel<Bytes>,
256 ) {
257 if self.broadcast_request.is_empty() {
258 tracing::warn!(%peer_id, request_id=%id, "no subscribers listening to request. dropping request");
260 return;
261 }
262
263 match self.broadcast_request.len() > CHANNEL_LIMIT {
265 true => {
266 if self.gc_timer.is_none() {
268 self.gc_timer.replace(Delay::new(TIMEOUT));
269 }
270 }
271 false => {
272 self.gc_timer.take();
273 self.broadcast_request.retain(|ch| !ch.is_closed());
274 }
275 }
276
277 self.pending_request
278 .entry(peer_id)
279 .or_default()
280 .insert(id, response_channel);
281
282 for tx in self.broadcast_request.iter_mut() {
283 if let Err(e) = tx.try_send((peer_id, id, request.clone())) {
284 tracing::warn!(%peer_id, request_id=%id, error=%e, "unable to send request to subscriber");
285 continue;
286 }
287 }
288 }
289
290 fn process_response(&mut self, id: OutboundRequestId, peer_id: PeerId, response: Bytes) {
291 let Some(list) = self.pending_response.get_mut(&peer_id) else {
292 return;
293 };
294
295 let Some(ch) = list.remove(&id) else {
296 tracing::warn!(%peer_id, request_id=%id, "no pending request available that is awaiting for a response. dropping response");
297 return;
298 };
299
300 if list.is_empty() {
301 self.pending_response.remove(&peer_id);
302 }
303
304 if ch.send(Ok(response)).is_err() {
305 tracing::warn!(%peer_id, request_id=%id, "unable to send response");
306 }
307 }
308
309 fn process_outbound_failure(
310 &mut self,
311 id: OutboundRequestId,
312 peer_id: PeerId,
313 error: OutboundFailure,
314 ) {
315 tracing::error!(%peer_id, request_id=%id, %error, "outbound failure");
316
317 if let Entry::Occupied(mut entry) = self.pending_response.entry(peer_id) {
318 let list = entry.get_mut();
319
320 if let Some(tx) = list.remove(&id) {
321 _ = tx.send(Err(Error::RequestResponse(error)));
322 }
323
324 if list.is_empty() {
325 entry.remove();
326 }
327 }
328 }
329
330 fn process_inbound_failure(
331 &mut self,
332 id: InboundRequestId,
333 peer_id: PeerId,
334 inbound_failure: InboundFailure,
335 ) {
336 tracing::error!(%peer_id, request_id=%id, error=%inbound_failure, "inbound failure");
337 if let Entry::Occupied(mut entry) = self.pending_request.entry(peer_id) {
338 let list = entry.get_mut();
339 list.remove(&id);
340 if list.is_empty() {
341 entry.remove();
342 }
343 }
344 }
345}
346
347impl NetworkBehaviour for Behaviour {
348 type ConnectionHandler =
349 <request_response::Behaviour<Codec> as NetworkBehaviour>::ConnectionHandler;
350 type ToSwarm = ();
351
352 fn handle_pending_inbound_connection(
353 &mut self,
354 connection_id: ConnectionId,
355 local_addr: &Multiaddr,
356 remote_addr: &Multiaddr,
357 ) -> Result<(), ConnectionDenied> {
358 self.rr_behaviour
359 .handle_pending_inbound_connection(connection_id, local_addr, remote_addr)
360 }
361
362 fn handle_pending_outbound_connection(
363 &mut self,
364 connection_id: ConnectionId,
365 maybe_peer: Option<PeerId>,
366 addresses: &[Multiaddr],
367 effective_role: Endpoint,
368 ) -> Result<Vec<Multiaddr>, ConnectionDenied> {
369 self.rr_behaviour.handle_pending_outbound_connection(
370 connection_id,
371 maybe_peer,
372 addresses,
373 effective_role,
374 )
375 }
376
377 fn handle_established_inbound_connection(
378 &mut self,
379 connection_id: ConnectionId,
380 peer: PeerId,
381 local_addr: &Multiaddr,
382 remote_addr: &Multiaddr,
383 ) -> Result<THandler<Self>, ConnectionDenied> {
384 self.rr_behaviour.handle_established_inbound_connection(
385 connection_id,
386 peer,
387 local_addr,
388 remote_addr,
389 )
390 }
391
392 fn handle_established_outbound_connection(
393 &mut self,
394 connection_id: ConnectionId,
395 peer: PeerId,
396 addr: &Multiaddr,
397 role_override: Endpoint,
398 reuse: PortUse,
399 ) -> Result<THandler<Self>, ConnectionDenied> {
400 self.rr_behaviour.handle_established_outbound_connection(
401 connection_id,
402 peer,
403 addr,
404 role_override,
405 reuse,
406 )
407 }
408
409 fn on_connection_handler_event(
410 &mut self,
411 peer_id: PeerId,
412 connection_id: ConnectionId,
413 event: THandlerOutEvent<Self>,
414 ) {
415 self.rr_behaviour
416 .on_connection_handler_event(peer_id, connection_id, event)
417 }
418
419 fn on_swarm_event(&mut self, event: FromSwarm) {
420 self.rr_behaviour.on_swarm_event(event);
421 }
422
423 fn poll(&mut self, cx: &mut Context) -> Poll<ToSwarm<Self::ToSwarm, THandlerInEvent<Self>>> {
424 if self.gc_timer.poll_unpin(cx).is_ready() {
425 self.broadcast_request.retain(|ch| !ch.is_closed());
426 if self.broadcast_request.len() > CHANNEL_LIMIT {
427 self.gc_timer.replace(Delay::new(TIMEOUT));
428 }
429 }
430
431 while let Poll::Ready(event) = self.rr_behaviour.poll(cx) {
432 match event {
433 ToSwarm::GenerateEvent(request_response::Event::Message {
434 connection_id: _,
435 peer: peer_id,
436 message,
437 }) => match message {
438 request_response::Message::Response {
439 request_id,
440 response,
441 } => self.process_response(request_id, peer_id, response),
442 request_response::Message::Request {
443 request_id,
444 request,
445 channel,
446 } => {
447 self.process_request(request_id, peer_id, request, channel);
448 }
449 },
450 ToSwarm::GenerateEvent(request_response::Event::ResponseSent {
451 connection_id: _,
452 peer: peer_id,
453 request_id,
454 }) => {
455 tracing::trace!(%peer_id, %request_id, "response sent");
456 }
457 ToSwarm::GenerateEvent(request_response::Event::OutboundFailure {
458 connection_id: _,
459 peer,
460 request_id,
461 error,
462 }) => {
463 tracing::error!(peer_id = %peer, %request_id, ?error, direction="outbound");
464 self.process_outbound_failure(request_id, peer, error);
465 }
466 ToSwarm::GenerateEvent(request_response::Event::InboundFailure {
467 connection_id: _,
468 peer,
469 request_id,
470 error,
471 }) => {
472 tracing::error!(peer_id = %peer, %request_id, ?error, direction="inbound");
473 self.process_inbound_failure(request_id, peer, error);
474 }
475 other => {
476 let new_to_swarm =
477 other.map_out(|_| unreachable!("we manually map `GenerateEvent` variants"));
478 return Poll::Ready(new_to_swarm);
479 }
480 };
481 }
482
483 Poll::Pending
484 }
485}
486
487#[cfg(test)]
488mod tests {
489 use super::*;
490 use libp2p::core::muxing::StreamMuxerBox;
491 use libp2p::core::transport::MemoryTransport;
492 use libp2p::core::upgrade::Version;
493 use libp2p::multiaddr::Protocol;
494 use libp2p::swarm::SwarmEvent;
495 use libp2p::{Swarm, SwarmBuilder};
496 use libp2p::{Transport, noise, yamux};
497
498 #[tokio::test]
499 async fn send_single_request() {
500 let mut node_a = swarm_node().await;
501 let mut node_b = swarm_node().await;
502
503 let peer_id_a = *node_a.local_peer_id();
504
505 let peer_id_b = *node_b.local_peer_id();
506 let addr_b = node_b.listeners().collect::<Vec<_>>()[0];
507
508 node_a.add_peer_address(peer_id_b, addr_b.clone());
509
510 let mut response_fut = node_a.behaviour_mut().send_request(peer_id_b, "ping");
511 let mut node_b_request_listener = node_b.behaviour_mut().subscribe();
512
513 let mut received_request = false;
514 let mut received_response = false;
515
516 loop {
517 tokio::select! {
518 _event = node_a.select_next_some() => {},
519 _event = node_b.select_next_some() => {},
520 Some((sender_peer_id, id, request)) = node_b_request_listener.next() => {
521 assert_eq!(sender_peer_id, peer_id_a);
522 assert_eq!(request, "ping");
523 received_request = true;
524 node_b.behaviour_mut().send_response(peer_id_a, id, "pong").expect("channel still active");
525 }
526 response = &mut response_fut => {
527 let response = response.expect("valid response");
528 assert_eq!(response, "pong");
529 received_response = true;
530 }
531 }
532
533 if received_request && received_response {
534 break;
535 }
536 }
537 }
538
539 #[tokio::test]
540 async fn send_single_request_to_multiple_peers() {
541 let mut node_a = swarm_node().await;
542 let mut node_b = swarm_node().await;
543 let mut node_c = swarm_node().await;
544
545 let peer_id_a = *node_a.local_peer_id();
546
547 let peer_id_b = *node_b.local_peer_id();
548 let addr_b = node_b.listeners().collect::<Vec<_>>()[0];
549
550 let peer_id_c = *node_c.local_peer_id();
551 let addr_c = node_c.listeners().collect::<Vec<_>>()[0];
552
553 node_a.add_peer_address(peer_id_b, addr_b.clone());
554 node_a.add_peer_address(peer_id_c, addr_c.clone());
555
556 let mut response_st = node_a
557 .behaviour_mut()
558 .send_requests([peer_id_b, peer_id_c], "ping");
559 let mut node_b_request_listener = node_b.behaviour_mut().subscribe();
560 let mut node_c_request_listener = node_c.behaviour_mut().subscribe();
561
562 let mut received_request_for_b = false;
563 let mut received_request_for_c = false;
564 let mut received_response_from_b = false;
565 let mut received_response_from_c = false;
566
567 loop {
568 tokio::select! {
569 _event = node_a.select_next_some() => {},
570 _event = node_b.select_next_some() => {},
571 _event = node_c.select_next_some() => {},
572 Some((sender_peer_id, id, request)) = node_b_request_listener.next() => {
573 assert_eq!(sender_peer_id, peer_id_a);
574 assert_eq!(request, "ping");
575 received_request_for_b = true;
576 node_b.behaviour_mut().send_response(peer_id_a, id, "pong_b").expect("channel still active");
577 }
578 Some((sender_peer_id, id, request)) = node_c_request_listener.next() => {
579 assert_eq!(sender_peer_id, peer_id_a);
580 assert_eq!(request, "ping");
581 received_request_for_c = true;
582 node_c.behaviour_mut().send_response(peer_id_a, id, "pong_c").expect("channel still active");
583 }
584 Some((peer_id, response)) = response_st.next() => {
585 match response {
586 Ok(response) if response == "pong_b" => {
587 assert_eq!(peer_id, peer_id_b);
588 received_response_from_b = true;
589 },
590 Ok(response) if response == "pong_c" => {
591 assert_eq!(peer_id, peer_id_c);
592 received_response_from_c = true;
593 }
594 _ => unreachable!(),
595 }
596 }
597 }
598
599 if received_request_for_b
600 && received_request_for_c
601 && received_response_from_b
602 && received_response_from_c
603 {
604 break;
605 }
606 }
607 }
608
609 async fn swarm_node() -> Swarm<Behaviour> {
610 let mut swarm = SwarmBuilder::with_new_identity()
611 .with_tokio()
612 .with_other_transport(|k| {
613 let auth_upgrade = noise::Config::new(k)?;
614 let multiplex_upgrade = yamux::Config::default();
615 let memory_transport = MemoryTransport::new();
616 let transport = memory_transport
617 .upgrade(Version::V1)
618 .authenticate(auth_upgrade)
619 .multiplex(multiplex_upgrade)
620 .map(|(peer, muxer), _| (peer, StreamMuxerBox::new(muxer)))
621 .boxed();
622 Ok(transport)
623 })
624 .unwrap()
625 .with_behaviour(|_| Ok(Behaviour::new(Default::default())))
626 .unwrap()
627 .build();
628
629 let id = swarm
630 .listen_on(Multiaddr::empty().with(Protocol::Memory(0)))
631 .expect("valid listener");
632
633 if let SwarmEvent::NewListenAddr {
634 listener_id,
635 address: _,
636 } = swarm.next().await.expect("swarm havent dropped")
637 {
638 assert_eq!(listener_id, id);
639 }
640
641 swarm
642 }
643}