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