1use std::collections::{HashMap, HashSet};
5use std::net::SocketAddr;
6use std::sync::Arc;
7
8use display_error_chain::ErrorChainExt;
9use parking_lot::RwLock;
10use slim_config::client::ClientConfig;
11use slim_config::client::TransportChannel;
12use slim_config::component::configuration::Configuration;
13use tokio::sync::mpsc::{self, Sender};
14use tokio::sync::oneshot;
15use tokio::task::JoinHandle;
16use tokio_stream::wrappers::ReceiverStream;
17use tokio_stream::{Stream, StreamExt};
18use tokio_util::sync::CancellationToken;
19
20use tonic::Status;
21use tracing::{Instrument, debug, error, info, warn};
22
23cfg_if::cfg_if! {
27 if #[cfg(not(target_arch = "wasm32"))] {
28 use std::pin::Pin;
29
30 use crate::api::DataPlaneServiceServer;
31 use crate::api::proto::dataplane::v1::data_plane_service_client::DataPlaneServiceClient;
32 use crate::api::proto::dataplane::v1::data_plane_service_server::DataPlaneService;
33 use slim_config::server::ServerConfig;
34 use slim_config::server_handler::ServerHandler;
35 use slim_config::websocket::server as websocket_server;
36 use slim_config::websocket::server::AcceptedWebSocketConnection;
37 use tonic::{Request, Response};
38 }
39}
40
41#[cfg(feature = "otel_tracing")]
42use crate::otel_tracing;
43
44use crate::api::ProtoPublishType as PublishType;
45use crate::api::ProtoSubscribeType as SubscribeType;
46use crate::api::ProtoSubscriptionAckType as SubscriptionAckType;
47use crate::api::ProtoUnsubscribeType as UnsubscribeType;
48use crate::api::proto::dataplane::v1::Message;
49
50use crate::api::{
51 LinkNegotiationPayload, ProtoLink, ProtoLinkMessageType as LinkType, ProtoLinkType,
52 ProtoMessage, ProtoName,
53};
54use crate::connection::{Channel, Connection};
55use crate::errors::{DataPathError, MessageContext};
56use crate::forwarder::Forwarder;
57use crate::messages::utils::SlimHeaderFlags;
58use crate::sync::peer as sync_peer;
59use crate::sync::remote::{RemoteSync, SubscriptionInfo};
60use crate::tables::connection_table::ConnectionTable;
61use crate::tables::subscription_table::SubscriptionTableImpl;
62use crate::tables::{ConnType, MatchFilter};
63use crate::websocket;
64
65struct SubscriptionOutcome {
67 transition: bool,
69 is_peer_conn: bool,
71 forward_conn: Option<u64>,
73}
74
75#[cfg(test)]
77static ENV_LOCK: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(());
78
79#[derive(Debug)]
80struct MessageProcessorInternal {
81 forwarder: Forwarder<Connection>,
83
84 drain_signal: parking_lot::RwLock<Option<drain::Signal>>,
86
87 drain_watch: parking_lot::RwLock<Option<drain::Watch>>,
89
90 tx_control_plane: RwLock<Option<Sender<Result<Message, Status>>>>,
92
93 remote_sync: RemoteSync,
95
96 service_id: String,
98
99 deployment_name: String,
103
104 #[cfg_attr(target_arch = "wasm32", allow(dead_code))]
107 server_require_header_mac: bool,
108
109 negotiation_timeout: std::time::Duration,
111
112 relay_peer_publishes: bool,
116
117 peer_sync: parking_lot::RwLock<crate::sync::PeerSync>,
120
121 server_enforce_pqc: bool,
122}
123
124#[derive(Debug, Clone)]
125pub struct MessageProcessor {
126 internal: Arc<MessageProcessorInternal>,
127}
128
129impl Default for MessageProcessor {
130 fn default() -> Self {
131 Self::new_with_service_id(String::new(), false)
132 }
133}
134
135enum StreamSetup {
140 Registered(u64),
142 Pending {
144 connection: Box<Connection>,
145 existing_index: Option<u64>,
146 },
147}
148
149impl MessageProcessor {
150 pub fn new_with_service_id(service_id: String, enforce_pqc: bool) -> Self {
151 Self::new_internal(
152 service_id,
153 String::new(),
154 false,
155 enforce_pqc,
156 std::time::Duration::from_secs(5),
157 false,
158 )
159 }
160
161 #[cfg(not(target_arch = "wasm32"))]
163 pub fn new_with_server_config(
164 service_id: String,
165 deployment_name: String,
166 server_config: &ServerConfig,
167 enforce_pqc: bool,
168 relay_peer_publishes: bool,
169 ) -> Self {
170 Self::new_internal(
171 service_id,
172 deployment_name,
173 server_config.require_header_mac,
174 enforce_pqc,
175 std::time::Duration::from_secs(server_config.negotiation_timeout_secs),
176 relay_peer_publishes,
177 )
178 }
179
180 fn new_internal(
181 service_id: String,
182 deployment_name: String,
183 server_require_header_mac: bool,
184 server_enforce_pqc: bool,
185 negotiation_timeout: std::time::Duration,
186 relay_peer_publishes: bool,
187 ) -> Self {
188 let (signal, watch) = drain::channel();
189 let internal = MessageProcessorInternal {
190 forwarder: Forwarder::new(),
191 drain_signal: RwLock::new(Some(signal)),
192 drain_watch: RwLock::new(Some(watch)),
193 tx_control_plane: RwLock::new(None),
194 remote_sync: RemoteSync::default(),
195 service_id,
196 deployment_name,
197 server_require_header_mac,
198 server_enforce_pqc,
199 negotiation_timeout,
200 relay_peer_publishes,
201 peer_sync: parking_lot::RwLock::new(crate::sync::PeerSync::standalone()),
202 };
203 Self {
204 internal: Arc::new(internal),
205 }
206 }
207
208 pub fn new() -> Self {
209 Self::default()
210 }
211
212 #[cfg(not(target_arch = "wasm32"))]
217 pub async fn run_server(
218 &self,
219 config: &ServerConfig,
220 ) -> Result<CancellationToken, DataPathError> {
221 debug!(%config, "starting dataplane server");
222
223 if config.require_header_mac != self.internal.server_require_header_mac {
224 warn!(
225 configured = config.require_header_mac,
226 processor = self.internal.server_require_header_mac,
227 "server require_header_mac differs from MessageProcessor; inbound connections use the processor value set at construction (prefer MessageProcessor::new_with_server_config)",
228 );
229 }
230
231 if config.tls_setting.config.enforce_pqc != self.internal.server_enforce_pqc {
232 warn!(
233 configured = config.tls_setting.config.enforce_pqc,
234 processor = self.internal.server_enforce_pqc,
235 "server enforce_pqc differs from MessageProcessor; inbound connections use the processor value set at construction"
236 );
237 }
238
239 let watch = self.get_drain_watch()?;
240 config
241 .run_server(watch, Arc::new(self.clone()))
242 .await
243 .map_err(Into::into)
244 }
245
246 #[cfg(not(target_arch = "wasm32"))]
247 async fn handle_websocket_accepted(&self, accepted: AcceptedWebSocketConnection) {
248 let cancellation_token = CancellationToken::new();
249 let streams =
250 websocket::spawn_transport_tasks(accepted.websocket, cancellation_token.clone());
251
252 let connection = Connection::new(ConnType::Remote, Channel::Client(streams.outbound))
253 .with_remote_addr(accepted.remote_addr)
254 .with_local_addr(accepted.local_addr)
255 .with_require_header_mac(self.internal.server_require_header_mac)
256 .with_cancellation_token(Some(cancellation_token.clone()));
257
258 debug!(
259 remote = ?connection.remote_addr(),
260 local = ?connection.local_addr(),
261 "new websocket connection received from remote",
262 );
263 info!(telemetry = true, counter.num_active_connections = 1);
264
265 if let Err(err) = self.process_stream(
266 streams.inbound,
267 StreamSetup::Pending {
268 connection: Box::new(connection),
269 existing_index: None,
270 },
271 None,
272 cancellation_token,
273 ConnType::Remote,
274 false,
275 ) {
276 error!(error = %err.chain(), "error starting websocket processing stream");
277 }
278 }
279
280 pub fn signal_drain(&self) {
286 self.internal.drain_signal.write().take();
287 self.internal.drain_watch.write().take();
288 }
289
290 pub async fn shutdown(&self) -> Result<(), DataPathError> {
291 let signal = self
293 .internal
294 .drain_signal
295 .write()
296 .take()
297 .ok_or(DataPathError::AlreadyClosedError)?;
298
299 self.internal.drain_watch.write().take();
301
302 signal.drain().await;
304
305 Ok(())
306 }
307
308 fn set_tx_control_plane(&self, tx: Sender<Result<Message, Status>>) {
309 let mut tx_guard = self.internal.tx_control_plane.write();
310 *tx_guard = Some(tx);
311 }
312
313 fn get_tx_control_plane(&self) -> Option<Sender<Result<Message, Status>>> {
314 let tx_guard = self.internal.tx_control_plane.read();
315 tx_guard.clone()
316 }
317
318 pub fn forwarder(&self) -> &Forwarder<Connection> {
319 &self.internal.forwarder
320 }
321
322 pub(crate) fn remote_sync(&self) -> &RemoteSync {
323 &self.internal.remote_sync
324 }
325
326 pub(crate) fn verify_remote_header_mac(
328 &self,
329 conn_index: u64,
330 message: &Message,
331 enforce_strict_verification: bool,
332 ) -> Result<(), DataPathError> {
333 let conn = self
334 .forwarder()
335 .get_connection(conn_index)
336 .ok_or(DataPathError::ConnectionNotFound(conn_index))?;
337 if !matches!(conn.connection_type(), ConnType::Remote | ConnType::Edge) {
338 return Ok(());
339 }
340 let header = message
341 .try_get_slim_header()
342 .ok_or(DataPathError::UnknownMsgType)?;
343
344 let has_wire_mac = header.header_mac.as_ref().is_some_and(|m| !m.is_empty());
345
346 if (message.is_subscribe() || message.is_unsubscribe()) && !has_wire_mac {
351 if enforce_strict_verification {
352 return Err(DataPathError::NegotiationError(
353 "empty HMAC is not allowed in strict verification mode".to_string(),
354 ));
355 }
356 return Ok(());
357 }
358
359 let Some(mac) = conn.header_hmac() else {
360 if enforce_strict_verification {
361 return Err(DataPathError::NegotiationError(
362 "strict header MAC required but link HMAC session is not installed".to_string(),
363 ));
364 }
365 if message.is_publish() && has_wire_mac {
369 return Err(DataPathError::HeaderMacAwaitingLinkNegotiation(conn_index));
370 }
371 return Ok(());
372 };
373 let link_id = conn
374 .link_id()
375 .filter(|id| !id.is_empty())
376 .ok_or(DataPathError::HeaderMacAwaitingLinkNegotiation(conn_index))?;
377 mac.verify_slim_header(header, &link_id)
378 .map_err(DataPathError::HeaderIntegrity)
379 }
380
381 pub(crate) fn get_drain_watch(&self) -> Result<drain::Watch, DataPathError> {
382 self.internal
383 .drain_watch
384 .read()
385 .clone()
386 .ok_or(DataPathError::AlreadyClosedError)
387 }
388
389 async fn restore_remote_subscriptions(
392 &self,
393 remote_subs: &HashSet<SubscriptionInfo>,
394 conn_index: u64,
395 restore_tracking: bool,
396 ) {
397 self.remote_sync()
398 .restore(self, remote_subs, conn_index, restore_tracking)
399 .await;
400 }
401
402 async fn try_to_connect(
403 &self,
404 client_config: ClientConfig,
405 local: Option<SocketAddr>,
406 remote: Option<SocketAddr>,
407 existing_conn_index: Option<u64>,
408 ) -> Result<(JoinHandle<()>, u64), DataPathError> {
409 client_config.validate()?;
410
411 let mut watch = std::pin::pin!(self.get_drain_watch()?.signaled());
412 let channel = tokio::select! {
413 _ = &mut watch => {
414 return Err(DataPathError::ShuttingDownError);
415 }
416 res = client_config.to_channel() => {
417 res?
418 }
419 };
420
421 let cancellation_token = CancellationToken::new();
422 let link_id = client_config.link_id.clone();
423
424 match channel {
425 #[cfg(target_arch = "wasm32")]
428 TransportChannel::Grpc(never) => match never {},
429 #[cfg(not(target_arch = "wasm32"))]
430 TransportChannel::Grpc(grpc_channel) => {
431 let mut client = DataPlaneServiceClient::new(grpc_channel);
432 let (tx, rx) = mpsc::channel(128);
433 let stream = client
434 .open_channel(Request::new(ReceiverStream::new(rx)))
435 .await?;
436
437 let (handle, conn_index_rx) = self.register_remote_connection(
438 stream.into_inner(),
439 Channel::Client(tx),
440 &client_config,
441 local,
442 remote,
443 existing_conn_index,
444 cancellation_token,
445 Some(link_id.clone()),
446 )?;
447
448 let conn_index = conn_index_rx.await.map_err(|_| {
449 DataPathError::NegotiationError(
450 "negotiation task terminated unexpectedly".to_string(),
451 )
452 })??;
453
454 if matches!(client_config.connection_type, ConnType::Peer) {
457 let fwd = self.peer_sync();
458 if !fwd.has_peer_state() {
459 fwd.add_peer_conn_and_sync(self, conn_index);
460 }
461 }
462
463 Ok((handle, conn_index))
464 }
465 TransportChannel::Websocket(ws_channel) => {
466 let websocket = ws_channel
467 .take_websocket()
468 .expect("websocket channel already consumed");
469 let streams =
470 websocket::spawn_transport_tasks(websocket, cancellation_token.clone());
471
472 let (handle, conn_index_rx) = self.register_remote_connection(
473 streams.inbound,
474 Channel::Client(streams.outbound),
475 &client_config,
476 local.or(ws_channel.local_addr()),
477 remote.or(ws_channel.remote_addr()),
478 existing_conn_index,
479 cancellation_token,
480 Some(link_id.clone()),
481 )?;
482
483 let conn_index = conn_index_rx.await.map_err(|_| {
484 DataPathError::NegotiationError(
485 "negotiation task terminated unexpectedly".to_string(),
486 )
487 })??;
488
489 if matches!(client_config.connection_type, ConnType::Peer) {
492 let fwd = self.peer_sync();
493 if !fwd.has_peer_state() {
494 fwd.add_peer_conn_and_sync(self, conn_index);
495 }
496 }
497
498 Ok((handle, conn_index))
499 }
500 }
501 }
502
503 #[allow(clippy::too_many_arguments)]
509 fn register_remote_connection<S>(
510 &self,
511 inbound: S,
512 outbound: Channel,
513 client_config: &ClientConfig,
514 local: Option<SocketAddr>,
515 remote: Option<SocketAddr>,
516 existing_conn_index: Option<u64>,
517 cancellation_token: CancellationToken,
518 link_id: Option<String>,
519 ) -> Result<
520 (
521 JoinHandle<()>,
522 oneshot::Receiver<Result<u64, DataPathError>>,
523 ),
524 DataPathError,
525 >
526 where
527 S: Stream<Item = Result<Message, Status>> + Unpin + Send + 'static,
528 {
529 let mut connection = Connection::new(client_config.connection_type, outbound)
530 .with_local_addr(local)
531 .with_remote_addr(remote)
532 .with_config_data(Some(client_config.clone()))
533 .with_require_header_mac(client_config.require_header_mac)
534 .with_cancellation_token(Some(cancellation_token.clone()));
535 if let Some(link_id) = link_id {
536 connection = connection.with_link_id(link_id);
537 }
538
539 debug!(
540 remote = ?connection.remote_addr(),
541 local = ?connection.local_addr(),
542 ?client_config.connection_type,
543 "new connection initiated locally",
544 );
545
546 let (handle, conn_index_rx) = self.process_stream(
547 inbound,
548 StreamSetup::Pending {
549 connection: Box::new(connection),
550 existing_index: existing_conn_index,
551 },
552 Some(client_config.clone()),
553 cancellation_token,
554 client_config.connection_type,
555 false,
556 )?;
557
558 Ok((handle, conn_index_rx))
559 }
560
561 pub async fn connect(
562 &self,
563 client_config: ClientConfig,
564 local: Option<SocketAddr>,
565 remote: Option<SocketAddr>,
566 ) -> Result<(JoinHandle<()>, u64), DataPathError> {
567 self.try_to_connect(client_config, local, remote, None)
568 .await
569 }
570
571 pub fn disconnect(&self, conn: u64) -> Result<ClientConfig, DataPathError> {
572 let connection = match self.forwarder().get_connection(conn) {
573 Some(c) => c,
574 None => {
575 error!(%conn, "error handling disconnect: connection unknown");
576 return Err(DataPathError::DisconnectionError(conn));
577 }
578 };
579
580 let token = match connection.cancellation_token() {
581 Some(t) => t,
582 None => {
583 error!(%conn, "error handling disconnect: missing cancellation token");
584 return Err(DataPathError::DisconnectionError(conn));
585 }
586 };
587
588 token.cancel();
590
591 connection
592 .config_data()
593 .cloned()
594 .ok_or(DataPathError::DisconnectionError(conn))
595 }
596
597 #[tracing::instrument(skip_all, fields(service_id = %self.internal.service_id))]
598 pub fn register_local_connection(
599 &self,
600 from_control_plane: bool,
601 ) -> Result<
602 (
603 u64,
604 tokio::sync::mpsc::Sender<Result<Message, Status>>,
605 tokio::sync::mpsc::Receiver<Result<Message, Status>>,
606 ),
607 DataPathError,
608 > {
609 let (tx1, rx1) = mpsc::channel(512);
611
612 debug!("establishing new local app connection");
613
614 let (tx2, rx2) = mpsc::channel(512);
616
617 if from_control_plane && self.get_tx_control_plane().is_none() {
620 self.set_tx_control_plane(tx2.clone());
621 }
622
623 let cancellation_token = CancellationToken::new();
625 let connection = Connection::new(ConnType::Local, Channel::Server(tx2))
626 .with_cancellation_token(Some(cancellation_token.clone()));
627
628 let conn_id = self
630 .forwarder()
631 .on_connection_established(connection, None)
632 .unwrap();
633
634 debug!(%conn_id, "local connection established");
635 info!(telemetry = true, counter.num_active_connections = 1);
636
637 self.process_stream(
639 ReceiverStream::new(rx1),
640 StreamSetup::Registered(conn_id),
641 None,
642 cancellation_token,
643 ConnType::Local,
644 from_control_plane,
645 )?;
646
647 Ok((conn_id, tx1, rx2))
649 }
650
651 pub async fn send_msg(
652 &self,
653 #[cfg(feature = "otel_tracing")] mut msg: Message,
654 #[cfg(not(feature = "otel_tracing"))] msg: Message,
655 out_conn: u64,
656 ) -> Result<(), DataPathError> {
657 #[cfg(feature = "otel_tracing")]
658 otel_tracing::prepare_outbound_msg(
659 &mut msg,
660 "send_message",
661 &self.internal.service_id,
662 otel_tracing::SpanTarget::Connection(out_conn),
663 );
664 self.send_msg_raw(msg, out_conn).await
665 }
666
667 async fn send_msg_raw(&self, mut msg: Message, out_conn: u64) -> Result<(), DataPathError> {
668 let connection = self.forwarder().get_connection(out_conn);
669 match connection {
670 Some(conn) => {
671 if !msg.is_link() && !msg.is_subscription_ack() {
674 msg.clear_slim_header();
675 }
676
677 if !msg.is_link()
678 && !msg.is_subscription_ack()
679 && matches!(conn.connection_type(), ConnType::Remote | ConnType::Edge)
680 && conn.require_header_mac()
681 && conn.header_hmac().is_none()
682 {
683 return Err(DataPathError::NegotiationError(
684 "strict header MAC required but link HMAC session is not installed"
685 .to_string(),
686 ));
687 }
688
689 if !msg.is_link()
690 && !msg.is_subscription_ack()
691 && matches!(conn.connection_type(), ConnType::Remote | ConnType::Edge)
692 && let Some(mac) = conn.header_hmac()
693 {
694 let link_id = conn
695 .link_id()
696 .or_else(|| conn.config_data().map(|c| c.link_id.clone()))
697 .filter(|id| !id.is_empty());
698 if let Some(ref id) = link_id {
699 let header = msg.get_slim_header_mut();
700
701 mac.sign_slim_header(header, id.as_str())
702 .map_err(DataPathError::HeaderIntegrity)?;
703
704 #[cfg(debug_assertions)]
707 if std::env::var("SLIM_TEST_TAMPER_DESTINATION").is_ok()
708 && let Some(dest) = header.destination.as_mut()
709 && let Some(sn) = dest.str_name.as_mut()
710 {
711 sn.str_component_2.push_str("-integrity-test-tamper");
712 }
713 } else {
714 return Err(DataPathError::HeaderMacAwaitingLinkNegotiation(out_conn));
715 }
716 }
717
718 if !msg.is_link()
719 && !msg.is_subscription_ack()
720 && matches!(conn.channel(), Channel::Server(_))
721 && matches!(conn.connection_type(), ConnType::Local)
722 {
723 msg.get_slim_header_mut().header_mac = None;
724 }
725
726 match conn.channel() {
727 Channel::Server(s) => {
728 s.send(Ok(msg))
729 .await
730 .map_err(|e| DataPathError::MessageProcessingError {
731 source: Box::new(DataPathError::ConnectionNotFound(out_conn)),
732 msg: Box::new(e.0.unwrap_or_default()),
733 })
734 }
735 Channel::Client(s) => {
736 s.send(msg)
737 .await
738 .map_err(|e| DataPathError::MessageProcessingError {
739 source: Box::new(DataPathError::ConnectionNotFound(out_conn)),
740 msg: Box::new(e.0),
741 })
742 }
743 }
744 }
745 None => Err(DataPathError::ConnectionNotFound(out_conn)),
746 }
747 }
748
749 async fn send_status(&self, conn_index: u64, status: Status) {
752 if let Some(conn) = self.forwarder().get_connection(conn_index)
753 && let Channel::Server(tx) = conn.channel()
754 {
755 let _ = tx.send(Err(status)).await;
756 }
757 }
758
759 async fn match_and_forward_msg(
760 &self,
761 #[cfg(feature = "otel_tracing")] mut msg: Message,
762 #[cfg(not(feature = "otel_tracing"))] msg: Message,
763 in_connection: u64,
764 fanout: u32,
765 filter: MatchFilter,
766 ) -> Result<(), DataPathError> {
767 let header = msg.get_slim_header();
768 debug!(name = %header.get_dst(), %fanout, "match and forward message");
769
770 if let Some(val) = msg.get_forward_to() {
773 debug!(conn = %val, "forwarding message to connection");
774 return self.send_msg(msg, val).await;
775 }
776
777 let encoded = header.get_encoded_dst();
778
779 match self
780 .forwarder()
781 .on_publish_msg_match(encoded, in_connection, fanout, filter)
782 {
783 Ok(out_vec) => {
784 let len = out_vec.len();
785 if len == 1 {
787 return self.send_msg(msg, out_vec[0]).await;
788 }
789
790 #[cfg(feature = "otel_tracing")]
791 otel_tracing::prepare_fanout_msg(
792 &mut msg,
793 "send_message",
794 &self.internal.service_id,
795 len as u32,
796 );
797
798 let mut i = 0usize;
799 while i < len - 1 {
800 self.send_msg_raw(msg.clone(), out_vec[i]).await?;
801 i += 1;
802 }
803 self.send_msg_raw(msg, out_vec[i]).await?;
804 Ok(())
805 }
806 Err(e) => {
807 debug!(name = %header.get_dst(), %fanout, error = %e, "no match for publish destination");
808 Err(DataPathError::MessageProcessingError {
809 source: Box::new(e),
810 msg: Box::new(msg),
811 })
812 }
813 }
814 }
815
816 async fn handle_link_message(
821 &self,
822 link: ProtoLink,
823 conn_index: u64,
824 category: ConnType,
825 ) -> Result<(), DataPathError> {
826 if category.is_local() {
827 debug!(%conn_index, "ignoring link message received on local connection");
828 return Ok(());
829 }
830 match link.link_type {
831 Some(ProtoLinkType::LinkNegotiation(payload)) => {
832 self.handle_link_negotiation(&payload, conn_index).await
833 }
834 None => {
835 debug!(%conn_index, "received link message with unset link_type");
836 Ok(())
837 }
838 }
839 }
840
841 async fn handle_link_negotiation(
847 &self,
848 payload: &LinkNegotiationPayload,
849 in_connection: u64,
850 ) -> Result<(), DataPathError> {
851 debug!(
852 %in_connection,
853 link_id = %payload.link_id,
854 is_reply = payload.is_reply,
855 "ignoring link negotiation message on already-negotiated connection",
856 );
857
858 Ok(())
859 }
860
861 pub(crate) async fn handle_peer_upgrade(
864 &self,
865 remote_node_id: &str,
866 remote_deployment_name: &str,
867 in_connection: u64,
868 link_id: &str,
869 ) -> Result<(), DataPathError> {
870 if remote_node_id == self.internal.service_id {
872 warn!(
873 %in_connection, %link_id,
874 "rejecting peer connection from self (same node_id)"
875 );
876 self.send_status(
877 in_connection,
878 Status::permission_denied("self-connection rejected: same node_id"),
879 )
880 .await;
881 let _ = self.disconnect(in_connection);
882 return Ok(());
883 }
884
885 if !self.internal.deployment_name.is_empty()
887 && remote_deployment_name != self.internal.deployment_name
888 {
889 warn!(
890 %in_connection, %link_id,
891 local_group = %self.internal.deployment_name,
892 remote_group = %remote_deployment_name,
893 "rejecting peer upgrade: deployment_name mismatch"
894 );
895 self.send_status(
896 in_connection,
897 Status::permission_denied("deployment_name mismatch"),
898 )
899 .await;
900 let _ = self.disconnect(in_connection);
901 return Ok(());
902 }
903
904 info!(
905 %in_connection, %link_id, %remote_node_id,
906 "upgrading server-side connection to Peer (negotiation)"
907 );
908 self.connection_table().update(in_connection, |conn| {
909 conn.set_connection_type(ConnType::Peer)
910 });
911
912 self.peer_sync()
913 .on_incoming_peer(self, remote_node_id.to_string(), in_connection);
914
915 Ok(())
916 }
917
918 async fn process_publish(
919 &self,
920 msg: Message,
921 in_connection: u64,
922 filter: MatchFilter,
923 ) -> Result<(), DataPathError> {
924 debug!(
925 %in_connection,
926 ?msg,
927 "received publication"
928 );
929
930 info!(
932 telemetry = true,
933 monotonic_counter.num_messages_by_type = 1,
934 method = "publish"
935 );
936 let fanout = msg.get_fanout();
941
942 self.match_and_forward_msg(msg, in_connection, fanout, filter)
943 .await
944 }
945
946 pub(crate) async fn send_subscription_ack(
947 &self,
948 in_connection: u64,
949 subscription_id: u64,
950 result: &Result<(), DataPathError>,
951 ) {
952 let (success, error_msg) = match result {
953 Ok(()) => (true, String::new()),
954 Err(e) => (false, e.to_string()),
955 };
956
957 let ack_msg =
958 Message::builder().build_subscription_ack(subscription_id, success, error_msg);
959
960 if let Err(e) = self.send_msg(ack_msg, in_connection).await {
961 error!(error = %e.chain(), "failed to send subscription ack");
962 }
963 }
964
965 fn update_subscription_state(
969 &self,
970 msg: &Message,
971 conn: u64,
972 forward: Option<u64>,
973 add: bool,
974 subscription_id: u64,
975 ) -> Result<SubscriptionOutcome, DataPathError> {
976 let dst = msg.get_dst();
977
978 let connection = if let Some(c) = self.forwarder().get_connection(conn) {
980 c
981 } else {
982 return Err(DataPathError::ConnectionNotFound(conn));
983 };
984
985 debug!(
986 %conn,
987 %dst,
988 is_local = connection.is_local_connection(),
989 "processing {}subscription state",
990 if add { "" } else { "un" }
991 );
992
993 let is_peer_conn = connection.is_peer_connection();
994
995 let transition = self.forwarder().on_subscription_msg(
996 dst,
997 conn,
998 connection.connection_type(),
999 add,
1000 subscription_id,
1001 )?;
1002
1003 Ok(SubscriptionOutcome {
1004 transition,
1005 is_peer_conn,
1006 forward_conn: forward,
1007 })
1008 }
1009
1010 async fn process_subscription(
1018 &self,
1019 msg: Message,
1020 in_connection: u64,
1021 add: bool,
1022 ) -> Result<(), DataPathError> {
1023 debug!(
1024 %in_connection,
1025 ?msg,
1026 "received {}subscription",
1027 if add { "" } else { "un" }
1028 );
1029
1030 info!(
1032 telemetry = true,
1033 monotonic_counter.num_messages_by_type = 1,
1034 message_type = { if add { "subscribe" } else { "unsubscribe" } }
1035 );
1036 let subscription_id = msg.get_subscription_id();
1039
1040 debug!(?subscription_id, "received subscription id");
1041
1042 let header = msg.get_slim_header();
1044
1045 let (in_conn, recv_from, forward) = header.get_connections();
1047 let in_conn = recv_from.unwrap_or(in_conn);
1048
1049 let forward = forward.filter(|&out| {
1052 self.forwarder()
1053 .get_connection(out)
1054 .map(|c| !c.is_local_connection())
1055 .unwrap_or(true)
1056 });
1057
1058 let Some(connection) = self.forwarder().get_connection(in_conn) else {
1060 if let Some(id) = subscription_id {
1061 debug!(%in_conn, "connection not found, sending error ack");
1062 self.send_subscription_ack(
1063 in_connection,
1064 id,
1065 &Err(DataPathError::ConnectionNotFound(in_conn)),
1066 )
1067 .await;
1068 }
1069 return Err(DataPathError::MessageProcessingError {
1070 source: Box::new(DataPathError::ConnectionNotFound(in_conn)),
1071 msg: Box::new(msg),
1072 });
1073 };
1074
1075 if recv_from.is_some() && connection.is_local_connection() {
1077 if let Some(id) = subscription_id {
1078 debug!(%in_conn, "subscription looped back to local connection, acking ok");
1079 self.send_subscription_ack(in_connection, id, &Ok(())).await;
1080 }
1081 return Ok(());
1082 }
1083
1084 let sub_id = subscription_id.unwrap_or(0);
1091 if add && sub_id != 0 && self.peer_sync().has_seen_sub_id(sub_id) {
1092 debug!(
1093 %in_conn,
1094 %sub_id,
1095 "dropping subscription already forwarded by this node (loop prevention)"
1096 );
1097 if let Some(id) = subscription_id {
1098 self.send_subscription_ack(in_connection, id, &Ok(())).await;
1099 }
1100 return Ok(());
1101 }
1102
1103 let outcome = match self.update_subscription_state(&msg, in_conn, forward, add, sub_id) {
1105 Ok(o) => o,
1106 Err(e) => {
1107 if let Some(id) = subscription_id {
1108 self.send_subscription_ack(in_connection, id, &Err(e)).await;
1109 return Ok(());
1111 }
1112 return Err(DataPathError::MessageProcessingError {
1113 source: Box::new(e),
1114 msg: Box::new(msg),
1115 });
1116 }
1117 };
1118
1119 if connection.is_local_connection()
1125 && !outcome.is_peer_conn
1126 && outcome.transition
1127 && let Some(txcp) = self.get_tx_control_plane()
1128 {
1129 let _ = txcp.send(Ok(msg.clone())).await;
1130 }
1131
1132 let remaining_ttl = msg.get_ttl();
1142
1143 let (peer_target, peer_ttl) = if !outcome.is_peer_conn && outcome.transition {
1144 let ttl = self.peer_sync().subscription_ttl();
1146 (Some(crate::sync::PeerTarget::All), ttl)
1147 } else if outcome.is_peer_conn && remaining_ttl >= 2 {
1148 (
1151 Some(crate::sync::PeerTarget::ExcludeConn(in_conn)),
1152 remaining_ttl,
1153 )
1154 } else {
1155 (None, 0)
1156 };
1157
1158 let targets = crate::sync::ForwardTargets {
1159 peers: peer_target,
1160 forward_conn: outcome.forward_conn,
1161 };
1162
1163 if targets.has_any() {
1166 let fwd = self.peer_sync();
1167 let dst = msg.get_dst();
1168 debug!(
1169 %in_connection,
1170 %dst,
1171 %remaining_ttl,
1172 %peer_ttl,
1173 ?targets,
1174 "spawning subscription forwarder task"
1175 );
1176 let drain = self.get_drain_watch().ok();
1177 if let Some(drain) = drain {
1178 fwd.spawn_forward_and_ack(
1179 self.clone(),
1180 msg,
1181 dst,
1182 sub_id,
1183 add,
1184 targets,
1185 in_connection,
1186 subscription_id,
1187 peer_ttl,
1188 drain,
1189 );
1190 return Ok(());
1191 }
1192 }
1195
1196 if let Some(id) = subscription_id {
1198 debug!(%in_connection, "sending immediate subscription ack (no forwarding)");
1199 self.send_subscription_ack(in_connection, id, &Ok(())).await;
1200 }
1201
1202 Ok(())
1203 }
1204
1205 pub async fn process_message(
1206 &self,
1207 msg: Message,
1208 in_connection: u64,
1209 category: ConnType,
1210 ) -> Result<(), DataPathError> {
1211 match msg.message_type {
1212 Some(SubscribeType(_)) => self.process_subscription(msg, in_connection, true).await,
1213 Some(UnsubscribeType(_)) => self.process_subscription(msg, in_connection, false).await,
1214 Some(PublishType(_)) => {
1215 let filter = match category {
1216 ConnType::Peer => {
1217 if self.internal.relay_peer_publishes {
1218 MatchFilter::ALL
1219 } else {
1220 MatchFilter::EXCLUDE_PEER
1221 }
1222 }
1223 _ => MatchFilter::ALL,
1224 };
1225 self.process_publish(msg, in_connection, filter).await
1226 }
1227 Some(LinkType(link)) => {
1228 self.handle_link_message(link, in_connection, category)
1229 .await
1230 }
1231 Some(SubscriptionAckType(ack)) => {
1232 let result = if ack.success {
1233 Ok(())
1234 } else {
1235 Err(DataPathError::RemoteSubscriptionAckError(ack.error))
1236 };
1237
1238 self.peer_sync().resolve_ack(ack.subscription_id, result);
1239 Ok(())
1240 }
1241 None => unreachable!(
1242 "message type not set; validate() must be called before process_message"
1243 ),
1244 }
1245 }
1246
1247 pub(crate) async fn handle_new_message(
1248 &self,
1249 conn_index: u64,
1250 category: ConnType,
1251 mut msg: Message,
1252 ) -> Result<(), DataPathError> {
1253 debug!(%conn_index, "received message from connection");
1254 info!(
1255 telemetry = true,
1256 monotonic_counter.num_processed_messages = 1
1257 );
1258
1259 if let Err(err) = msg.validate() {
1261 info!(
1262 telemetry = true,
1263 monotonic_counter.num_messages_by_type = 1,
1264 message_type = "none"
1265 );
1266
1267 let ret_err = DataPathError::MessageProcessingError {
1268 source: Box::new(err.into()),
1269 msg: Box::new(msg),
1270 };
1271
1272 return Err(ret_err);
1273 }
1274
1275 if !msg.is_link() && !msg.is_subscription_ack() {
1277 msg.set_incoming_conn(Some(conn_index));
1279
1280 if !category.is_local() && msg.decrement_ttl() == 0 {
1282 debug!(%conn_index, "dropping message: TTL expired");
1283 return Err(DataPathError::TtlExpired);
1284 }
1285
1286 #[cfg(feature = "otel_tracing")]
1287 otel_tracing::prepare_inbound_msg(
1288 &mut msg,
1289 "process_local",
1290 &self.internal.service_id,
1291 conn_index,
1292 category.is_local(),
1293 );
1294 }
1295
1296 match self.process_message(msg, conn_index, category).await {
1297 Ok(_) => Ok(()),
1298 Err(e) => {
1299 info!(
1301 telemetry = true,
1302 monotonic_counter.num_message_process_errors = 1
1303 );
1304 Err(e)
1308 }
1309 }
1310 }
1311
1312 #[tracing::instrument(skip_all, fields(service_id = %self.internal.service_id, conn_index))]
1313 async fn send_error_to_local_app(&self, conn_index: u64, err: DataPathError) {
1314 debug!(%conn_index, "sending error to local application");
1315 let connection = self.forwarder().get_connection(conn_index);
1316 match connection {
1317 Some(conn) => {
1318 debug!("try to notify the error to the local application");
1319 if let Channel::Server(tx) = conn.channel() {
1320 let session_ctx = match &err {
1322 DataPathError::MessageProcessingError { msg, .. } => {
1323 MessageContext::from_msg(msg)
1324 }
1325 _ => None,
1326 };
1327
1328 let payload = crate::errors::ErrorPayload::new(err.to_string(), session_ctx);
1330 let error_message = payload.to_json_string();
1331
1332 let status = Status::new(tonic::Code::Internal, error_message);
1334
1335 if tx.send(Err(status)).await.is_err() {
1336 debug!(error = %err.chain(), "unable to notify the error to the local app");
1337 }
1338 }
1339 }
1340 None => {
1341 error!(
1342 "error sending error to local app: connection {:?} not found",
1343 conn_index
1344 );
1345 }
1346 }
1347 }
1348
1349 #[tracing::instrument(skip_all, fields(service_id = %self.internal.service_id, conn_index))]
1350 async fn reconnect(
1351 &self,
1352 client_conf: ClientConfig,
1353 conn_index: u64,
1354 cancellation_token: &CancellationToken,
1355 ) -> bool {
1356 info!("connection lost with remote endpoint, attempting to reconnect");
1357
1358 let is_peer = self
1359 .forwarder()
1360 .get_connection(conn_index)
1361 .map(|c| c.connection_type() == ConnType::Peer)
1362 .unwrap_or(false);
1363
1364 let remote_subscriptions = if !is_peer {
1368 self.remote_sync()
1369 .get_subscriptions_for_reconnect(conn_index)
1370 } else {
1371 Default::default()
1372 };
1373
1374 tokio::select! {
1375 _ = cancellation_token.cancelled() => {
1376 debug!("cancellation token signaled, stopping reconnection process");
1377 false
1378 }
1379 res = self.try_to_connect(client_conf, None, None, Some(conn_index)) => {
1380 match res {
1381 Ok(_) => {
1382 info!("connection re-established successfully");
1383 if is_peer {
1384 let ttl = self.peer_sync().subscription_ttl();
1386 if let Err(e) = sync_peer::send_local_remote_sync(
1387 self, conn_index, ttl,
1388 )
1389 .await
1390 {
1391 warn!(
1392 error = %e,
1393 "failed to send full sync after peer reconnect"
1394 );
1395 }
1396 } else {
1397 self.restore_remote_subscriptions(
1399 &remote_subscriptions,
1400 conn_index,
1401 false,
1402 )
1403 .await;
1404 }
1405 true
1406 }
1407 Err(e) => {
1408 error!(error = %e.chain(), "unable to reconnect to remote node");
1409 false
1410 }
1411 }
1412 }
1413 }
1414 }
1415
1416 async fn notify_control_plane_subscriptions_lost(
1422 tx_cp: Option<Sender<Result<Message, Status>>>,
1423 local_subs: HashMap<ProtoName, HashSet<u64>>,
1424 conn_index: u64,
1425 ) {
1426 let Some(tx) = tx_cp else { return };
1427 for local_sub in local_subs.into_keys() {
1428 debug!(
1429 %local_sub,
1430 "notify control plane about lost subscription",
1431 );
1432 let msg = Message::builder()
1433 .source(local_sub.clone())
1434 .destination(local_sub.clone())
1435 .flags(SlimHeaderFlags::default().with_recv_from(conn_index))
1436 .build_unsubscribe()
1437 .unwrap();
1438 if let Err(e) = tx.send(Ok(msg)).await {
1439 debug!(
1440 %local_sub,
1441 error = %e.chain(),
1442 "failed to send unsubscribe to control plane",
1443 );
1444 }
1445 }
1446 }
1447
1448 async fn resolve_connection(
1457 &self,
1458 stream: &mut (impl Stream<Item = Result<Message, Status>> + Unpin + Send),
1459 setup: StreamSetup,
1460 category: ConnType,
1461 conn_index_tx: oneshot::Sender<Result<u64, DataPathError>>,
1462 watch: &drain::Watch,
1463 token: &CancellationToken,
1464 ) -> Option<(u64, ConnType)> {
1465 match setup {
1466 StreamSetup::Registered(idx) => {
1467 let _ = conn_index_tx.send(Ok(idx));
1468 Some((idx, category))
1469 }
1470 StreamSetup::Pending {
1471 connection,
1472 existing_index,
1473 } => {
1474 self.negotiate_and_register(
1475 stream,
1476 *connection,
1477 existing_index,
1478 category,
1479 conn_index_tx,
1480 watch,
1481 token,
1482 )
1483 .await
1484 }
1485 }
1486 }
1487
1488 #[allow(clippy::too_many_arguments)]
1492 async fn negotiate_and_register(
1493 &self,
1494 stream: &mut (impl Stream<Item = Result<Message, Status>> + Unpin + Send),
1495 mut connection: Connection,
1496 existing_index: Option<u64>,
1497 category: ConnType,
1498 conn_index_tx: oneshot::Sender<Result<u64, DataPathError>>,
1499 watch: &drain::Watch,
1500 token: &CancellationToken,
1501 ) -> Option<(u64, ConnType)> {
1502 let enforce_pqc = Self::resolve_enforce_pqc(&connection, &self.internal);
1503 let timeout = self.internal.negotiation_timeout;
1504 let params = crate::negotiation::NegotiationParams {
1505 node_id: &self.internal.service_id,
1506 deployment_name: &self.internal.deployment_name,
1507 connection_type: category,
1508 enforce_pqc,
1509 };
1510
1511 let negotiation_result = tokio::select! {
1512 result = tokio::time::timeout(
1513 timeout,
1514 crate::negotiation::run_negotiation(&mut connection, stream, ¶ms),
1515 ) => match result {
1516 Ok(r) => r,
1517 Err(_) => Err(DataPathError::NegotiationError(
1518 "timed out waiting for link negotiation".to_string(),
1519 )),
1520 },
1521 _ = watch.clone().signaled() => {
1522 info!("shutting down during link negotiation");
1523 let _ = conn_index_tx.send(Err(DataPathError::ShuttingDownError));
1524 return None;
1525 }
1526 _ = token.cancelled() => {
1527 info!("connection cancelled during link negotiation");
1528 let _ = conn_index_tx.send(Err(DataPathError::ShuttingDownError));
1529 return None;
1530 }
1531 };
1532
1533 let result = match negotiation_result {
1534 Ok(r) => r,
1535 Err(e) => {
1536 error!(error = %e.chain(), "link negotiation failed, closing connection");
1537 let _ = conn_index_tx.send(Err(e));
1538 info!(telemetry = true, counter.num_active_connections = -1);
1539 return None;
1540 }
1541 };
1542
1543 let idx = match self
1545 .forwarder()
1546 .on_connection_established(connection, existing_index)
1547 {
1548 Some(idx) => idx,
1549 None => {
1550 let _ = conn_index_tx.send(Err(DataPathError::ConnectionTableAddError));
1551 info!(telemetry = true, counter.num_active_connections = -1);
1552 return None;
1553 }
1554 };
1555
1556 debug!(%idx, "connection registered after link negotiation");
1557
1558 let link_id = self
1560 .forwarder()
1561 .get_connection(idx)
1562 .and_then(|c| c.link_id())
1563 .unwrap_or_default();
1564 let category = match result.connection_type {
1565 ConnType::Peer => {
1566 if let Err(e) = self
1567 .handle_peer_upgrade(
1568 &result.remote_node_id,
1569 &result.remote_deployment_name,
1570 idx,
1571 &link_id,
1572 )
1573 .await
1574 {
1575 error!(error = %e.chain(), "peer upgrade failed after negotiation");
1576 let _ = conn_index_tx.send(Err(e));
1577 info!(telemetry = true, counter.num_active_connections = -1);
1578 return None;
1579 }
1580 ConnType::Peer
1581 }
1582 ConnType::Remote => {
1583 if let Some(tx) = self.get_tx_control_plane() {
1586 let link = ProtoLink {
1587 link_type: Some(ProtoLinkType::LinkNegotiation(LinkNegotiationPayload {
1588 link_id,
1589 ..Default::default()
1590 })),
1591 };
1592 let msg = ProtoMessage {
1593 metadata: Default::default(),
1594 message_type: Some(LinkType(link)),
1595 };
1596 let _ = tx.send(Ok(msg)).await;
1597 }
1598 ConnType::Remote
1599 }
1600 ConnType::Edge => {
1601 self.connection_table().update(idx, |conn| {
1602 conn.set_connection_type(ConnType::Edge);
1603 });
1604 ConnType::Edge
1605 }
1606 other => other,
1607 };
1608
1609 let _ = conn_index_tx.send(Ok(idx));
1610 Some((idx, category))
1611 }
1612
1613 fn process_stream(
1614 &self,
1615 mut stream: impl Stream<Item = Result<Message, Status>> + Unpin + Send + 'static,
1616 setup: StreamSetup,
1617 client_config: Option<ClientConfig>,
1618 cancellation_token: CancellationToken,
1619 category: ConnType,
1620 from_control_plane: bool,
1621 ) -> Result<
1622 (
1623 JoinHandle<()>,
1624 oneshot::Receiver<Result<u64, DataPathError>>,
1625 ),
1626 DataPathError,
1627 > {
1628 let self_clone = self.clone();
1630 let token_clone = cancellation_token.clone();
1631 let client_conf_clone = client_config.clone();
1632 let tx_cp: Option<Sender<Result<Message, Status>>> = self.get_tx_control_plane();
1633 let watch = self.get_drain_watch()?;
1634 let is_local = category.is_local();
1635
1636 let (conn_index_tx, conn_index_rx) = oneshot::channel();
1637
1638 let require_header_mac = match &setup {
1639 StreamSetup::Registered(idx) => self
1640 .forwarder()
1641 .get_connection(*idx)
1642 .map(|c| c.require_header_mac())
1643 .unwrap_or(false),
1644 StreamSetup::Pending { connection, .. } => connection.require_header_mac(),
1645 };
1646
1647 let span = tracing::info_span!(
1648 "process_stream",
1649 service_id = %self.internal.service_id,
1650 conn_index = match &setup {
1651 StreamSetup::Registered(idx) => *idx,
1652 _ => 0,
1653 },
1654 is_local,
1655 );
1656
1657 let handle = crate::runtime::spawn(async move {
1658 let mut try_to_reconnect = true;
1659
1660 let Some((conn_index, category)) = self_clone
1663 .resolve_connection(
1664 &mut stream,
1665 setup,
1666 category,
1667 conn_index_tx,
1668 &watch,
1669 &token_clone,
1670 )
1671 .await
1672 else {
1673 return;
1674 };
1675
1676 let mut watch = std::pin::pin!(watch.signaled());
1677 loop {
1678 tokio::select! {
1679 next = stream.next() => {
1680 match next {
1681 Some(result) => {
1682 match result {
1683 Ok(msg) => {
1684 if !is_local
1685 && !msg.is_link()
1686 && !msg.is_subscription_ack()
1687 && let Err(e) = self_clone
1688 .verify_remote_header_mac(conn_index, &msg, require_header_mac)
1689 {
1690 error!(
1691 %conn_index,
1692 error = %e.chain(),
1693 "SLIM header integrity verification failed",
1694 );
1695 continue;
1696 }
1697 let is_remote = !is_local
1704 && self_clone
1705 .forwarder()
1706 .get_connection(conn_index)
1707 .map(|c| matches!(c.connection_type(), ConnType::Remote | ConnType::Edge))
1708 .unwrap_or(false);
1709 if is_remote
1710 && !from_control_plane
1711 && let Some(txcp) = &tx_cp
1712 {
1713 let cp_msg = match msg.get_type() {
1714 SubscribeType(_) => ProtoMessage::builder()
1717 .source(msg.get_source())
1718 .destination(msg.get_dst())
1719 .build_subscribe()
1720 .ok(),
1721 UnsubscribeType(_) => ProtoMessage::builder()
1722 .source(msg.get_source())
1723 .destination(msg.get_dst())
1724 .build_unsubscribe()
1725 .ok(),
1726 _ => None
1727 };
1728 if let Some(m) = cp_msg {
1729 let _ = txcp.send(Ok(m)).await;
1730 }
1731 }
1732
1733 if let Err(e) = self_clone.handle_new_message(conn_index, category, msg).await {
1734 if matches!(e, DataPathError::NegotiationError(_)) {
1736 error!(%conn_index, "fatal link negotiation error, closing connection");
1737 try_to_reconnect = false;
1738 break;
1739 }
1740 debug!(%conn_index, error = %e.chain(), "error processing incoming message");
1741 if is_local {
1743 self_clone.send_error_to_local_app(conn_index, e).await;
1745 }
1746 }
1747 }
1748 Err(e) => {
1749 if e.code() == tonic::Code::PermissionDenied {
1750 warn!(
1751 %conn_index,
1752 message = %e.message(),
1753 "connection rejected by remote, will not reconnect"
1754 );
1755 try_to_reconnect = false;
1756 } else if let Some(io_err) = MessageProcessor::match_for_io_error(&e) {
1757 if io_err.kind() == std::io::ErrorKind::BrokenPipe {
1758 info!(%conn_index, "connection closed by peer");
1759 }
1760 } else {
1761 error!(error = %e.chain(), "error receiving messages");
1762 }
1763 break;
1764 }
1765 }
1766 }
1767 None => {
1768 debug!(%conn_index, "end of stream");
1769 break;
1770 }
1771 }
1772 }
1773 _ = &mut watch => {
1774 info!(%conn_index, "shutting down stream on drain");
1775 try_to_reconnect = false;
1776 break;
1777 }
1778 _ = token_clone.cancelled() => {
1779 info!(%conn_index, "shutting down stream on cancellation token");
1780 try_to_reconnect = false;
1781 break;
1782 }
1783 }
1784 }
1785
1786 drop(stream);
1790
1791 let mut connected = false;
1792
1793 if try_to_reconnect
1794 && !matches!(category, ConnType::Remote)
1795 && let Some(config) = client_conf_clone
1796 {
1797 connected = self_clone.reconnect(config, conn_index, &token_clone)
1801 .instrument(tracing::Span::none())
1802 .await;
1803 } else {
1804 debug!(%conn_index, "close connection")
1805 }
1806
1807 if !connected {
1808 let local_subs = self_clone
1810 .forwarder()
1811 .on_connection_drop(conn_index, category);
1812 let _remote_subs = self_clone
1813 .remote_sync()
1814 .on_connection_drop(conn_index);
1815
1816 if matches!(category, ConnType::Peer) {
1818 self_clone.peer_sync().remove_peer_conn(conn_index);
1819 }
1820
1821 {
1826 let fwd = self_clone.peer_sync();
1827 for name in local_subs.keys() {
1828 let still_reachable = name.name.is_some_and(|enc| {
1829 self_clone
1830 .forwarder()
1831 .on_publish_msg_match(enc, u64::MAX, u32::MAX, MatchFilter::ALL)
1832 .is_ok()
1833 });
1834 if !still_reachable {
1835 debug!(
1836 %name,
1837 %conn_index,
1838 ?category,
1839 "notifying peers of unsubscription (connection drop)"
1840 );
1841 fwd.notify_peers_unsubscribe(&self_clone, name).await;
1842 } else {
1843 debug!(
1844 %name,
1845 %conn_index,
1846 ?category,
1847 "name still reachable, not emitting removal"
1848 );
1849 }
1850 }
1851 }
1852
1853
1854 if !is_local {
1856 MessageProcessor::notify_control_plane_subscriptions_lost(
1857 tx_cp, local_subs, conn_index,
1858 )
1859 .await;
1860 }
1861
1862 info!(telemetry = true, counter.num_active_connections = -1);
1863 }
1864 }.instrument(span));
1865
1866 Ok((handle, conn_index_rx))
1867 }
1868
1869 fn match_for_io_error(err_status: &Status) -> Option<&std::io::Error> {
1870 let mut err: &(dyn std::error::Error + 'static) = err_status;
1871
1872 loop {
1873 if let Some(io_err) = err.downcast_ref::<std::io::Error>() {
1874 return Some(io_err);
1875 }
1876
1877 #[cfg(not(target_arch = "wasm32"))]
1881 if let Some(h2_err) = err.downcast_ref::<h2::Error>()
1882 && let Some(io_err) = h2_err.get_io()
1883 {
1884 return Some(io_err);
1885 }
1886
1887 err = err.source()?;
1888 }
1889 }
1890
1891 pub fn subscription_table(&self) -> &SubscriptionTableImpl {
1892 &self.internal.forwarder.subscription_table
1893 }
1894
1895 pub fn connection_table(&self) -> &ConnectionTable<Connection> {
1896 &self.internal.forwarder.connection_table
1897 }
1898
1899 pub fn service_id(&self) -> &str {
1901 &self.internal.service_id
1902 }
1903
1904 pub fn relay_peer_publishes(&self) -> bool {
1906 self.internal.relay_peer_publishes
1907 }
1908
1909 pub fn set_peer_sync(&self, peer_sync: crate::sync::PeerSync) {
1911 *self.internal.peer_sync.write() = peer_sync;
1912 }
1913
1914 pub(crate) fn peer_sync(&self) -> crate::sync::PeerSync {
1916 self.internal.peer_sync.read().clone()
1917 }
1918
1919 fn resolve_enforce_pqc(connection: &Connection, internal: &MessageProcessorInternal) -> bool {
1920 #[cfg(not(target_arch = "wasm32"))]
1921 {
1922 connection
1923 .config_data()
1924 .map(|c| c.tls_setting.config.enforce_pqc)
1925 .unwrap_or(internal.server_enforce_pqc)
1926 }
1927 #[cfg(target_arch = "wasm32")]
1928 {
1929 let _ = connection;
1931 internal.server_enforce_pqc
1932 }
1933 }
1934}
1935
1936#[cfg(not(target_arch = "wasm32"))]
1937impl ServerHandler for MessageProcessor {
1938 fn grpc_routes(&self) -> Option<tonic::service::Routes> {
1939 let svc = DataPlaneServiceServer::from_arc(Arc::new(self.clone()));
1940 Some(tonic::service::Routes::new(svc))
1941 }
1942
1943 fn on_websocket_accepted(&self) -> Option<websocket_server::OnAcceptedWebSocket> {
1944 let processor = self.clone();
1945 Some(Arc::new(move |accepted| {
1946 let processor = processor.clone();
1947 Box::pin(async move { processor.handle_websocket_accepted(accepted).await })
1948 }))
1949 }
1950}
1951
1952#[cfg(not(target_arch = "wasm32"))]
1953#[tonic::async_trait]
1954impl DataPlaneService for MessageProcessor {
1955 type OpenChannelStream = Pin<Box<dyn Stream<Item = Result<Message, Status>> + Send + 'static>>;
1956
1957 async fn open_channel(
1958 &self,
1959 request: Request<tonic::Streaming<Message>>,
1960 ) -> Result<Response<Self::OpenChannelStream>, Status> {
1961 let remote_addr = request.remote_addr();
1962 let local_addr = request.local_addr();
1963
1964 let stream = request.into_inner();
1965 let (tx, rx) = mpsc::channel(128);
1966
1967 let connection = Connection::new(ConnType::Remote, Channel::Server(tx))
1968 .with_remote_addr(remote_addr)
1969 .with_local_addr(local_addr)
1970 .with_require_header_mac(self.internal.server_require_header_mac);
1971
1972 debug!(
1973 remote = ?connection.remote_addr(),
1974 local = ?connection.local_addr(),
1975 "new connection received from remote",
1976 );
1977 info!(telemetry = true, counter.num_active_connections = 1);
1978
1979 self.process_stream(
1980 stream,
1981 StreamSetup::Pending {
1982 connection: Box::new(connection),
1983 existing_index: None,
1984 },
1985 None,
1986 CancellationToken::new(),
1987 ConnType::Remote,
1988 false,
1989 )
1990 .map_err(|e| {
1991 error!(error = %e.chain(), "error starting new processing stream");
1992 Status::unavailable(format!("error processing stream: {:?}", e))
1993 })?;
1994
1995 let out_stream = ReceiverStream::new(rx);
1996 Ok(Response::new(
1997 Box::pin(out_stream) as Self::OpenChannelStream
1998 ))
1999 }
2000}
2001
2002#[cfg(test)]
2003mod tests {
2004 use std::time::Duration;
2005
2006 use super::*;
2007 use crate::api::{ProtoMessage, ProtoName, ProtoSubscriptionAck};
2008 use crate::header_mac::HeaderMacSession;
2009 use crate::sync::remote::SubscriptionInfo;
2010 use tonic::Status;
2011
2012 async fn assert_failed_subscription_ack_is_sent(add: bool) {
2013 let processor = MessageProcessor::new();
2014 let (in_connection, _tx, mut rx) = processor
2015 .register_local_connection(false)
2016 .expect("failed to create local connection");
2017
2018 let source = ProtoName::from_strings(["org", "ns", "source"]).with_id(1);
2019 let destination = ProtoName::from_strings(["org", "ns", "destination"]).with_id(2);
2020 let ack_id: u64 = if add { 1 } else { 2 };
2021 let invalid_connection = u64::MAX - 1;
2022
2023 let builder = Message::builder()
2024 .source(source.clone())
2025 .destination(destination.clone())
2026 .incoming_conn(invalid_connection)
2027 .subscription_id(ack_id);
2028
2029 let msg = if add {
2030 builder.build_subscribe().unwrap()
2031 } else {
2032 builder.build_unsubscribe().unwrap()
2033 };
2034
2035 let result = processor
2036 .process_subscription(msg, in_connection, add)
2037 .await;
2038 assert!(matches!(
2039 result,
2040 Err(DataPathError::MessageProcessingError { .. })
2041 ));
2042
2043 let ack_msg = tokio::time::timeout(Duration::from_secs(1), rx.recv())
2044 .await
2045 .expect("timeout waiting for ack")
2046 .expect("ack channel closed")
2047 .expect("failed to receive ack message");
2048
2049 assert!(matches!(ack_msg.get_type(), SubscriptionAckType(_)));
2050 let ack = ack_msg.get_subscription_ack();
2051 assert_eq!(ack.subscription_id, ack_id);
2052 assert!(!ack.success, "failed ack should have success=false");
2053 assert!(
2054 !ack.error.is_empty(),
2055 "failed ack should include an error message"
2056 );
2057 }
2058
2059 #[tokio::test]
2060 async fn test_process_subscription_sends_failed_ack_on_subscribe_error() {
2061 assert_failed_subscription_ack_is_sent(true).await;
2062 }
2063
2064 #[tokio::test]
2065 async fn test_process_subscription_sends_failed_ack_on_unsubscribe_error() {
2066 assert_failed_subscription_ack_is_sent(false).await;
2067 }
2068
2069 #[tokio::test]
2072 async fn test_handle_link_message_is_local_ignored() {
2073 let processor = MessageProcessor::new();
2074 let link = ProtoLink { link_type: None };
2075 assert!(
2076 processor
2077 .handle_link_message(link, 0, ConnType::Local)
2078 .await
2079 .is_ok()
2080 );
2081 }
2082
2083 #[tokio::test]
2084 async fn test_handle_link_message_none_link_type_ignored() {
2085 let processor = MessageProcessor::new();
2086 let link = ProtoLink { link_type: None };
2087 assert!(
2088 processor
2089 .handle_link_message(link, 0, ConnType::Remote)
2090 .await
2091 .is_ok()
2092 );
2093 }
2094
2095 #[tokio::test]
2101 async fn test_handle_link_negotiation_post_negotiation_is_noop() {
2102 let processor = MessageProcessor::new();
2103 let payload = LinkNegotiationPayload {
2104 link_id: uuid::Uuid::new_v4().to_string(),
2105 slim_version: "1.0.0".into(),
2106 is_reply: false,
2107 link_ecdh_public_key: vec![],
2108 link_kem_payload: None,
2109 connection_type: 0,
2110 node_id: String::new(),
2111 deployment_name: String::new(),
2112 };
2113 assert!(
2115 processor
2116 .handle_link_negotiation(&payload, u64::MAX)
2117 .await
2118 .is_ok()
2119 );
2120 let (conn_id, _rx) = make_negotiated_server_conn(&processor, "1.2.0");
2122 assert!(
2123 processor
2124 .handle_link_negotiation(&payload, conn_id)
2125 .await
2126 .is_ok()
2127 );
2128 }
2129
2130 fn make_negotiated_server_conn(
2135 processor: &MessageProcessor,
2136 version: &str,
2137 ) -> (u64, tokio::sync::mpsc::Receiver<Result<Message, Status>>) {
2138 let (tx, rx) = mpsc::channel(16);
2139 let conn = Connection::new(ConnType::Remote, Channel::Server(tx))
2140 .with_require_header_mac(processor.internal.server_require_header_mac)
2141 .with_negotiation(&uuid::Uuid::new_v4().to_string(), version)
2142 .with_header_hmac(HeaderMacSession::new(b"01234567890123456789012345678901").unwrap());
2143 let conn_id = processor
2144 .forwarder()
2145 .on_connection_established(conn, None)
2146 .unwrap();
2147 (conn_id, rx)
2148 }
2149
2150 #[tokio::test]
2151 async fn test_negotiation_timeout_configurable() {
2152 let server_config = ServerConfig {
2153 endpoint: "localhost:12345".to_string(),
2154 negotiation_timeout_secs: 1, ..Default::default()
2156 };
2157 let processor = MessageProcessor::new_with_server_config(
2158 "test_service".to_string(),
2159 String::new(),
2160 &server_config,
2161 false,
2162 false,
2163 );
2164
2165 assert_eq!(
2166 processor.internal.negotiation_timeout,
2167 std::time::Duration::from_secs(1)
2168 );
2169 }
2170
2171 #[test]
2172 fn verify_remote_header_mac_strict_rejects_publish_without_mac_session() {
2173 let processor = MessageProcessor::new();
2174 let (tx, _rx) = mpsc::channel(16);
2176 let conn = Connection::new(ConnType::Remote, Channel::Server(tx))
2177 .with_require_header_mac(true)
2178 .with_negotiation(&uuid::Uuid::new_v4().to_string(), "1.2.0");
2179 let remote_conn = processor
2180 .forwarder()
2181 .on_connection_established(conn, None)
2182 .unwrap();
2183 let c = processor.forwarder().get_connection(remote_conn).unwrap();
2184 assert!(c.header_hmac().is_none());
2185
2186 let source = ProtoName::from_strings(["org", "default", "a"]).with_id(1);
2187 let dest = ProtoName::from_strings(["org", "default", "b"]).with_id(2);
2188 let msg = ProtoMessage::builder()
2189 .source(source)
2190 .destination(dest)
2191 .application_payload("text/plain", b"hey".to_vec())
2192 .build_publish()
2193 .expect("publish");
2194
2195 let err = processor
2196 .verify_remote_header_mac(remote_conn, &msg, true)
2197 .expect_err("unsigned publish must fail in strict mode without MAC session");
2198 assert!(matches!(err, DataPathError::NegotiationError(_)));
2199 }
2200
2201 #[test]
2202 fn verify_remote_header_mac_accepts_signed_inter_node_publish() {
2203 let processor = MessageProcessor::new();
2204 let (remote_conn, _rx) = make_negotiated_server_conn(&processor, "1.2.0");
2205 let link_id = processor
2206 .forwarder()
2207 .get_connection(remote_conn)
2208 .unwrap()
2209 .link_id()
2210 .expect("link id after negotiation");
2211
2212 let source = ProtoName::from_strings(["org", "default", "a"]).with_id(1);
2213 let dest = ProtoName::from_strings(["org", "default", "b"]).with_id(2);
2214 let require_header_mac = true;
2215 let mut msg = ProtoMessage::builder()
2216 .source(source)
2217 .destination(dest)
2218 .application_payload("text/plain", b"hey".to_vec())
2219 .build_publish()
2220 .expect("publish");
2221
2222 let mac = HeaderMacSession::new(b"01234567890123456789012345678901").unwrap();
2223 mac.sign_slim_header(msg.get_slim_header_mut(), &link_id)
2224 .expect("sign header");
2225
2226 assert!(
2227 processor
2228 .verify_remote_header_mac(remote_conn, &msg, require_header_mac)
2229 .is_ok()
2230 );
2231 }
2232
2233 #[test]
2234 fn verify_remote_header_mac_rejects_destination_tamper_after_sign() {
2235 let processor = MessageProcessor::new();
2236 let (remote_conn, _rx) = make_negotiated_server_conn(&processor, "1.2.0");
2237 let link_id = processor
2238 .forwarder()
2239 .get_connection(remote_conn)
2240 .unwrap()
2241 .link_id()
2242 .expect("link id after negotiation");
2243
2244 let source = ProtoName::from_strings(["org", "default", "a"]).with_id(1);
2245 let dest = ProtoName::from_strings(["org", "default", "b"]).with_id(2);
2246 let mut msg = ProtoMessage::builder()
2247 .source(source)
2248 .destination(dest)
2249 .application_payload("text/plain", b"hey".to_vec())
2250 .build_publish()
2251 .expect("publish");
2252
2253 let mac = HeaderMacSession::new(b"01234567890123456789012345678901").unwrap();
2254 let require_header_mac = true;
2255 mac.sign_slim_header(msg.get_slim_header_mut(), &link_id)
2256 .expect("sign header");
2257
2258 let header = msg.get_slim_header_mut();
2259 if let Some(dest) = header.destination.as_mut()
2260 && let Some(sn) = dest.str_name.as_mut()
2261 {
2262 sn.str_component_2.push_str("-integrity-test-tamper");
2263 }
2264
2265 let err = processor
2266 .verify_remote_header_mac(remote_conn, &msg, require_header_mac)
2267 .expect_err("tampered header must fail MAC verify");
2268 assert!(matches!(err, DataPathError::HeaderIntegrity(_)));
2269 }
2270
2271 #[tokio::test]
2272 #[allow(clippy::disallowed_methods)]
2273 async fn test_send_msg_raw_tamper_destination_env_var() {
2274 let _guard = ENV_LOCK.lock().await;
2275 unsafe {
2276 std::env::set_var("SLIM_TEST_TAMPER_DESTINATION", "1");
2277 }
2278
2279 let processor = MessageProcessor::new();
2280 let (conn_id, mut rx) = make_negotiated_server_conn(&processor, "1.2.0");
2281
2282 let source = ProtoName::from_strings(["org", "default", "a"]).with_id(1);
2283 let dest = ProtoName::from_strings(["org", "default", "b"]).with_id(2);
2284 let msg = ProtoMessage::builder()
2285 .source(source)
2286 .destination(dest)
2287 .application_payload("text/plain", b"hey".to_vec())
2288 .build_publish()
2289 .expect("publish");
2290
2291 processor
2292 .send_msg_raw(msg, conn_id)
2293 .await
2294 .expect("send_msg_raw failed");
2295
2296 let sent_msg = rx.recv().await.unwrap().unwrap();
2297 let header = sent_msg.get_slim_header();
2298 let dest_name = header.destination.as_ref().expect("destination");
2299 let str_name = dest_name.str_name.as_ref().expect("str_name");
2300 let require_header_mac = true;
2301
2302 assert!(str_name.str_component_2.ends_with("-integrity-test-tamper"));
2304
2305 let err = processor
2307 .verify_remote_header_mac(conn_id, &sent_msg, require_header_mac)
2308 .expect_err("tampered header must fail MAC verify");
2309 assert!(matches!(err, DataPathError::HeaderIntegrity(_)));
2310
2311 unsafe {
2312 std::env::remove_var("SLIM_TEST_TAMPER_DESTINATION");
2313 }
2314 }
2315
2316 #[tokio::test]
2317 async fn test_process_subscription_remote_ack_path_success() {
2318 let processor = MessageProcessor::new();
2321 let (local_conn, _tx_local, mut rx_local) = processor
2322 .register_local_connection(false)
2323 .expect("failed to create local connection");
2324
2325 let (remote_conn, mut rx_remote) = make_negotiated_server_conn(&processor, "1.2.0");
2326
2327 let source = ProtoName::from_strings(["org", "ns", "src"]).with_id(1);
2328 let destination = ProtoName::from_strings(["org", "ns", "dst"]).with_id(2);
2329 let upstream_ack_id: u64 = 100;
2330
2331 let sub_msg = Message::builder()
2333 .source(source.clone())
2334 .destination(destination.clone())
2335 .incoming_conn(local_conn)
2336 .forward_to(remote_conn)
2337 .subscription_id(upstream_ack_id)
2338 .build_subscribe()
2339 .unwrap();
2340
2341 let result = processor
2343 .process_subscription(sub_msg, local_conn, true)
2344 .await;
2345 assert!(result.is_ok());
2346
2347 let forwarded = tokio::time::timeout(Duration::from_secs(1), rx_remote.recv())
2350 .await
2351 .expect("timeout waiting for forwarded subscribe")
2352 .expect("forwarded subscribe channel closed")
2353 .unwrap();
2354 assert!(matches!(forwarded.get_type(), SubscribeType(_)));
2355
2356 let forwarded_sub_id = forwarded
2358 .get_subscription_id()
2359 .expect("forwarded subscribe must carry the same subscription_id");
2360 assert_eq!(
2361 forwarded_sub_id, upstream_ack_id,
2362 "subscription_id must not change when forwarding"
2363 );
2364
2365 let ack = ProtoSubscriptionAck {
2367 subscription_id: upstream_ack_id,
2368 success: true,
2369 error: String::new(),
2370 };
2371 processor.peer_sync().resolve_ack(
2372 ack.subscription_id,
2373 if ack.success {
2374 Ok(())
2375 } else {
2376 Err(DataPathError::RemoteSubscriptionAckError(ack.error.clone()))
2377 },
2378 );
2379
2380 let upstream_ack = tokio::time::timeout(Duration::from_secs(2), rx_local.recv())
2382 .await
2383 .expect("timeout waiting for upstream ack")
2384 .expect("upstream ack channel closed")
2385 .expect("upstream ack should be Ok");
2386
2387 assert!(matches!(upstream_ack.get_type(), SubscriptionAckType(_)));
2388 let ack_inner = upstream_ack.get_subscription_ack();
2389 assert_eq!(ack_inner.subscription_id, upstream_ack_id);
2390 assert!(ack_inner.success);
2391 }
2392
2393 #[tokio::test]
2394 async fn test_process_subscription_remote_ack_error_forwarded_upstream() {
2395 let processor = MessageProcessor::new();
2397 let (local_conn, _tx_local, mut rx_local) = processor
2398 .register_local_connection(false)
2399 .expect("failed to create local connection");
2400
2401 let (remote_conn, mut rx_remote) = make_negotiated_server_conn(&processor, "1.2.0");
2402
2403 let source = ProtoName::from_strings(["org", "ns", "src"]).with_id(1);
2404 let destination = ProtoName::from_strings(["org", "ns", "dst"]).with_id(2);
2405 let upstream_ack_id: u64 = 102;
2406
2407 let sub_msg = Message::builder()
2408 .source(source.clone())
2409 .destination(destination.clone())
2410 .incoming_conn(local_conn)
2411 .forward_to(remote_conn)
2412 .subscription_id(upstream_ack_id)
2413 .build_subscribe()
2414 .unwrap();
2415
2416 processor
2417 .process_subscription(sub_msg, local_conn, true)
2418 .await
2419 .unwrap();
2420
2421 let forwarded = tokio::time::timeout(Duration::from_secs(1), rx_remote.recv())
2422 .await
2423 .expect("timeout")
2424 .expect("channel closed")
2425 .unwrap();
2426
2427 let forwarded_sub_id = forwarded
2428 .get_subscription_id()
2429 .expect("forwarded subscribe must carry the same subscription_id");
2430 assert_eq!(
2431 forwarded_sub_id, upstream_ack_id,
2432 "subscription_id must not change when forwarding"
2433 );
2434
2435 let ack = ProtoSubscriptionAck {
2437 subscription_id: upstream_ack_id,
2438 success: false,
2439 error: "remote error".to_string(),
2440 };
2441 processor.peer_sync().resolve_ack(
2442 ack.subscription_id,
2443 if ack.success {
2444 Ok(())
2445 } else {
2446 Err(DataPathError::RemoteSubscriptionAckError(ack.error.clone()))
2447 },
2448 );
2449
2450 let upstream_ack = tokio::time::timeout(Duration::from_secs(2), rx_local.recv())
2451 .await
2452 .expect("timeout")
2453 .expect("channel closed")
2454 .expect("must be Ok");
2455
2456 assert!(matches!(upstream_ack.get_type(), SubscriptionAckType(_)));
2457 let ack_inner = upstream_ack.get_subscription_ack();
2458 assert_eq!(ack_inner.subscription_id, upstream_ack_id);
2459 assert!(!ack_inner.success);
2460 assert!(!ack_inner.error.is_empty());
2461 }
2462
2463 #[tokio::test]
2466 async fn test_notify_cp_subs_lost_sends_unsubscribes() {
2467 let (tx, mut rx) = mpsc::channel::<Result<Message, Status>>(16);
2468 let mut subs = HashMap::new();
2469 let name = ProtoName::from_strings(["org", "default", "svc"]);
2470 subs.insert(name.clone(), HashSet::from([1u64, 2u64]));
2471
2472 MessageProcessor::notify_control_plane_subscriptions_lost(Some(tx), subs, 42).await;
2473
2474 let msg = rx.recv().await.unwrap().unwrap();
2475 assert!(matches!(msg.get_type(), UnsubscribeType(_)));
2476 assert_eq!(msg.get_source(), name.clone());
2477 }
2478
2479 #[tokio::test]
2480 async fn test_notify_cp_subs_lost_no_tx_is_noop() {
2481 let subs = HashMap::from([(
2482 ProtoName::from_strings(["org", "default", "svc"]),
2483 HashSet::from([1u64]),
2484 )]);
2485 MessageProcessor::notify_control_plane_subscriptions_lost(None, subs, 1).await;
2487 }
2488
2489 #[tokio::test]
2490 async fn test_notify_cp_subs_lost_empty_subs() {
2491 let (tx, mut rx) = mpsc::channel::<Result<Message, Status>>(16);
2492 MessageProcessor::notify_control_plane_subscriptions_lost(Some(tx), HashMap::new(), 1)
2493 .await;
2494 assert!(rx.try_recv().is_err());
2496 }
2497
2498 #[tokio::test]
2501 async fn test_restore_remote_subscriptions_with_tracking() {
2502 let processor = MessageProcessor::new();
2503 let (conn_id, mut rx) = make_negotiated_server_conn(&processor, "1.2.0");
2504
2505 let source = ProtoName::from_strings(["org", "default", "src"]);
2506 let dest = ProtoName::from_strings(["org", "default", "dst"]);
2507 let sub = SubscriptionInfo::new(source.clone(), dest.clone(), "id1".into(), conn_id, 7);
2508 let subs = HashSet::from([sub]);
2509
2510 processor
2511 .restore_remote_subscriptions(&subs, conn_id, true)
2512 .await;
2513
2514 let msg = rx.recv().await.unwrap().unwrap();
2516 assert!(matches!(msg.get_type(), SubscribeType(_)));
2517
2518 let tracked = processor
2520 .remote_sync()
2521 .get_subscriptions_for_reconnect(conn_id);
2522 assert_eq!(tracked.len(), 1);
2523 }
2524
2525 #[tokio::test]
2526 async fn test_restore_remote_subscriptions_without_tracking() {
2527 let processor = MessageProcessor::new();
2528 let (conn_id, mut rx) = make_negotiated_server_conn(&processor, "1.2.0");
2529
2530 let source = ProtoName::from_strings(["org", "default", "src"]);
2531 let dest = ProtoName::from_strings(["org", "default", "dst"]);
2532 let sub = SubscriptionInfo::new(source.clone(), dest.clone(), "id1".into(), conn_id, 7);
2533 let subs = HashSet::from([sub]);
2534
2535 processor
2536 .restore_remote_subscriptions(&subs, conn_id, false)
2537 .await;
2538
2539 let msg = rx.recv().await.unwrap().unwrap();
2541 assert!(matches!(msg.get_type(), SubscribeType(_)));
2542
2543 let tracked = processor
2545 .remote_sync()
2546 .get_subscriptions_for_reconnect(conn_id);
2547 assert!(tracked.is_empty());
2548 }
2549}