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 async fn reconnect(
1350 &self,
1351 client_conf: ClientConfig,
1352 conn_index: u64,
1353 cancellation_token: &CancellationToken,
1354 ) -> bool {
1355 let span = tracing::info_span!(
1361 "reconnect",
1362 service_id = %self.internal.service_id,
1363 conn_index,
1364 );
1365
1366 async {
1367 info!("connection lost with remote endpoint, attempting to reconnect");
1368
1369 let is_peer = self
1370 .forwarder()
1371 .get_connection(conn_index)
1372 .map(|c| c.connection_type() == ConnType::Peer)
1373 .unwrap_or(false);
1374
1375 let remote_subscriptions = if !is_peer {
1379 self.remote_sync()
1380 .get_subscriptions_for_reconnect(conn_index)
1381 } else {
1382 Default::default()
1383 };
1384
1385 tokio::select! {
1386 _ = cancellation_token.cancelled() => {
1387 debug!("cancellation token signaled, stopping reconnection process");
1388 false
1389 }
1390 res = self.try_to_connect(client_conf, None, None, Some(conn_index)) => {
1391 match res {
1392 Ok(_) => {
1393 info!("connection re-established successfully");
1394 if is_peer {
1395 let ttl = self.peer_sync().subscription_ttl();
1397 if let Err(e) = sync_peer::send_local_remote_sync(
1398 self, conn_index, ttl,
1399 )
1400 .await
1401 {
1402 warn!(
1403 error = %e,
1404 "failed to send full sync after peer reconnect"
1405 );
1406 }
1407 } else {
1408 self.restore_remote_subscriptions(
1410 &remote_subscriptions,
1411 conn_index,
1412 false,
1413 )
1414 .await;
1415 }
1416 true
1417 }
1418 Err(e) => {
1419 error!(error = %e.chain(), "unable to reconnect to remote node");
1420 false
1421 }
1422 }
1423 }
1424 }
1425 }
1426 .instrument(span)
1427 .await
1428 }
1429
1430 async fn notify_control_plane_subscriptions_lost(
1436 tx_cp: Option<Sender<Result<Message, Status>>>,
1437 local_subs: HashMap<ProtoName, HashSet<u64>>,
1438 conn_index: u64,
1439 ) {
1440 let Some(tx) = tx_cp else { return };
1441 for local_sub in local_subs.into_keys() {
1442 debug!(
1443 %local_sub,
1444 "notify control plane about lost subscription",
1445 );
1446 let msg = Message::builder()
1447 .source(local_sub.clone())
1448 .destination(local_sub.clone())
1449 .flags(SlimHeaderFlags::default().with_recv_from(conn_index))
1450 .build_unsubscribe()
1451 .unwrap();
1452 if let Err(e) = tx.send(Ok(msg)).await {
1453 debug!(
1454 %local_sub,
1455 error = %e.chain(),
1456 "failed to send unsubscribe to control plane",
1457 );
1458 }
1459 }
1460 }
1461
1462 async fn resolve_connection(
1471 &self,
1472 stream: &mut (impl Stream<Item = Result<Message, Status>> + Unpin + Send),
1473 setup: StreamSetup,
1474 category: ConnType,
1475 conn_index_tx: oneshot::Sender<Result<u64, DataPathError>>,
1476 watch: &drain::Watch,
1477 token: &CancellationToken,
1478 ) -> Option<(u64, ConnType)> {
1479 match setup {
1480 StreamSetup::Registered(idx) => {
1481 let _ = conn_index_tx.send(Ok(idx));
1482 Some((idx, category))
1483 }
1484 StreamSetup::Pending {
1485 connection,
1486 existing_index,
1487 } => {
1488 self.negotiate_and_register(
1489 stream,
1490 *connection,
1491 existing_index,
1492 category,
1493 conn_index_tx,
1494 watch,
1495 token,
1496 )
1497 .await
1498 }
1499 }
1500 }
1501
1502 #[allow(clippy::too_many_arguments)]
1506 async fn negotiate_and_register(
1507 &self,
1508 stream: &mut (impl Stream<Item = Result<Message, Status>> + Unpin + Send),
1509 mut connection: Connection,
1510 existing_index: Option<u64>,
1511 category: ConnType,
1512 conn_index_tx: oneshot::Sender<Result<u64, DataPathError>>,
1513 watch: &drain::Watch,
1514 token: &CancellationToken,
1515 ) -> Option<(u64, ConnType)> {
1516 let enforce_pqc = Self::resolve_enforce_pqc(&connection, &self.internal);
1517 let timeout = self.internal.negotiation_timeout;
1518 let params = crate::negotiation::NegotiationParams {
1519 node_id: &self.internal.service_id,
1520 deployment_name: &self.internal.deployment_name,
1521 connection_type: category,
1522 enforce_pqc,
1523 };
1524
1525 let negotiation_result = tokio::select! {
1526 result = tokio::time::timeout(
1527 timeout,
1528 crate::negotiation::run_negotiation(&mut connection, stream, ¶ms),
1529 ) => match result {
1530 Ok(r) => r,
1531 Err(_) => Err(DataPathError::NegotiationError(
1532 "timed out waiting for link negotiation".to_string(),
1533 )),
1534 },
1535 _ = watch.clone().signaled() => {
1536 info!("shutting down during link negotiation");
1537 let _ = conn_index_tx.send(Err(DataPathError::ShuttingDownError));
1538 return None;
1539 }
1540 _ = token.cancelled() => {
1541 info!("connection cancelled during link negotiation");
1542 let _ = conn_index_tx.send(Err(DataPathError::ShuttingDownError));
1543 return None;
1544 }
1545 };
1546
1547 let result = match negotiation_result {
1548 Ok(r) => r,
1549 Err(e) => {
1550 error!(error = %e.chain(), "link negotiation failed, closing connection");
1551 let _ = conn_index_tx.send(Err(e));
1552 info!(telemetry = true, counter.num_active_connections = -1);
1553 return None;
1554 }
1555 };
1556
1557 let idx = match self
1559 .forwarder()
1560 .on_connection_established(connection, existing_index)
1561 {
1562 Some(idx) => idx,
1563 None => {
1564 let _ = conn_index_tx.send(Err(DataPathError::ConnectionTableAddError));
1565 info!(telemetry = true, counter.num_active_connections = -1);
1566 return None;
1567 }
1568 };
1569
1570 debug!(%idx, "connection registered after link negotiation");
1571
1572 let link_id = self
1574 .forwarder()
1575 .get_connection(idx)
1576 .and_then(|c| c.link_id())
1577 .unwrap_or_default();
1578 let category = match result.connection_type {
1579 ConnType::Peer => {
1580 if let Err(e) = self
1581 .handle_peer_upgrade(
1582 &result.remote_node_id,
1583 &result.remote_deployment_name,
1584 idx,
1585 &link_id,
1586 )
1587 .await
1588 {
1589 error!(error = %e.chain(), "peer upgrade failed after negotiation");
1590 let _ = conn_index_tx.send(Err(e));
1591 info!(telemetry = true, counter.num_active_connections = -1);
1592 return None;
1593 }
1594 ConnType::Peer
1595 }
1596 ConnType::Remote => {
1597 if let Some(tx) = self.get_tx_control_plane() {
1600 let link = ProtoLink {
1601 link_type: Some(ProtoLinkType::LinkNegotiation(LinkNegotiationPayload {
1602 link_id,
1603 ..Default::default()
1604 })),
1605 };
1606 let msg = ProtoMessage {
1607 metadata: Default::default(),
1608 message_type: Some(LinkType(link)),
1609 };
1610 let _ = tx.send(Ok(msg)).await;
1611 }
1612 ConnType::Remote
1613 }
1614 ConnType::Edge => {
1615 self.connection_table().update(idx, |conn| {
1616 conn.set_connection_type(ConnType::Edge);
1617 });
1618 ConnType::Edge
1619 }
1620 other => other,
1621 };
1622
1623 let _ = conn_index_tx.send(Ok(idx));
1624 Some((idx, category))
1625 }
1626
1627 fn process_stream(
1628 &self,
1629 mut stream: impl Stream<Item = Result<Message, Status>> + Unpin + Send + 'static,
1630 setup: StreamSetup,
1631 client_config: Option<ClientConfig>,
1632 cancellation_token: CancellationToken,
1633 category: ConnType,
1634 from_control_plane: bool,
1635 ) -> Result<
1636 (
1637 JoinHandle<()>,
1638 oneshot::Receiver<Result<u64, DataPathError>>,
1639 ),
1640 DataPathError,
1641 > {
1642 let self_clone = self.clone();
1644 let token_clone = cancellation_token.clone();
1645 let client_conf_clone = client_config.clone();
1646 let tx_cp: Option<Sender<Result<Message, Status>>> = self.get_tx_control_plane();
1647 let watch = self.get_drain_watch()?;
1648 let is_local = category.is_local();
1649
1650 let (conn_index_tx, conn_index_rx) = oneshot::channel();
1651
1652 let require_header_mac = match &setup {
1653 StreamSetup::Registered(idx) => self
1654 .forwarder()
1655 .get_connection(*idx)
1656 .map(|c| c.require_header_mac())
1657 .unwrap_or(false),
1658 StreamSetup::Pending { connection, .. } => connection.require_header_mac(),
1659 };
1660
1661 let span = tracing::info_span!(
1662 "process_stream",
1663 service_id = %self.internal.service_id,
1664 conn_index = match &setup {
1665 StreamSetup::Registered(idx) => *idx,
1666 _ => 0,
1667 },
1668 is_local,
1669 );
1670
1671 let handle = crate::runtime::spawn(async move {
1672 let mut try_to_reconnect = true;
1673
1674 let Some((conn_index, category)) = self_clone
1677 .resolve_connection(
1678 &mut stream,
1679 setup,
1680 category,
1681 conn_index_tx,
1682 &watch,
1683 &token_clone,
1684 )
1685 .await
1686 else {
1687 return;
1688 };
1689
1690 let mut watch = std::pin::pin!(watch.signaled());
1691 loop {
1692 tokio::select! {
1693 next = stream.next() => {
1694 match next {
1695 Some(result) => {
1696 match result {
1697 Ok(msg) => {
1698 if !is_local
1699 && !msg.is_link()
1700 && !msg.is_subscription_ack()
1701 && let Err(e) = self_clone
1702 .verify_remote_header_mac(conn_index, &msg, require_header_mac)
1703 {
1704 error!(
1705 %conn_index,
1706 error = %e.chain(),
1707 "SLIM header integrity verification failed",
1708 );
1709 continue;
1710 }
1711 let is_remote = !is_local
1718 && self_clone
1719 .forwarder()
1720 .get_connection(conn_index)
1721 .map(|c| matches!(c.connection_type(), ConnType::Remote | ConnType::Edge))
1722 .unwrap_or(false);
1723 if is_remote
1724 && !from_control_plane
1725 && let Some(txcp) = &tx_cp
1726 {
1727 let cp_msg = match msg.get_type() {
1728 SubscribeType(_) => ProtoMessage::builder()
1731 .source(msg.get_source())
1732 .destination(msg.get_dst())
1733 .build_subscribe()
1734 .ok(),
1735 UnsubscribeType(_) => ProtoMessage::builder()
1736 .source(msg.get_source())
1737 .destination(msg.get_dst())
1738 .build_unsubscribe()
1739 .ok(),
1740 _ => None
1741 };
1742 if let Some(m) = cp_msg {
1743 let _ = txcp.send(Ok(m)).await;
1744 }
1745 }
1746
1747 if let Err(e) = self_clone.handle_new_message(conn_index, category, msg).await {
1748 if matches!(e, DataPathError::NegotiationError(_)) {
1750 error!(%conn_index, "fatal link negotiation error, closing connection");
1751 try_to_reconnect = false;
1752 break;
1753 }
1754 debug!(%conn_index, error = %e.chain(), "error processing incoming message");
1755 if is_local {
1757 self_clone.send_error_to_local_app(conn_index, e).await;
1759 }
1760 }
1761 }
1762 Err(e) => {
1763 if e.code() == tonic::Code::PermissionDenied {
1764 warn!(
1765 %conn_index,
1766 message = %e.message(),
1767 "connection rejected by remote, will not reconnect"
1768 );
1769 try_to_reconnect = false;
1770 } else if let Some(io_err) = MessageProcessor::match_for_io_error(&e) {
1771 if io_err.kind() == std::io::ErrorKind::BrokenPipe {
1772 info!(%conn_index, "connection closed by peer");
1773 }
1774 } else {
1775 error!(error = %e.chain(), "error receiving messages");
1776 }
1777 break;
1778 }
1779 }
1780 }
1781 None => {
1782 debug!(%conn_index, "end of stream");
1783 break;
1784 }
1785 }
1786 }
1787 _ = &mut watch => {
1788 info!(%conn_index, "shutting down stream on drain");
1789 try_to_reconnect = false;
1790 break;
1791 }
1792 _ = token_clone.cancelled() => {
1793 info!(%conn_index, "shutting down stream on cancellation token");
1794 try_to_reconnect = false;
1795 break;
1796 }
1797 }
1798 }
1799
1800 drop(stream);
1804
1805 let mut connected = false;
1806
1807 if try_to_reconnect
1808 && !matches!(category, ConnType::Remote)
1809 && let Some(config) = client_conf_clone
1810 {
1811 connected = self_clone.reconnect(config, conn_index, &token_clone)
1815 .instrument(tracing::Span::none())
1816 .await;
1817 } else {
1818 debug!(%conn_index, "close connection")
1819 }
1820
1821 if !connected {
1822 let local_subs = self_clone
1824 .forwarder()
1825 .on_connection_drop(conn_index, category);
1826 let _remote_subs = self_clone
1827 .remote_sync()
1828 .on_connection_drop(conn_index);
1829
1830 if matches!(category, ConnType::Peer) {
1832 self_clone.peer_sync().remove_peer_conn(conn_index);
1833 }
1834
1835 {
1840 let fwd = self_clone.peer_sync();
1841 for name in local_subs.keys() {
1842 let still_reachable = name.name.is_some_and(|enc| {
1843 self_clone
1844 .forwarder()
1845 .on_publish_msg_match(enc, u64::MAX, u32::MAX, MatchFilter::ALL)
1846 .is_ok()
1847 });
1848 if !still_reachable {
1849 debug!(
1850 %name,
1851 %conn_index,
1852 ?category,
1853 "notifying peers of unsubscription (connection drop)"
1854 );
1855 fwd.notify_peers_unsubscribe(&self_clone, name).await;
1856 } else {
1857 debug!(
1858 %name,
1859 %conn_index,
1860 ?category,
1861 "name still reachable, not emitting removal"
1862 );
1863 }
1864 }
1865 }
1866
1867
1868 if !is_local {
1870 MessageProcessor::notify_control_plane_subscriptions_lost(
1871 tx_cp, local_subs, conn_index,
1872 )
1873 .await;
1874 }
1875
1876 info!(telemetry = true, counter.num_active_connections = -1);
1877 }
1878 }.instrument(span));
1879
1880 Ok((handle, conn_index_rx))
1881 }
1882
1883 fn match_for_io_error(err_status: &Status) -> Option<&std::io::Error> {
1884 let mut err: &(dyn std::error::Error + 'static) = err_status;
1885
1886 loop {
1887 if let Some(io_err) = err.downcast_ref::<std::io::Error>() {
1888 return Some(io_err);
1889 }
1890
1891 #[cfg(not(target_arch = "wasm32"))]
1895 if let Some(h2_err) = err.downcast_ref::<h2::Error>()
1896 && let Some(io_err) = h2_err.get_io()
1897 {
1898 return Some(io_err);
1899 }
1900
1901 err = err.source()?;
1902 }
1903 }
1904
1905 pub fn subscription_table(&self) -> &SubscriptionTableImpl {
1906 &self.internal.forwarder.subscription_table
1907 }
1908
1909 pub fn connection_table(&self) -> &ConnectionTable<Connection> {
1910 &self.internal.forwarder.connection_table
1911 }
1912
1913 pub fn service_id(&self) -> &str {
1915 &self.internal.service_id
1916 }
1917
1918 pub fn relay_peer_publishes(&self) -> bool {
1920 self.internal.relay_peer_publishes
1921 }
1922
1923 pub fn set_peer_sync(&self, peer_sync: crate::sync::PeerSync) {
1925 *self.internal.peer_sync.write() = peer_sync;
1926 }
1927
1928 pub(crate) fn peer_sync(&self) -> crate::sync::PeerSync {
1930 self.internal.peer_sync.read().clone()
1931 }
1932
1933 fn resolve_enforce_pqc(connection: &Connection, internal: &MessageProcessorInternal) -> bool {
1934 #[cfg(not(target_arch = "wasm32"))]
1935 {
1936 connection
1937 .config_data()
1938 .map(|c| c.tls_setting.config.enforce_pqc)
1939 .unwrap_or(internal.server_enforce_pqc)
1940 }
1941 #[cfg(target_arch = "wasm32")]
1942 {
1943 let _ = connection;
1945 internal.server_enforce_pqc
1946 }
1947 }
1948}
1949
1950#[cfg(not(target_arch = "wasm32"))]
1951impl ServerHandler for MessageProcessor {
1952 fn grpc_routes(&self) -> Option<tonic::service::Routes> {
1953 let svc = DataPlaneServiceServer::from_arc(Arc::new(self.clone()));
1954 Some(tonic::service::Routes::new(svc))
1955 }
1956
1957 fn on_websocket_accepted(&self) -> Option<websocket_server::OnAcceptedWebSocket> {
1958 let processor = self.clone();
1959 Some(Arc::new(move |accepted| {
1960 let processor = processor.clone();
1961 Box::pin(async move { processor.handle_websocket_accepted(accepted).await })
1962 }))
1963 }
1964}
1965
1966#[cfg(not(target_arch = "wasm32"))]
1967#[tonic::async_trait]
1968impl DataPlaneService for MessageProcessor {
1969 type OpenChannelStream = Pin<Box<dyn Stream<Item = Result<Message, Status>> + Send + 'static>>;
1970
1971 async fn open_channel(
1972 &self,
1973 request: Request<tonic::Streaming<Message>>,
1974 ) -> Result<Response<Self::OpenChannelStream>, Status> {
1975 let remote_addr = request.remote_addr();
1976 let local_addr = request.local_addr();
1977
1978 let stream = request.into_inner();
1979 let (tx, rx) = mpsc::channel(128);
1980
1981 let connection = Connection::new(ConnType::Remote, Channel::Server(tx))
1982 .with_remote_addr(remote_addr)
1983 .with_local_addr(local_addr)
1984 .with_require_header_mac(self.internal.server_require_header_mac);
1985
1986 debug!(
1987 remote = ?connection.remote_addr(),
1988 local = ?connection.local_addr(),
1989 "new connection received from remote",
1990 );
1991 info!(telemetry = true, counter.num_active_connections = 1);
1992
1993 self.process_stream(
1994 stream,
1995 StreamSetup::Pending {
1996 connection: Box::new(connection),
1997 existing_index: None,
1998 },
1999 None,
2000 CancellationToken::new(),
2001 ConnType::Remote,
2002 false,
2003 )
2004 .map_err(|e| {
2005 error!(error = %e.chain(), "error starting new processing stream");
2006 Status::unavailable(format!("error processing stream: {:?}", e))
2007 })?;
2008
2009 let out_stream = ReceiverStream::new(rx);
2010 Ok(Response::new(
2011 Box::pin(out_stream) as Self::OpenChannelStream
2012 ))
2013 }
2014}
2015
2016#[cfg(test)]
2017mod tests {
2018 use std::time::Duration;
2019
2020 use super::*;
2021 use crate::api::{ProtoMessage, ProtoName, ProtoSubscriptionAck};
2022 use crate::header_mac::HeaderMacSession;
2023 use crate::sync::remote::SubscriptionInfo;
2024 use tonic::Status;
2025
2026 async fn assert_failed_subscription_ack_is_sent(add: bool) {
2027 let processor = MessageProcessor::new();
2028 let (in_connection, _tx, mut rx) = processor
2029 .register_local_connection(false)
2030 .expect("failed to create local connection");
2031
2032 let source = ProtoName::from_strings(["org", "ns", "source"]).with_id(1);
2033 let destination = ProtoName::from_strings(["org", "ns", "destination"]).with_id(2);
2034 let ack_id: u64 = if add { 1 } else { 2 };
2035 let invalid_connection = u64::MAX - 1;
2036
2037 let builder = Message::builder()
2038 .source(source.clone())
2039 .destination(destination.clone())
2040 .incoming_conn(invalid_connection)
2041 .subscription_id(ack_id);
2042
2043 let msg = if add {
2044 builder.build_subscribe().unwrap()
2045 } else {
2046 builder.build_unsubscribe().unwrap()
2047 };
2048
2049 let result = processor
2050 .process_subscription(msg, in_connection, add)
2051 .await;
2052 assert!(matches!(
2053 result,
2054 Err(DataPathError::MessageProcessingError { .. })
2055 ));
2056
2057 let ack_msg = tokio::time::timeout(Duration::from_secs(1), rx.recv())
2058 .await
2059 .expect("timeout waiting for ack")
2060 .expect("ack channel closed")
2061 .expect("failed to receive ack message");
2062
2063 assert!(matches!(ack_msg.get_type(), SubscriptionAckType(_)));
2064 let ack = ack_msg.get_subscription_ack();
2065 assert_eq!(ack.subscription_id, ack_id);
2066 assert!(!ack.success, "failed ack should have success=false");
2067 assert!(
2068 !ack.error.is_empty(),
2069 "failed ack should include an error message"
2070 );
2071 }
2072
2073 #[tokio::test]
2074 async fn test_process_subscription_sends_failed_ack_on_subscribe_error() {
2075 assert_failed_subscription_ack_is_sent(true).await;
2076 }
2077
2078 #[tokio::test]
2079 async fn test_process_subscription_sends_failed_ack_on_unsubscribe_error() {
2080 assert_failed_subscription_ack_is_sent(false).await;
2081 }
2082
2083 #[tokio::test]
2086 async fn test_handle_link_message_is_local_ignored() {
2087 let processor = MessageProcessor::new();
2088 let link = ProtoLink { link_type: None };
2089 assert!(
2090 processor
2091 .handle_link_message(link, 0, ConnType::Local)
2092 .await
2093 .is_ok()
2094 );
2095 }
2096
2097 #[tokio::test]
2098 async fn test_handle_link_message_none_link_type_ignored() {
2099 let processor = MessageProcessor::new();
2100 let link = ProtoLink { link_type: None };
2101 assert!(
2102 processor
2103 .handle_link_message(link, 0, ConnType::Remote)
2104 .await
2105 .is_ok()
2106 );
2107 }
2108
2109 #[tokio::test]
2115 async fn test_handle_link_negotiation_post_negotiation_is_noop() {
2116 let processor = MessageProcessor::new();
2117 let payload = LinkNegotiationPayload {
2118 link_id: uuid::Uuid::new_v4().to_string(),
2119 slim_version: "1.0.0".into(),
2120 is_reply: false,
2121 link_ecdh_public_key: vec![],
2122 link_kem_payload: None,
2123 connection_type: 0,
2124 node_id: String::new(),
2125 deployment_name: String::new(),
2126 };
2127 assert!(
2129 processor
2130 .handle_link_negotiation(&payload, u64::MAX)
2131 .await
2132 .is_ok()
2133 );
2134 let (conn_id, _rx) = make_negotiated_server_conn(&processor, "1.2.0");
2136 assert!(
2137 processor
2138 .handle_link_negotiation(&payload, conn_id)
2139 .await
2140 .is_ok()
2141 );
2142 }
2143
2144 fn make_negotiated_server_conn(
2149 processor: &MessageProcessor,
2150 version: &str,
2151 ) -> (u64, tokio::sync::mpsc::Receiver<Result<Message, Status>>) {
2152 let (tx, rx) = mpsc::channel(16);
2153 let conn = Connection::new(ConnType::Remote, Channel::Server(tx))
2154 .with_require_header_mac(processor.internal.server_require_header_mac)
2155 .with_negotiation(&uuid::Uuid::new_v4().to_string(), version)
2156 .with_header_hmac(HeaderMacSession::new(b"01234567890123456789012345678901").unwrap());
2157 let conn_id = processor
2158 .forwarder()
2159 .on_connection_established(conn, None)
2160 .unwrap();
2161 (conn_id, rx)
2162 }
2163
2164 #[tokio::test]
2165 async fn test_negotiation_timeout_configurable() {
2166 let server_config = ServerConfig {
2167 endpoint: "localhost:12345".to_string(),
2168 negotiation_timeout_secs: 1, ..Default::default()
2170 };
2171 let processor = MessageProcessor::new_with_server_config(
2172 "test_service".to_string(),
2173 String::new(),
2174 &server_config,
2175 false,
2176 false,
2177 );
2178
2179 assert_eq!(
2180 processor.internal.negotiation_timeout,
2181 std::time::Duration::from_secs(1)
2182 );
2183 }
2184
2185 #[test]
2186 fn verify_remote_header_mac_strict_rejects_publish_without_mac_session() {
2187 let processor = MessageProcessor::new();
2188 let (tx, _rx) = mpsc::channel(16);
2190 let conn = Connection::new(ConnType::Remote, Channel::Server(tx))
2191 .with_require_header_mac(true)
2192 .with_negotiation(&uuid::Uuid::new_v4().to_string(), "1.2.0");
2193 let remote_conn = processor
2194 .forwarder()
2195 .on_connection_established(conn, None)
2196 .unwrap();
2197 let c = processor.forwarder().get_connection(remote_conn).unwrap();
2198 assert!(c.header_hmac().is_none());
2199
2200 let source = ProtoName::from_strings(["org", "default", "a"]).with_id(1);
2201 let dest = ProtoName::from_strings(["org", "default", "b"]).with_id(2);
2202 let msg = ProtoMessage::builder()
2203 .source(source)
2204 .destination(dest)
2205 .application_payload("text/plain", b"hey".to_vec())
2206 .build_publish()
2207 .expect("publish");
2208
2209 let err = processor
2210 .verify_remote_header_mac(remote_conn, &msg, true)
2211 .expect_err("unsigned publish must fail in strict mode without MAC session");
2212 assert!(matches!(err, DataPathError::NegotiationError(_)));
2213 }
2214
2215 #[test]
2216 fn verify_remote_header_mac_accepts_signed_inter_node_publish() {
2217 let processor = MessageProcessor::new();
2218 let (remote_conn, _rx) = make_negotiated_server_conn(&processor, "1.2.0");
2219 let link_id = processor
2220 .forwarder()
2221 .get_connection(remote_conn)
2222 .unwrap()
2223 .link_id()
2224 .expect("link id after negotiation");
2225
2226 let source = ProtoName::from_strings(["org", "default", "a"]).with_id(1);
2227 let dest = ProtoName::from_strings(["org", "default", "b"]).with_id(2);
2228 let require_header_mac = true;
2229 let mut msg = ProtoMessage::builder()
2230 .source(source)
2231 .destination(dest)
2232 .application_payload("text/plain", b"hey".to_vec())
2233 .build_publish()
2234 .expect("publish");
2235
2236 let mac = HeaderMacSession::new(b"01234567890123456789012345678901").unwrap();
2237 mac.sign_slim_header(msg.get_slim_header_mut(), &link_id)
2238 .expect("sign header");
2239
2240 assert!(
2241 processor
2242 .verify_remote_header_mac(remote_conn, &msg, require_header_mac)
2243 .is_ok()
2244 );
2245 }
2246
2247 #[test]
2248 fn verify_remote_header_mac_rejects_destination_tamper_after_sign() {
2249 let processor = MessageProcessor::new();
2250 let (remote_conn, _rx) = make_negotiated_server_conn(&processor, "1.2.0");
2251 let link_id = processor
2252 .forwarder()
2253 .get_connection(remote_conn)
2254 .unwrap()
2255 .link_id()
2256 .expect("link id after negotiation");
2257
2258 let source = ProtoName::from_strings(["org", "default", "a"]).with_id(1);
2259 let dest = ProtoName::from_strings(["org", "default", "b"]).with_id(2);
2260 let mut msg = ProtoMessage::builder()
2261 .source(source)
2262 .destination(dest)
2263 .application_payload("text/plain", b"hey".to_vec())
2264 .build_publish()
2265 .expect("publish");
2266
2267 let mac = HeaderMacSession::new(b"01234567890123456789012345678901").unwrap();
2268 let require_header_mac = true;
2269 mac.sign_slim_header(msg.get_slim_header_mut(), &link_id)
2270 .expect("sign header");
2271
2272 let header = msg.get_slim_header_mut();
2273 if let Some(dest) = header.destination.as_mut()
2274 && let Some(sn) = dest.str_name.as_mut()
2275 {
2276 sn.str_component_2.push_str("-integrity-test-tamper");
2277 }
2278
2279 let err = processor
2280 .verify_remote_header_mac(remote_conn, &msg, require_header_mac)
2281 .expect_err("tampered header must fail MAC verify");
2282 assert!(matches!(err, DataPathError::HeaderIntegrity(_)));
2283 }
2284
2285 #[tokio::test]
2286 #[allow(clippy::disallowed_methods)]
2287 async fn test_send_msg_raw_tamper_destination_env_var() {
2288 let _guard = ENV_LOCK.lock().await;
2289 unsafe {
2290 std::env::set_var("SLIM_TEST_TAMPER_DESTINATION", "1");
2291 }
2292
2293 let processor = MessageProcessor::new();
2294 let (conn_id, mut rx) = make_negotiated_server_conn(&processor, "1.2.0");
2295
2296 let source = ProtoName::from_strings(["org", "default", "a"]).with_id(1);
2297 let dest = ProtoName::from_strings(["org", "default", "b"]).with_id(2);
2298 let msg = ProtoMessage::builder()
2299 .source(source)
2300 .destination(dest)
2301 .application_payload("text/plain", b"hey".to_vec())
2302 .build_publish()
2303 .expect("publish");
2304
2305 processor
2306 .send_msg_raw(msg, conn_id)
2307 .await
2308 .expect("send_msg_raw failed");
2309
2310 let sent_msg = rx.recv().await.unwrap().unwrap();
2311 let header = sent_msg.get_slim_header();
2312 let dest_name = header.destination.as_ref().expect("destination");
2313 let str_name = dest_name.str_name.as_ref().expect("str_name");
2314 let require_header_mac = true;
2315
2316 assert!(str_name.str_component_2.ends_with("-integrity-test-tamper"));
2318
2319 let err = processor
2321 .verify_remote_header_mac(conn_id, &sent_msg, require_header_mac)
2322 .expect_err("tampered header must fail MAC verify");
2323 assert!(matches!(err, DataPathError::HeaderIntegrity(_)));
2324
2325 unsafe {
2326 std::env::remove_var("SLIM_TEST_TAMPER_DESTINATION");
2327 }
2328 }
2329
2330 #[tokio::test]
2331 async fn test_process_subscription_remote_ack_path_success() {
2332 let processor = MessageProcessor::new();
2335 let (local_conn, _tx_local, mut rx_local) = processor
2336 .register_local_connection(false)
2337 .expect("failed to create local connection");
2338
2339 let (remote_conn, mut rx_remote) = make_negotiated_server_conn(&processor, "1.2.0");
2340
2341 let source = ProtoName::from_strings(["org", "ns", "src"]).with_id(1);
2342 let destination = ProtoName::from_strings(["org", "ns", "dst"]).with_id(2);
2343 let upstream_ack_id: u64 = 100;
2344
2345 let sub_msg = Message::builder()
2347 .source(source.clone())
2348 .destination(destination.clone())
2349 .incoming_conn(local_conn)
2350 .forward_to(remote_conn)
2351 .subscription_id(upstream_ack_id)
2352 .build_subscribe()
2353 .unwrap();
2354
2355 let result = processor
2357 .process_subscription(sub_msg, local_conn, true)
2358 .await;
2359 assert!(result.is_ok());
2360
2361 let forwarded = tokio::time::timeout(Duration::from_secs(1), rx_remote.recv())
2364 .await
2365 .expect("timeout waiting for forwarded subscribe")
2366 .expect("forwarded subscribe channel closed")
2367 .unwrap();
2368 assert!(matches!(forwarded.get_type(), SubscribeType(_)));
2369
2370 let forwarded_sub_id = forwarded
2372 .get_subscription_id()
2373 .expect("forwarded subscribe must carry the same subscription_id");
2374 assert_eq!(
2375 forwarded_sub_id, upstream_ack_id,
2376 "subscription_id must not change when forwarding"
2377 );
2378
2379 let ack = ProtoSubscriptionAck {
2381 subscription_id: upstream_ack_id,
2382 success: true,
2383 error: String::new(),
2384 };
2385 processor.peer_sync().resolve_ack(
2386 ack.subscription_id,
2387 if ack.success {
2388 Ok(())
2389 } else {
2390 Err(DataPathError::RemoteSubscriptionAckError(ack.error.clone()))
2391 },
2392 );
2393
2394 let upstream_ack = tokio::time::timeout(Duration::from_secs(2), rx_local.recv())
2396 .await
2397 .expect("timeout waiting for upstream ack")
2398 .expect("upstream ack channel closed")
2399 .expect("upstream ack should be Ok");
2400
2401 assert!(matches!(upstream_ack.get_type(), SubscriptionAckType(_)));
2402 let ack_inner = upstream_ack.get_subscription_ack();
2403 assert_eq!(ack_inner.subscription_id, upstream_ack_id);
2404 assert!(ack_inner.success);
2405 }
2406
2407 #[tokio::test]
2408 async fn test_process_subscription_remote_ack_error_forwarded_upstream() {
2409 let processor = MessageProcessor::new();
2411 let (local_conn, _tx_local, mut rx_local) = processor
2412 .register_local_connection(false)
2413 .expect("failed to create local connection");
2414
2415 let (remote_conn, mut rx_remote) = make_negotiated_server_conn(&processor, "1.2.0");
2416
2417 let source = ProtoName::from_strings(["org", "ns", "src"]).with_id(1);
2418 let destination = ProtoName::from_strings(["org", "ns", "dst"]).with_id(2);
2419 let upstream_ack_id: u64 = 102;
2420
2421 let sub_msg = Message::builder()
2422 .source(source.clone())
2423 .destination(destination.clone())
2424 .incoming_conn(local_conn)
2425 .forward_to(remote_conn)
2426 .subscription_id(upstream_ack_id)
2427 .build_subscribe()
2428 .unwrap();
2429
2430 processor
2431 .process_subscription(sub_msg, local_conn, true)
2432 .await
2433 .unwrap();
2434
2435 let forwarded = tokio::time::timeout(Duration::from_secs(1), rx_remote.recv())
2436 .await
2437 .expect("timeout")
2438 .expect("channel closed")
2439 .unwrap();
2440
2441 let forwarded_sub_id = forwarded
2442 .get_subscription_id()
2443 .expect("forwarded subscribe must carry the same subscription_id");
2444 assert_eq!(
2445 forwarded_sub_id, upstream_ack_id,
2446 "subscription_id must not change when forwarding"
2447 );
2448
2449 let ack = ProtoSubscriptionAck {
2451 subscription_id: upstream_ack_id,
2452 success: false,
2453 error: "remote error".to_string(),
2454 };
2455 processor.peer_sync().resolve_ack(
2456 ack.subscription_id,
2457 if ack.success {
2458 Ok(())
2459 } else {
2460 Err(DataPathError::RemoteSubscriptionAckError(ack.error.clone()))
2461 },
2462 );
2463
2464 let upstream_ack = tokio::time::timeout(Duration::from_secs(2), rx_local.recv())
2465 .await
2466 .expect("timeout")
2467 .expect("channel closed")
2468 .expect("must be Ok");
2469
2470 assert!(matches!(upstream_ack.get_type(), SubscriptionAckType(_)));
2471 let ack_inner = upstream_ack.get_subscription_ack();
2472 assert_eq!(ack_inner.subscription_id, upstream_ack_id);
2473 assert!(!ack_inner.success);
2474 assert!(!ack_inner.error.is_empty());
2475 }
2476
2477 #[tokio::test]
2480 async fn test_notify_cp_subs_lost_sends_unsubscribes() {
2481 let (tx, mut rx) = mpsc::channel::<Result<Message, Status>>(16);
2482 let mut subs = HashMap::new();
2483 let name = ProtoName::from_strings(["org", "default", "svc"]);
2484 subs.insert(name.clone(), HashSet::from([1u64, 2u64]));
2485
2486 MessageProcessor::notify_control_plane_subscriptions_lost(Some(tx), subs, 42).await;
2487
2488 let msg = rx.recv().await.unwrap().unwrap();
2489 assert!(matches!(msg.get_type(), UnsubscribeType(_)));
2490 assert_eq!(msg.get_source(), name.clone());
2491 }
2492
2493 #[tokio::test]
2494 async fn test_notify_cp_subs_lost_no_tx_is_noop() {
2495 let subs = HashMap::from([(
2496 ProtoName::from_strings(["org", "default", "svc"]),
2497 HashSet::from([1u64]),
2498 )]);
2499 MessageProcessor::notify_control_plane_subscriptions_lost(None, subs, 1).await;
2501 }
2502
2503 #[tokio::test]
2504 async fn test_notify_cp_subs_lost_empty_subs() {
2505 let (tx, mut rx) = mpsc::channel::<Result<Message, Status>>(16);
2506 MessageProcessor::notify_control_plane_subscriptions_lost(Some(tx), HashMap::new(), 1)
2507 .await;
2508 assert!(rx.try_recv().is_err());
2510 }
2511
2512 #[tokio::test]
2515 async fn test_restore_remote_subscriptions_with_tracking() {
2516 let processor = MessageProcessor::new();
2517 let (conn_id, mut rx) = make_negotiated_server_conn(&processor, "1.2.0");
2518
2519 let source = ProtoName::from_strings(["org", "default", "src"]);
2520 let dest = ProtoName::from_strings(["org", "default", "dst"]);
2521 let sub = SubscriptionInfo::new(source.clone(), dest.clone(), "id1".into(), conn_id, 7);
2522 let subs = HashSet::from([sub]);
2523
2524 processor
2525 .restore_remote_subscriptions(&subs, conn_id, true)
2526 .await;
2527
2528 let msg = rx.recv().await.unwrap().unwrap();
2530 assert!(matches!(msg.get_type(), SubscribeType(_)));
2531
2532 let tracked = processor
2534 .remote_sync()
2535 .get_subscriptions_for_reconnect(conn_id);
2536 assert_eq!(tracked.len(), 1);
2537 }
2538
2539 #[tokio::test]
2540 async fn test_restore_remote_subscriptions_without_tracking() {
2541 let processor = MessageProcessor::new();
2542 let (conn_id, mut rx) = make_negotiated_server_conn(&processor, "1.2.0");
2543
2544 let source = ProtoName::from_strings(["org", "default", "src"]);
2545 let dest = ProtoName::from_strings(["org", "default", "dst"]);
2546 let sub = SubscriptionInfo::new(source.clone(), dest.clone(), "id1".into(), conn_id, 7);
2547 let subs = HashSet::from([sub]);
2548
2549 processor
2550 .restore_remote_subscriptions(&subs, conn_id, false)
2551 .await;
2552
2553 let msg = rx.recv().await.unwrap().unwrap();
2555 assert!(matches!(msg.get_type(), SubscribeType(_)));
2556
2557 let tracked = processor
2559 .remote_sync()
2560 .get_subscriptions_for_reconnect(conn_id);
2561 assert!(tracked.is_empty());
2562 }
2563}