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