Skip to main content

slim_controller/
service.rs

1// Copyright AGNTCY Contributors (https://github.com/agntcy)
2// SPDX-License-Identifier: Apache-2.0
3
4use std::collections::{HashMap, VecDeque};
5use std::pin::Pin;
6use std::sync::Arc;
7
8use std::time::Duration;
9
10use display_error_chain::ErrorChainExt;
11use slim_config::server::ServerConfig;
12use slim_session::subscription_manager::SubscriptionManager;
13use tokio::sync::mpsc;
14use tokio::task::JoinHandle;
15use tokio_stream::{Stream, StreamExt, wrappers::ReceiverStream};
16use tokio_util::sync::CancellationToken;
17use tonic::{Request, Response, Status};
18use tracing::{debug, error, info};
19
20use crate::api::proto::api::v1::control_message::Payload;
21use crate::api::proto::api::v1::controller_service_server::ControllerServiceServer;
22use crate::api::proto::api::v1::{
23    self, ConnectionDetails, ConnectionDirection, ConnectionListResponse, ConnectionType,
24    RouteListResponse,
25};
26use crate::api::proto::api::v1::{
27    ConnectionEntry, ControlMessage, RouteEntry,
28    controller_service_client::ControllerServiceClient,
29    controller_service_server::ControllerService as GrpcControllerService,
30};
31use crate::errors::ControllerError;
32use prost_types::Struct;
33use slim_config::client::{ClientConfig, TransportChannel};
34use slim_datapath::api::{
35    MessageType::Link as LinkMessageType, MessageType::Subscribe,
36    MessageType::SubscriptionAck as SubscriptionAckType, MessageType::Unsubscribe,
37    ProtoMessage as DataPlaneMessage,
38};
39use slim_datapath::api::{NameId, ProtoName};
40use slim_datapath::message_processing::MessageProcessor;
41use slim_datapath::messages::utils::SlimHeaderFlags;
42use slim_datapath::tables::{ConnType, SubscriptionTable};
43
44type TxChannel = mpsc::Sender<Result<ControlMessage, Status>>;
45type TxChannels = HashMap<String, TxChannel>;
46
47/// Maximum number of queued subscription notifications
48const MAX_QUEUED_NOTIFICATIONS: usize = 1000;
49
50/// Timeout for waiting on a subscription ack from the datapath.
51const SUBSCRIPTION_ACK_TIMEOUT: Duration = Duration::from_secs(30);
52
53use slim_auth::auth_provider::AuthProvider;
54use slim_auth::traits::TokenProvider;
55
56/// Settings struct for creating a ControlPlane instance
57#[derive(Clone)]
58pub struct ControlPlaneSettings {
59    /// Node ID of this SLIM instance
60    pub id: String,
61    /// Optional group name
62    pub group_name: Option<String>,
63    /// Server configurations
64    pub servers: Vec<ServerConfig>,
65    /// Client configurations
66    pub clients: Vec<ClientConfig>,
67    /// Message processor instance
68    pub message_processor: MessageProcessor,
69    /// array of connection details used by the control
70    /// plane to store the connection settings (e.g., TLS settings).
71    pub connection_details: Vec<ConnectionDetails>,
72    /// Optional auth provider for generating registration credentials.
73    pub auth_provider: Option<AuthProvider>,
74}
75
76/// Inner structure for the controller service
77/// This structure holds the internal state of the controller service,
78/// including the ID, message processor, connections, and channels.
79/// It is normally wrapped in an Arc to allow shared ownership across multiple threads.
80struct ControllerServiceInternal {
81    /// Node ID of this SLIM instance
82    id: String,
83
84    /// optional group name
85    group_name: Option<String>,
86
87    /// underlying message processor
88    message_processor: MessageProcessor,
89
90    /// channel to send messages into the datapath
91    tx_slim: mpsc::Sender<Result<DataPlaneMessage, Status>>,
92
93    /// channels to send control messages
94    tx_channels: parking_lot::RwLock<TxChannels>,
95
96    /// cancellation token for graceful shutdown
97    cancellation_tokens: parking_lot::RwLock<HashMap<String, CancellationToken>>,
98
99    /// drain watch channel
100    drain_watch: parking_lot::RwLock<Option<drain::Watch>>,
101
102    /// queue for pending subscription notifications when connections are down
103    pending_notifications: Arc<parking_lot::Mutex<VecDeque<ControlMessage>>>,
104
105    /// Manages pending subscription ack tracking (id generation, registration, resolution).
106    subscription_manager: SubscriptionManager,
107
108    /// connection details used by control plane to store connection settings
109    connection_details: Vec<ConnectionDetails>,
110
111    /// Maps (subscription_name, link_id) → subscription_id for route tracking
112    route_subscription_ids: parking_lot::Mutex<HashMap<(ProtoName, u64), u64>>,
113
114    /// Reverse index: link_id → conn_id for O(1) lookup.
115    /// Populated lazily on first resolve and eagerly on connection create/delete.
116    link_id_to_conn_id: parking_lot::RwLock<HashMap<String, u64>>,
117
118    /// JoinHandles for control-plane stream processing tasks, keyed by endpoint.
119    stream_handles: parking_lot::Mutex<HashMap<String, tokio::task::JoinHandle<()>>>,
120
121    /// Optional auth provider for generating registration credentials.
122    auth_provider: Option<AuthProvider>,
123}
124
125#[derive(Clone)]
126struct ControllerService {
127    /// internal service state
128    inner: Arc<ControllerServiceInternal>,
129}
130
131/// The ControlPlane service is the main entry point for the controller service.
132pub struct ControlPlane {
133    /// servers
134    servers: Vec<ServerConfig>,
135
136    /// clients
137    clients: Vec<ClientConfig>,
138
139    /// drain signal channel
140    drain_signal: parking_lot::RwLock<Option<drain::Signal>>,
141
142    /// controller
143    controller: ControllerService,
144
145    /// channel to receive message from the datapath
146    /// to be used in listen_from_data_plane
147    rx_slim_option: Option<mpsc::Receiver<Result<DataPlaneMessage, Status>>>,
148}
149
150/// ControllerServiceInternal implements Drop trait to cancel all running listeners and
151/// clean up resources.
152impl Drop for ControlPlane {
153    fn drop(&mut self) {
154        // cancel all running listeners
155        for (_endpoint, token) in self.controller.inner.cancellation_tokens.write().drain() {
156            token.cancel();
157        }
158    }
159}
160
161pub(crate) fn from_server_config(server_config: &ServerConfig) -> ConnectionDetails {
162    let mut endpoint = server_config.endpoint.clone();
163    let mut external_endpoint = None;
164    let mut spire_socket_path = None;
165    let mut trust_domain = None;
166    let mut remaining_fields = std::collections::BTreeMap::new();
167
168    if let Some(m) = &server_config.metadata {
169        for (k, v) in &m.inner {
170            match k.as_str() {
171                "local_endpoint" => {
172                    if let Some(s) = v.as_str()
173                        && !s.is_empty()
174                    {
175                        endpoint = s.to_string();
176                    }
177                }
178                "external_endpoint" => {
179                    if let Some(s) = v.as_str()
180                        && !s.is_empty()
181                    {
182                        external_endpoint = Some(s.to_string());
183                    }
184                }
185                "spire_socket_path" => {
186                    if let Some(s) = v.as_str()
187                        && !s.is_empty()
188                    {
189                        spire_socket_path = Some(s.to_string());
190                    }
191                }
192                "trust_domain" => {
193                    if let Some(s) = v.as_str()
194                        && !s.is_empty()
195                    {
196                        trust_domain = Some(s.to_string());
197                    }
198                }
199                _ => {
200                    remaining_fields.insert(k.clone(), prost_types::Value::from(v));
201                }
202            }
203        }
204    }
205
206    let spire_mtls = if !server_config.tls_setting.insecure || spire_socket_path.is_some() {
207        Some(v1::connection_details::SpireMtls {
208            socket_path: spire_socket_path.unwrap_or_default(),
209            trust_domain,
210        })
211    } else {
212        None
213    };
214
215    let metadata = if remaining_fields.is_empty() {
216        None
217    } else {
218        Some(Struct {
219            fields: remaining_fields,
220        })
221    };
222
223    ConnectionDetails {
224        endpoint,
225        external_endpoint,
226        spire_mtls,
227        metadata,
228    }
229}
230
231/// ControlPlane implements the service trait for the controller service.
232impl ControlPlane {
233    /// Create a new ControlPlane service instance
234    /// This function initializes the ControlPlane with the given ID, servers, clients, and message processor.
235    /// It also sets up the internal state, including the connections and channels.
236    /// # Arguments
237    /// * `id` - The ID of the SLIM instance.
238    /// * `servers` - A vector of server configurations.
239    /// * `clients` - A vector of client configurations.
240    /// * `drain_rx` - A drain watch channel for graceful shutdown.
241    /// * `message_processor` - An Arc to the message processor instance.
242    /// * `pubsub_servers` - A slice of server configurations for pub/sub connections.
243    /// # Returns
244    /// A new instance of ControlPlane.
245    pub fn new(config: ControlPlaneSettings) -> Self {
246        // create local connection with the message processor
247        let (_, tx_slim, rx_slim) = config
248            .message_processor
249            .register_local_connection(true)
250            .unwrap();
251
252        let (signal, watch) = drain::channel();
253
254        ControlPlane {
255            servers: config.servers,
256            clients: config.clients,
257            controller: ControllerService {
258                inner: Arc::new(ControllerServiceInternal {
259                    id: config.id,
260                    group_name: config.group_name,
261                    message_processor: config.message_processor,
262                    subscription_manager: SubscriptionManager::new(tx_slim.clone()),
263                    tx_slim,
264                    tx_channels: parking_lot::RwLock::new(HashMap::new()),
265                    cancellation_tokens: parking_lot::RwLock::new(HashMap::new()),
266                    drain_watch: parking_lot::RwLock::new(Some(watch)),
267                    pending_notifications: Arc::new(parking_lot::Mutex::new(VecDeque::new())),
268                    connection_details: config.connection_details,
269                    route_subscription_ids: parking_lot::Mutex::new(HashMap::new()),
270                    link_id_to_conn_id: parking_lot::RwLock::new(HashMap::new()),
271                    stream_handles: parking_lot::Mutex::new(HashMap::new()),
272                    auth_provider: config.auth_provider,
273                }),
274            },
275            drain_signal: parking_lot::RwLock::new(Some(signal)),
276            rx_slim_option: Some(rx_slim),
277        }
278    }
279
280    /// Take an existing ControlPlane instance and return a new one with the provided clients.
281    pub fn with_clients(mut self, clients: Vec<ClientConfig>) -> Self {
282        self.clients = clients;
283        self
284    }
285
286    /// Take an existing ControlPlane instance and return a new one with the provided servers.
287    pub fn with_servers(mut self, servers: Vec<ServerConfig>) -> Self {
288        self.servers = servers;
289        self
290    }
291
292    /// Run the clients and servers of the ControlPlane service.
293    /// This function starts all the servers and clients defined in the ControlPlane.
294    /// # Returns
295    /// A Result indicating success or failure of the operation.
296    /// # Errors
297    /// If there is an error starting any of the servers or clients, it will return a ControllerError.
298    pub async fn run(&mut self) -> Result<(), ControllerError> {
299        let rx = self
300            .rx_slim_option
301            .take()
302            .ok_or(ControllerError::AlreadyStarted)?;
303
304        // Collect servers to avoid borrowing self both mutably and immutably
305        let servers = self.servers.clone();
306        let clients = self.clients.clone();
307
308        // run all servers
309        for server in servers {
310            self.run_server(server).await?;
311        }
312
313        // run all clients
314        for client in clients {
315            self.run_client(client).await?;
316        }
317
318        self.listen_from_data_plane(rx).await?;
319
320        Ok(())
321    }
322
323    pub async fn deregister(&self) -> Result<(), ControllerError> {
324        let node_id = self.controller.inner.id.clone();
325        let deregister_msg = ControlMessage {
326            message_id: uuid::Uuid::new_v4().to_string(),
327            payload: Some(Payload::DeregisterNodeRequest(v1::DeregisterNodeRequest {
328                node: Some(v1::Node { id: node_id }),
329            })),
330        };
331        let channels: Vec<(String, TxChannel)> = self
332            .controller
333            .inner
334            .tx_channels
335            .read()
336            .iter()
337            .map(|(ep, tx)| (ep.clone(), tx.clone()))
338            .collect();
339        for (endpoint, tx) in channels {
340            if let Err(e) = tx.send(Ok(deregister_msg.clone())).await {
341                error!(%endpoint, error = %e, "failed to send deregister request");
342            }
343        }
344        Ok(())
345    }
346
347    pub async fn shutdown(&self) -> Result<(), ControllerError> {
348        // Get signal drain
349        let signal = self
350            .drain_signal
351            .write()
352            .take()
353            .ok_or(ControllerError::AlreadyStopped)?;
354
355        // Stop everything using the cancellation tokens
356        self.controller
357            .inner
358            .cancellation_tokens
359            .write()
360            .drain()
361            .for_each(|(endpoint, token)| {
362                info!(%endpoint, "stopping");
363                token.cancel();
364            });
365
366        // Drop watch channel
367        self.controller.inner.drain_watch.write().take();
368
369        // Wait for drain to complete
370        signal.drain().await;
371
372        Ok(())
373    }
374
375    async fn listen_from_data_plane(
376        &mut self,
377        mut rx: mpsc::Receiver<Result<DataPlaneMessage, Status>>,
378    ) -> Result<(), ControllerError> {
379        let cancellation_token = CancellationToken::new();
380        let cancellation_token_clone = cancellation_token.clone();
381
382        self.controller
383            .inner
384            .cancellation_tokens
385            .write()
386            .insert("DATA_PLANE".to_string(), cancellation_token_clone);
387
388        let clients = self.clients.clone();
389        let controller = self.controller.clone();
390
391        // Get a drain watch clone
392        let watch = self.controller.drain_watch()?;
393
394        debug!("Starting data plane listener");
395        tokio::spawn(async move {
396            let mut drain_fut = std::pin::pin!(watch.signaled());
397            loop {
398                tokio::select! {
399                    next = rx.recv() => {
400                        match next {
401                            Some(res) => {
402                                match res {
403                                    Ok(msg) => {
404                                        debug!("Received message {:?} from data plane, forwarding to control plane", msg);
405                                        match msg.get_type() {
406                                            Subscribe(_) => {
407                                                controller.handle_subscribe_message(msg.get_dst(), &clients).await;
408                                            }
409                                            Unsubscribe(_) => {
410                                                controller.handle_unsubscribe_message(msg.get_dst(), &clients).await;
411                                            }
412                                            SubscriptionAckType(_) => {
413                                                controller.inner.subscription_manager.resolve_ack(msg.get_subscription_ack());
414                                            }
415                                            LinkMessageType(link) => {
416                                                controller.handle_link_received(link, &clients).await;
417                                            }
418                                            _ => {
419                                                debug!("Ignoring unexpected message type from dataplane: {:?}", msg.get_type());
420                                            }
421                                        }
422                                    }
423                                    Err(e) => {
424                                        error!(error = %e.chain(), "received error from the data plane");
425                                        continue;
426                                    }
427                                }
428                            }
429                            None => {
430                                debug!("Data plane receiver channel closed.");
431                                break;
432                            }
433                        }
434                    }
435                    _ = cancellation_token.cancelled() => {
436                        debug!("shutting down stream on cancellation token");
437                        break;
438                    }
439                    _ = &mut drain_fut => {
440                        debug!("shutting down stream on drain");
441                        break;
442                    }
443                }
444            }
445        });
446        Ok(())
447    }
448
449    /// Stop the ControlPlane service.
450    /// This function stops all running listeners and cancels any ongoing operations.
451    /// It cleans up the internal state and ensures that all resources are released properly.
452    pub fn stop(&mut self) {
453        info!("stopping controller service");
454
455        // cancel all running listeners
456        for (endpoint, token) in self.controller.inner.cancellation_tokens.write().drain() {
457            info!(%endpoint, "stopping");
458            token.cancel();
459        }
460    }
461
462    /// Run a client configuration.
463    /// This function connects to the control plane using the provided client configuration.
464    /// It checks if the client is already running and if not, it starts a new connection.
465    async fn run_client(&mut self, client: ClientConfig) -> Result<(), ControllerError> {
466        if self
467            .controller
468            .inner
469            .cancellation_tokens
470            .read()
471            .contains_key(&client.endpoint)
472        {
473            return Err(ControllerError::ClientAlreadyRunning(client.endpoint));
474        }
475
476        let cancellation_token = CancellationToken::new();
477
478        let tx = self
479            .controller
480            .connect(client.clone(), cancellation_token.clone())
481            .await?;
482
483        // Store the cancellation token in the controller service
484        self.controller
485            .inner
486            .cancellation_tokens
487            .write()
488            .insert(client.endpoint.clone(), cancellation_token);
489
490        // Store the sender in the tx_channels map
491        self.controller
492            .inner
493            .tx_channels
494            .write()
495            .insert(client.endpoint.clone(), tx);
496
497        // return the sender for control messages
498        Ok(())
499    }
500
501    /// Run a server configuration.
502    /// This function starts a server using the provided server configuration.
503    /// It checks if the server is already running and if not, it starts a new server.
504    pub async fn run_server(&mut self, config: ServerConfig) -> Result<(), ControllerError> {
505        // Check if the server is already running
506        if self
507            .controller
508            .inner
509            .cancellation_tokens
510            .read()
511            .contains_key(&config.endpoint)
512        {
513            error!(endpoint = config.endpoint, "server is already running",);
514            return Err(ControllerError::ServerAlreadyRunning(config.endpoint));
515        }
516
517        let token = config
518            .run_grpc_server(
519                &[ControllerServiceServer::new(self.controller.clone())],
520                self.controller.drain_watch()?,
521            )
522            .await?;
523
524        // Store the cancellation token in the controller service
525        self.controller
526            .inner
527            .cancellation_tokens
528            .write()
529            .insert(config.endpoint.clone(), token.clone());
530
531        info!(%config.endpoint, "started controlplane server");
532
533        Ok(())
534    }
535}
536
537impl ControllerService {
538    fn resolve_connection_by_link_id(&self, link_id: &str) -> Result<Option<u64>, String> {
539        let cached = self.inner.link_id_to_conn_id.read().get(link_id).copied();
540        if let Some(conn_id) = cached {
541            if self
542                .inner
543                .message_processor
544                .connection_table()
545                .get(conn_id)
546                .is_some_and(|conn| conn.link_id().as_deref() == Some(link_id))
547            {
548                return Ok(Some(conn_id));
549            }
550            self.inner.link_id_to_conn_id.write().remove(link_id);
551        }
552
553        let mut resolved: Option<u64> = None;
554        self.inner
555            .message_processor
556            .connection_table()
557            .for_each(|id, conn| {
558                if conn.link_id().as_deref() == Some(link_id) && resolved.is_none() {
559                    resolved = Some(id);
560                }
561            });
562
563        if let Some(conn_id) = resolved {
564            self.inner
565                .link_id_to_conn_id
566                .write()
567                .insert(link_id.to_string(), conn_id);
568        }
569
570        Ok(resolved)
571    }
572
573    fn disconnect_connection_by_link_id(&self, link_id: &str) -> Result<(), String> {
574        if link_id.trim().is_empty() {
575            return Err("link_id cannot be empty".to_string());
576        }
577
578        let conn_id = match self.resolve_connection_by_link_id(link_id)? {
579            Some(id) => id,
580            None => {
581                return Err(format!("Connection with link_id {} not found", link_id));
582            }
583        };
584
585        if let Err(e) = self.inner.message_processor.disconnect(conn_id) {
586            // Best-effort delete: local/control-plane connections can lack config_data.
587            info!(
588                link_id = %link_id,
589                conn_id,
590                error = %e,
591                "Disconnect returned an error; continuing delete flow"
592            );
593        }
594
595        self.inner.link_id_to_conn_id.write().remove(link_id);
596        self.inner
597            .route_subscription_ids
598            .lock()
599            .retain(|(_name, cid), _| *cid != conn_id);
600
601        info!(link_id = %link_id, conn_id, "Successfully deleted connection by link_id");
602        Ok(())
603    }
604
605    fn resolve_route_connection(&self, route: &v1::Route) -> Result<Option<u64>, String> {
606        if let Some(link_id) = &route.link_id {
607            let trimmed = link_id.trim();
608            if !trimmed.is_empty() {
609                return self.resolve_connection_by_link_id(trimmed);
610            }
611        }
612
613        Ok(None)
614    }
615
616    async fn diff_connections(
617        &self,
618        desired_connections: &[v1::Connection],
619    ) -> Vec<v1::ConnectionAck> {
620        let mut connections_status = Vec::new();
621
622        let desired_link_ids: std::collections::HashSet<String> = desired_connections
623            .iter()
624            .map(|c| c.link_id.clone())
625            .collect();
626
627        let mut live_outgoing_link_ids: Vec<String> = Vec::new();
628        self.inner
629            .message_processor
630            .connection_table()
631            .for_each(|_id, conn| {
632                // Only manage Remote connections (CP-managed inter-group links).
633                // Peer connections are managed by the peer sync system and must not
634                // be deleted by the reconciler.
635                if conn.is_outgoing()
636                    && conn.connection_type() == slim_datapath::tables::ConnType::Remote
637                    && let Some(lid) = conn.link_id()
638                    && !lid.is_empty()
639                {
640                    live_outgoing_link_ids.push(lid);
641                }
642            });
643
644        for link_id in &live_outgoing_link_ids {
645            if desired_link_ids.contains(link_id) {
646                continue;
647            }
648            info!(link_id = %link_id, "desired state: removing connection");
649            let mut success = true;
650            let mut error_msg = String::new();
651            if let Err(err) = self.disconnect_connection_by_link_id(link_id) {
652                success = false;
653                error_msg = err;
654            }
655            connections_status.push(v1::ConnectionAck {
656                link_id: link_id.clone(),
657                success,
658                error_msg,
659            });
660        }
661
662        for conn in desired_connections {
663            let link_id = &conn.link_id;
664            if link_id.is_empty() {
665                continue;
666            }
667
668            let already_exists = match self.resolve_connection_by_link_id(link_id) {
669                Ok(Some(_)) => true,
670                Ok(None) => false,
671                Err(err) => {
672                    connections_status.push(v1::ConnectionAck {
673                        link_id: link_id.clone(),
674                        success: false,
675                        error_msg: err,
676                    });
677                    continue;
678                }
679            };
680
681            if already_exists {
682                connections_status.push(v1::ConnectionAck {
683                    link_id: link_id.clone(),
684                    success: true,
685                    error_msg: String::new(),
686                });
687                continue;
688            }
689
690            info!(?conn, "desired state: creating connection");
691            let mut success = true;
692            let mut error_msg = String::new();
693
694            match serde_json::from_str::<ClientConfig>(&conn.config_data) {
695                Err(e) => {
696                    success = false;
697                    error_msg = format!("Failed to parse config: {}", e);
698                }
699                Ok(client_config) => {
700                    match self
701                        .inner
702                        .message_processor
703                        .connect(client_config.clone(), None, None)
704                        .await
705                    {
706                        Err(e) => {
707                            success = false;
708                            error_msg = format!("Connection failed: {}", e);
709                        }
710                        Ok(conn_id) => {
711                            let requested_link_id = if client_config.link_id.trim().is_empty() {
712                                String::new()
713                            } else {
714                                client_config.link_id.clone()
715                            };
716                            if !requested_link_id.is_empty() {
717                                self.inner
718                                    .link_id_to_conn_id
719                                    .write()
720                                    .insert(requested_link_id, conn_id.1);
721                            }
722                            info!(
723                                link_id = %link_id,
724                                "Successfully created connection"
725                            );
726                        }
727                    }
728                }
729            }
730
731            connections_status.push(v1::ConnectionAck {
732                link_id: link_id.clone(),
733                success,
734                error_msg,
735            });
736        }
737
738        connections_status
739    }
740
741    fn resolve_desired_routes<'a>(
742        &self,
743        desired_routes: &'a [v1::Route],
744    ) -> (HashMap<(ProtoName, u64), &'a v1::Route>, Vec<v1::RouteAck>) {
745        type SubKey = (ProtoName, u64);
746        let mut desired_subs: HashMap<SubKey, &v1::Route> = HashMap::new();
747        let mut failures: Vec<v1::RouteAck> = Vec::new();
748
749        for sub in desired_routes {
750            match self.resolve_route_connection(sub) {
751                Ok(Some(conn_id)) => {
752                    let name = sub.name.clone().unwrap();
753                    desired_subs.insert((name, conn_id), sub);
754                }
755                Ok(None) => {
756                    failures.push(v1::RouteAck {
757                        route: Some(sub.clone()),
758                        success: false,
759                        error_msg: "connection not found".to_string(),
760                    });
761                }
762                Err(err) => {
763                    failures.push(v1::RouteAck {
764                        route: Some(sub.clone()),
765                        success: false,
766                        error_msg: err,
767                    });
768                }
769            }
770        }
771
772        (desired_subs, failures)
773    }
774
775    async fn delete_stale_subscriptions(
776        &self,
777        desired_subs: &HashMap<(ProtoName, u64), &v1::Route>,
778    ) -> Vec<v1::RouteAck> {
779        let active_subs: Vec<((ProtoName, u64), u64)> = self
780            .inner
781            .route_subscription_ids
782            .lock()
783            .iter()
784            .map(|((name, conn_id), sub_id)| ((name.clone(), *conn_id), *sub_id))
785            .collect();
786
787        let stale: Vec<_> = active_subs
788            .into_iter()
789            .filter(|((name, conn_id), _)| !desired_subs.contains_key(&(name.clone(), *conn_id)))
790            .collect();
791
792        let futs = stale.iter().map(|((name, conn_id), sub_id)| {
793            let name = name.clone();
794            let conn_id = *conn_id;
795            let sub_id = *sub_id;
796            async move {
797                let conn_alive = self
798                    .inner
799                    .message_processor
800                    .connection_table()
801                    .get(conn_id)
802                    .is_some();
803
804                let (success, error_msg) = if conn_alive {
805                    let source = name.clone().with_id(0);
806                    let unsub_msg = DataPlaneMessage::builder()
807                        .source(source)
808                        .destination(name.clone())
809                        .identity("")
810                        .flags(SlimHeaderFlags::default().with_recv_from(conn_id))
811                        .build_unsubscribe()
812                        .unwrap();
813
814                    match self
815                        .send_unsubscribe_message_with_ack(unsub_msg, sub_id)
816                        .await
817                    {
818                        Ok(()) => (true, String::new()),
819                        Err(err) => (false, format!("Failed to unsubscribe: {}", err)),
820                    }
821                } else {
822                    (true, String::new())
823                };
824
825                (name, conn_id, success, error_msg)
826            }
827        });
828
829        let results = futures::future::join_all(futs).await;
830
831        let mut routes_status = Vec::with_capacity(results.len());
832        for (name, conn_id, success, error_msg) in results {
833            if success {
834                self.inner
835                    .route_subscription_ids
836                    .lock()
837                    .remove(&(name.clone(), conn_id));
838            }
839
840            routes_status.push(v1::RouteAck {
841                route: Some(v1::Route {
842                    name: Some(name.clone()),
843                    link_id: None,
844                    direction: None,
845                }),
846                success,
847                error_msg,
848            });
849        }
850
851        routes_status
852    }
853
854    async fn create_new_subscriptions(
855        &self,
856        desired_subs: &HashMap<(ProtoName, u64), &v1::Route>,
857    ) -> Vec<v1::RouteAck> {
858        let mut routes_status = Vec::new();
859
860        let to_create: Vec<((ProtoName, u64), v1::Route)> = desired_subs
861            .iter()
862            .filter(|((name, conn_id), _)| {
863                !self
864                    .inner
865                    .route_subscription_ids
866                    .lock()
867                    .contains_key(&(name.clone(), *conn_id))
868            })
869            .map(|((name, conn_id), sub)| ((name.clone(), *conn_id), (*sub).clone()))
870            .collect();
871
872        // Report already-active subscriptions as success.
873        for ((name, conn_id), sub) in desired_subs {
874            let dominated = to_create
875                .iter()
876                .any(|((n, c), _)| n == name && *c == *conn_id);
877            if !dominated {
878                routes_status.push(v1::RouteAck {
879                    route: Some((*sub).clone()),
880                    success: true,
881                    error_msg: String::new(),
882                });
883            }
884        }
885
886        let futs = to_create.iter().map(|((name, conn_id), sub)| {
887            let name = name.clone();
888            let conn_id = *conn_id;
889            let sub = sub.clone();
890            async move {
891                let source = name.clone().with_id(0);
892                let sub_msg = DataPlaneMessage::builder()
893                    .source(source)
894                    .destination(name.clone())
895                    .identity("")
896                    .flags(SlimHeaderFlags::default().with_recv_from(conn_id))
897                    .build_subscribe()
898                    .unwrap();
899
900                let result = self.send_subscribe_message_with_ack(sub_msg).await;
901                (name, conn_id, sub, result)
902            }
903        });
904
905        let results = futures::future::join_all(futs).await;
906
907        for (name, conn_id, sub, result) in results {
908            let (success, error_msg) = match result {
909                Ok(subscription_id) => {
910                    self.inner
911                        .route_subscription_ids
912                        .lock()
913                        .insert((name, conn_id), subscription_id);
914                    info!(?sub, "desired state: created route");
915                    (true, String::new())
916                }
917                Err(err) => (false, format!("Failed to subscribe: {}", err)),
918            };
919
920            routes_status.push(v1::RouteAck {
921                route: Some(sub),
922                success,
923                error_msg,
924            });
925        }
926
927        routes_status
928    }
929
930    /// Handle new control messages.
931    async fn handle_new_control_message(
932        &self,
933        msg: ControlMessage,
934        tx: &mpsc::Sender<Result<ControlMessage, Status>>,
935    ) -> Result<(), ControllerError> {
936        match msg.payload {
937            Some(ref payload) => {
938                match payload {
939                    Payload::ConfigCommand(config) => {
940                        let (connections_status, routes_status) = if config.reconcile {
941                            let connections_status =
942                                self.diff_connections(&config.connections_to_create).await;
943                            let (desired_subs, mut routes_status) =
944                                self.resolve_desired_routes(&config.routes_to_set);
945                            routes_status
946                                .extend(self.delete_stale_subscriptions(&desired_subs).await);
947                            routes_status
948                                .extend(self.create_new_subscriptions(&desired_subs).await);
949                            (connections_status, routes_status)
950                        } else {
951                            let mut connections_status = Vec::new();
952                            let mut routes_status = Vec::new();
953
954                            // Process connections to delete by link_id.
955                            for link_id in &config.connections_to_delete {
956                                info!(link_id = %link_id, "received a connection to delete");
957                                let mut connection_success = true;
958                                let mut connection_error_msg = String::new();
959
960                                if let Err(err) = self.disconnect_connection_by_link_id(link_id) {
961                                    connection_success = false;
962                                    connection_error_msg = err;
963                                }
964
965                                connections_status.push(v1::ConnectionAck {
966                                    link_id: link_id.clone(),
967                                    success: connection_success,
968                                    error_msg: connection_error_msg,
969                                });
970                            }
971
972                            // Process connections to create
973                            for conn in &config.connections_to_create {
974                                info!(?conn, "received a connection to create");
975                                let mut connection_success = true;
976                                let mut connection_error_msg = String::new();
977
978                                match serde_json::from_str::<ClientConfig>(&conn.config_data) {
979                                    Err(e) => {
980                                        connection_success = false;
981                                        connection_error_msg =
982                                            format!("Failed to parse config: {}", e);
983                                    }
984                                    Ok(client_config) => {
985                                        let client_endpoint = &client_config.endpoint;
986                                        let requested_link_id =
987                                            if client_config.link_id.trim().is_empty() {
988                                                String::new()
989                                            } else {
990                                                client_config.link_id.clone()
991                                            };
992                                        let mut existing_conn_for_link_id = false;
993
994                                        if !requested_link_id.is_empty() {
995                                            match self
996                                                .resolve_connection_by_link_id(&requested_link_id)
997                                            {
998                                                Err(err) => {
999                                                    connection_success = false;
1000                                                    connection_error_msg = err;
1001                                                }
1002                                                Ok(Some(conn_id)) => {
1003                                                    existing_conn_for_link_id = true;
1004                                                    self.inner
1005                                                        .link_id_to_conn_id
1006                                                        .write()
1007                                                        .insert(requested_link_id.clone(), conn_id);
1008                                                    info!(
1009                                                        link_id = %requested_link_id,
1010                                                        conn_id,
1011                                                        "Connection already exists for link_id"
1012                                                    );
1013                                                }
1014                                                Ok(None) => {}
1015                                            }
1016                                        }
1017
1018                                        if connection_success && !existing_conn_for_link_id {
1019                                            match self
1020                                                .inner
1021                                                .message_processor
1022                                                .connect(client_config.clone(), None, None)
1023                                                .await
1024                                            {
1025                                                Err(e) => {
1026                                                    connection_success = false;
1027                                                    connection_error_msg =
1028                                                        format!("Connection failed: {}", e);
1029                                                }
1030                                                Ok(conn_id) => {
1031                                                    if !requested_link_id.is_empty() {
1032                                                        self.inner
1033                                                            .link_id_to_conn_id
1034                                                            .write()
1035                                                            .insert(
1036                                                                requested_link_id.clone(),
1037                                                                conn_id.1,
1038                                                            );
1039                                                    }
1040                                                    info!(
1041                                                        endpoint = %client_endpoint, "Successfully created connection",
1042                                                    );
1043                                                }
1044                                            }
1045                                        }
1046                                    }
1047                                }
1048
1049                                // Add connection status
1050                                connections_status.push(v1::ConnectionAck {
1051                                    link_id: conn.link_id.clone(),
1052                                    success: connection_success,
1053                                    error_msg: connection_error_msg,
1054                                });
1055                            }
1056
1057                            // Process routes to set
1058                            for route in &config.routes_to_set {
1059                                let mut route_success = true;
1060                                let mut route_error_msg = String::new();
1061
1062                                let conn = self.resolve_route_connection(route);
1063
1064                                if let Ok(Some(conn)) = conn {
1065                                    let name = route.name.clone().unwrap();
1066                                    let source = name.clone().with_id(NameId::NULL_COMPONENT);
1067
1068                                    let msg = DataPlaneMessage::builder()
1069                                        .source(source.clone())
1070                                        .destination(name.clone())
1071                                        .identity("")
1072                                        .flags(SlimHeaderFlags::default().with_recv_from(conn))
1073                                        .build_subscribe()
1074                                        .unwrap();
1075
1076                                    match self.send_subscribe_message_with_ack(msg).await {
1077                                        Ok(subscription_id) => {
1078                                            // Store the subscription_id for later unsubscription
1079                                            self.inner
1080                                                .route_subscription_ids
1081                                                .lock()
1082                                                .insert((name.clone(), conn), subscription_id);
1083                                            info!(?route, "Successfully created route");
1084                                        }
1085                                        Err(err) => {
1086                                            route_success = false;
1087                                            route_error_msg =
1088                                                format!("Failed to subscribe: {}", err);
1089                                        }
1090                                    }
1091                                } else {
1092                                    route_success = false;
1093                                    route_error_msg = match conn {
1094                                        Ok(None) => {
1095                                            format!(
1096                                                "Connection with link_id {} not found",
1097                                                route.link_id.as_deref().unwrap_or("<none>")
1098                                            )
1099                                        }
1100                                        Err(err) => err,
1101                                        _ => "unknown connection lookup error".to_string(),
1102                                    };
1103                                }
1104
1105                                // Add route status
1106                                routes_status.push(v1::RouteAck {
1107                                    route: Some(route.clone()),
1108                                    success: route_success,
1109                                    error_msg: route_error_msg,
1110                                });
1111                            }
1112
1113                            // Process routes to delete
1114                            for route in &config.routes_to_delete {
1115                                let mut route_success = true;
1116                                let mut route_error_msg = String::new();
1117
1118                                let conn = self.resolve_route_connection(route);
1119
1120                                if let Ok(Some(conn)) = conn {
1121                                    let name = route.name.clone().unwrap();
1122                                    let source = name.clone().with_id(NameId::NULL_COMPONENT);
1123
1124                                    let msg = DataPlaneMessage::builder()
1125                                        .source(source.clone())
1126                                        .destination(name.clone())
1127                                        .identity("")
1128                                        .flags(SlimHeaderFlags::default().with_recv_from(conn))
1129                                        .build_unsubscribe()
1130                                        .unwrap();
1131
1132                                    let sub_id = self
1133                                        .inner
1134                                        .route_subscription_ids
1135                                        .lock()
1136                                        .remove(&(name.clone(), conn));
1137                                    let unsubscribe_result = match sub_id {
1138                                        Some(subscription_id) => {
1139                                            self.send_unsubscribe_message_with_ack(
1140                                                msg,
1141                                                subscription_id,
1142                                            )
1143                                            .await
1144                                        }
1145                                        None => {
1146                                            // No tracking ID in the map (e.g. after a CP restart
1147                                            // or for orphan cleanup from the reconciler).  Generate
1148                                            // a fresh ID — the datapath locates the subscription
1149                                            // by Name+connection, not by ID; the ID is only used
1150                                            // for ack correlation.
1151                                            let (ack_id, ack_rx) =
1152                                                self.inner.subscription_manager.register_ack();
1153                                            let mut fresh_msg = msg;
1154                                            fresh_msg.set_subscription_id(ack_id);
1155                                            if let Err(e) =
1156                                                self.send_control_message(fresh_msg).await
1157                                            {
1158                                                self.inner.subscription_manager.cancel_ack(ack_id);
1159                                                Err(format!("datapath send error: {}", e.chain()))
1160                                            } else {
1161                                                match tokio::time::timeout(
1162                                                    SUBSCRIPTION_ACK_TIMEOUT,
1163                                                    ack_rx,
1164                                                )
1165                                                .await
1166                                                {
1167                                                    Ok(Ok(Ok(()))) => Ok(()),
1168                                                    Ok(Ok(Err(err))) => Err(err.to_string()),
1169                                                    Ok(Err(_)) => {
1170                                                        Err("subscription ack channel closed"
1171                                                            .to_string())
1172                                                    }
1173                                                    Err(_) => {
1174                                                        self.inner
1175                                                            .subscription_manager
1176                                                            .cancel_ack(ack_id);
1177                                                        Err("subscription ack timed out"
1178                                                            .to_string())
1179                                                    }
1180                                                }
1181                                            }
1182                                        }
1183                                    };
1184                                    if let Err(err) = unsubscribe_result {
1185                                        route_success = false;
1186                                        route_error_msg = format!("Failed to unsubscribe: {}", err);
1187                                    } else {
1188                                        info!(?route, "Successfully deleted route");
1189                                    }
1190                                } else {
1191                                    route_success = false;
1192                                    route_error_msg = match conn {
1193                                        Ok(None) => {
1194                                            format!(
1195                                                "Connection with link_id {} not found",
1196                                                route.link_id.as_deref().unwrap_or("<none>")
1197                                            )
1198                                        }
1199                                        Err(err) => err,
1200                                        _ => "unknown connection lookup error".to_string(),
1201                                    };
1202                                }
1203
1204                                // Add route status (for deletion)
1205                                routes_status.push(v1::RouteAck {
1206                                    route: Some(route.clone()),
1207                                    success: route_success,
1208                                    error_msg: route_error_msg,
1209                                });
1210                            }
1211
1212                            (connections_status, routes_status)
1213                        };
1214
1215                        let config_ack = v1::ConfigurationCommandAck {
1216                            original_message_id: msg.message_id.clone(),
1217                            connections_status,
1218                            routes_status,
1219                        };
1220
1221                        let reply = ControlMessage {
1222                            message_id: uuid::Uuid::new_v4().to_string(),
1223                            payload: Some(Payload::ConfigCommandAck(config_ack)),
1224                        };
1225
1226                        if let Err(e) = tx.send(Ok(reply)).await {
1227                            error!(error = %e.chain(), "failed to send ConfigurationCommandAck");
1228                        }
1229
1230                        info!(
1231                            connections = %config.connections_to_create.len(),
1232                            connections_to_delete = %config.connections_to_delete.len(),
1233                            routes_to_set = %config.routes_to_set.len(),
1234                            routes_to_del = %config.routes_to_delete.len(),
1235                            "Processed ConfigurationCommand"
1236                        );
1237                    }
1238                    Payload::RouteListRequest(_) => {
1239                        const CHUNK_SIZE: usize = 100;
1240
1241                        let conn_table = self.inner.message_processor.connection_table();
1242                        let mut entries = Vec::new();
1243
1244                        self.inner.message_processor.subscription_table().for_each(
1245                            |name, id, local, remote, peer, edge| {
1246                                let mut entry = RouteEntry {
1247                                    name: Some(name.clone().with_id(id)),
1248                                    ..Default::default()
1249                                };
1250
1251                                for &cid in local {
1252                                    entry.connections.push(ConnectionEntry {
1253                                        id: cid,
1254                                        connection_type: ConnectionType::Local as i32,
1255                                        config_data: "{}".to_string(),
1256                                        link_id: None,
1257                                        direction: ConnectionDirection::Outgoing as i32,
1258                                        peer_node_id: None,
1259                                    });
1260                                }
1261
1262                                let conn_slices = [
1263                                    (remote, ConnectionType::Remote),
1264                                    (peer, ConnectionType::Peer),
1265                                    (edge, ConnectionType::Edge),
1266                                ];
1267                                for (conns, ct) in conn_slices {
1268                                    for &cid in conns {
1269                                        if let Some(conn) = conn_table.get(cid) {
1270                                            entry.connections.push(ConnectionEntry {
1271                                                id: cid,
1272                                                connection_type: ct as i32,
1273                                                config_data: conn
1274                                                    .config_data()
1275                                                    .and_then(|d| serde_json::to_string(d).ok())
1276                                                    .unwrap_or_else(|| "{}".to_string()),
1277                                                link_id: conn.link_id(),
1278                                                direction: if conn.is_outgoing() {
1279                                                    ConnectionDirection::Outgoing as i32
1280                                                } else {
1281                                                    ConnectionDirection::Incoming as i32
1282                                                },
1283                                                peer_node_id: conn
1284                                                    .peer_node_id()
1285                                                    .map(str::to_string),
1286                                            });
1287                                        } else {
1288                                            error!(%cid, ?ct, "no connection entry for id");
1289                                        }
1290                                    }
1291                                }
1292                                entries.push(entry);
1293                            },
1294                        );
1295
1296                        let chunks: Vec<_> = entries.chunks(CHUNK_SIZE).collect();
1297                        if chunks.is_empty() {
1298                            let resp = ControlMessage {
1299                                message_id: uuid::Uuid::new_v4().to_string(),
1300                                payload: Some(Payload::RouteListResponse(RouteListResponse {
1301                                    original_message_id: msg.message_id.clone(),
1302                                    entries: vec![],
1303                                    done: true,
1304                                })),
1305                            };
1306                            if let Err(e) = tx.send(Ok(resp)).await {
1307                                error!(error = %e.chain(), "failed to send route list response");
1308                            }
1309                        } else {
1310                            let n = chunks.len();
1311                            for (i, chunk) in chunks.into_iter().enumerate() {
1312                                let resp = ControlMessage {
1313                                    message_id: uuid::Uuid::new_v4().to_string(),
1314                                    payload: Some(Payload::RouteListResponse(RouteListResponse {
1315                                        original_message_id: msg.message_id.clone(),
1316                                        entries: chunk.to_vec(),
1317                                        done: i + 1 == n,
1318                                    })),
1319                                };
1320                                if let Err(e) = tx.send(Ok(resp)).await {
1321                                    error!(error = %e.chain(), "failed to send route batch");
1322                                    break;
1323                                }
1324                            }
1325                        }
1326                    }
1327                    Payload::ConnectionListRequest(_) => {
1328                        let mut all_entries = Vec::new();
1329                        self.inner
1330                            .message_processor
1331                            .connection_table()
1332                            .for_each(|id, conn| {
1333                                let ct = match conn.connection_type() {
1334                                    slim_datapath::tables::ConnType::Local => ConnectionType::Local,
1335                                    slim_datapath::tables::ConnType::Remote => {
1336                                        ConnectionType::Remote
1337                                    }
1338                                    slim_datapath::tables::ConnType::Peer => ConnectionType::Peer,
1339                                    slim_datapath::tables::ConnType::Edge => ConnectionType::Edge,
1340                                };
1341                                all_entries.push(ConnectionEntry {
1342                                    id,
1343                                    connection_type: ct as i32,
1344                                    config_data: conn
1345                                        .config_data()
1346                                        .and_then(|d| serde_json::to_string(d).ok())
1347                                        .unwrap_or_else(|| "{}".to_string()),
1348                                    link_id: conn.link_id(),
1349                                    direction: if conn.is_outgoing() {
1350                                        ConnectionDirection::Outgoing as i32
1351                                    } else {
1352                                        ConnectionDirection::Incoming as i32
1353                                    },
1354                                    peer_node_id: conn.peer_node_id().map(str::to_string),
1355                                });
1356                            });
1357
1358                        const CHUNK_SIZE: usize = 100;
1359                        let chunks: Vec<_> = all_entries.chunks(CHUNK_SIZE).collect();
1360                        if chunks.is_empty() {
1361                            let resp = ControlMessage {
1362                                message_id: uuid::Uuid::new_v4().to_string(),
1363                                payload: Some(Payload::ConnectionListResponse(
1364                                    ConnectionListResponse {
1365                                        original_message_id: msg.message_id.clone(),
1366                                        entries: vec![],
1367                                        done: true,
1368                                    },
1369                                )),
1370                            };
1371                            if let Err(e) = tx.send(Ok(resp)).await {
1372                                error!(error = %e.chain(), "failed to send connection list response");
1373                            }
1374                        } else {
1375                            let n = chunks.len();
1376                            for (i, chunk) in chunks.into_iter().enumerate() {
1377                                let resp = ControlMessage {
1378                                    message_id: uuid::Uuid::new_v4().to_string(),
1379                                    payload: Some(Payload::ConnectionListResponse(
1380                                        ConnectionListResponse {
1381                                            original_message_id: msg.message_id.clone(),
1382                                            entries: chunk.to_vec(),
1383                                            done: i + 1 == n,
1384                                        },
1385                                    )),
1386                                };
1387                                if let Err(e) = tx.send(Ok(resp)).await {
1388                                    error!(error = %e.chain(), "failed to send connection list batch");
1389                                    break;
1390                                }
1391                            }
1392                        }
1393                    }
1394                    Payload::RegisterNodeRequest(_) => {
1395                        error!("received a register node request");
1396                    }
1397                    Payload::DeregisterNodeRequest(_) => {
1398                        error!("received a deregister node request");
1399                    }
1400                    _ => {
1401                        // received unsupported message type, do nothing
1402                        debug!("received unsupported message type from control - ignoring");
1403                    }
1404                }
1405            }
1406            None => {
1407                error!(
1408                    message_id = %msg.message_id,
1409                    "received control message with no payload",
1410                );
1411            }
1412        }
1413
1414        Ok(())
1415    }
1416
1417    async fn handle_subscribe_message(&self, dst: ProtoName, clients: &[ClientConfig]) {
1418        let mut sub_vec = vec![];
1419
1420        let cmd = v1::Route {
1421            name: Some(dst),
1422            link_id: None,
1423            direction: None,
1424        };
1425
1426        debug!(
1427            "handle_subscribe_message: sending route_to_set to control plane: {:?}",
1428            cmd
1429        );
1430        sub_vec.push(cmd);
1431
1432        let ctrl = ControlMessage {
1433            message_id: uuid::Uuid::new_v4().to_string(),
1434            payload: Some(Payload::ConfigCommand(v1::ConfigurationCommand {
1435                connections_to_create: vec![],
1436                connections_to_delete: vec![],
1437                routes_to_set: sub_vec,
1438                routes_to_delete: vec![],
1439                reconcile: false,
1440                connections_received: vec![],
1441            })),
1442        };
1443
1444        return self.send_or_queue_notification(ctrl, clients).await;
1445    }
1446
1447    /// Called when the data plane completes a link negotiation for a remote
1448    /// incoming connection. Notifies the control-plane so it can claim the link.
1449    async fn handle_link_received(
1450        &self,
1451        link: &slim_datapath::api::ProtoLink,
1452        clients: &[ClientConfig],
1453    ) {
1454        use slim_datapath::api::ProtoLinkType;
1455
1456        let link_id = match &link.link_type {
1457            Some(ProtoLinkType::LinkNegotiation(payload)) => &payload.link_id,
1458            _ => {
1459                debug!("handle_link_received: ignoring link message without negotiation payload");
1460                return;
1461            }
1462        };
1463
1464        if link_id.is_empty() {
1465            debug!("handle_link_received: ignoring link message with empty link_id");
1466            return;
1467        }
1468
1469        debug!(
1470            "handle_link_received: notifying control-plane of new link_id: {}",
1471            link_id
1472        );
1473
1474        let ctrl = ControlMessage {
1475            message_id: uuid::Uuid::new_v4().to_string(),
1476            payload: Some(Payload::ConfigCommand(v1::ConfigurationCommand {
1477                connections_to_create: vec![],
1478                connections_to_delete: vec![],
1479                routes_to_set: vec![],
1480                routes_to_delete: vec![],
1481                reconcile: false,
1482                connections_received: vec![v1::ConnectionEntry {
1483                    id: 0,
1484                    connection_type: ConnectionType::Remote as i32,
1485                    config_data: String::new(),
1486                    link_id: Some(link_id.clone()),
1487                    direction: ConnectionDirection::Incoming as i32,
1488                    peer_node_id: None,
1489                }],
1490            })),
1491        };
1492
1493        self.send_or_queue_notification(ctrl, clients).await;
1494    }
1495
1496    async fn handle_unsubscribe_message(&self, dst: ProtoName, clients: &[ClientConfig]) {
1497        // Remove the subscription from our tracking map so the reconciler
1498        // knows it needs to re-install it if the route is still desired.
1499        // Without this, a reused conn_id would cause create_new_subscriptions
1500        // to skip re-creation because it thinks the subscription is still active.
1501        self.inner
1502            .route_subscription_ids
1503            .lock()
1504            .retain(|(name, _), _| *name != dst);
1505
1506        let mut unsub_vec = vec![];
1507
1508        let cmd = v1::Route {
1509            name: Some(dst),
1510            link_id: None,
1511            direction: None,
1512        };
1513
1514        debug!(
1515            "handle_unsubscribe_message: sending route_to_delete to control plane: {:?}",
1516            cmd
1517        );
1518        unsub_vec.push(cmd);
1519
1520        let ctrl = ControlMessage {
1521            message_id: uuid::Uuid::new_v4().to_string(),
1522            payload: Some(Payload::ConfigCommand(v1::ConfigurationCommand {
1523                connections_to_create: vec![],
1524                connections_to_delete: vec![],
1525                routes_to_set: vec![],
1526                routes_to_delete: unsub_vec,
1527                reconcile: false,
1528                connections_received: vec![],
1529            })),
1530        };
1531
1532        return self.send_or_queue_notification(ctrl, clients).await;
1533    }
1534
1535    /// Send a subscribe message and await the ack. Returns the subscription_id.
1536    async fn send_subscribe_message_with_ack(
1537        &self,
1538        mut msg: DataPlaneMessage,
1539    ) -> Result<u64, String> {
1540        let (ack_id, ack_rx) = self.inner.subscription_manager.register_ack();
1541        msg.set_subscription_id(ack_id);
1542
1543        if let Err(e) = self.send_control_message(msg).await {
1544            self.inner.subscription_manager.cancel_ack(ack_id);
1545            return Err(format!("datapath send error: {}", e.chain()));
1546        }
1547
1548        match tokio::time::timeout(SUBSCRIPTION_ACK_TIMEOUT, ack_rx).await {
1549            Ok(Ok(Ok(()))) => Ok(ack_id),
1550            Ok(Ok(Err(err))) => Err(err.to_string()),
1551            Ok(Err(_)) => Err("subscription ack channel closed".to_string()),
1552            Err(_) => {
1553                self.inner.subscription_manager.cancel_ack(ack_id);
1554                Err("subscription ack timed out".to_string())
1555            }
1556        }
1557    }
1558
1559    /// Send an unsubscribe message with a given subscription_id and await the ack.
1560    async fn send_unsubscribe_message_with_ack(
1561        &self,
1562        mut msg: DataPlaneMessage,
1563        subscription_id: u64,
1564    ) -> Result<(), String> {
1565        let ack_rx = self
1566            .inner
1567            .subscription_manager
1568            .register_ack_with_id(subscription_id);
1569        msg.set_subscription_id(subscription_id);
1570
1571        if let Err(e) = self.send_control_message(msg).await {
1572            self.inner.subscription_manager.cancel_ack(subscription_id);
1573            return Err(format!("datapath send error: {}", e.chain()));
1574        }
1575
1576        match tokio::time::timeout(SUBSCRIPTION_ACK_TIMEOUT, ack_rx).await {
1577            Ok(Ok(Ok(()))) => Ok(()),
1578            Ok(Ok(Err(err))) => Err(err.to_string()),
1579            Ok(Err(_)) => Err("subscription ack channel closed".to_string()),
1580            Err(_) => {
1581                self.inner.subscription_manager.cancel_ack(subscription_id);
1582                Err("subscription ack timed out".to_string())
1583            }
1584        }
1585    }
1586
1587    /// Send a control message to SLIM.
1588    async fn send_control_message(&self, msg: DataPlaneMessage) -> Result<(), ControllerError> {
1589        self.inner.tx_slim.send(Ok(msg)).await.map_err(|e| {
1590            error!(error = %e.chain(), "error sending message into datapath");
1591            ControllerError::Datapath(slim_datapath::errors::DataPathError::ConnectionError)
1592        })
1593    }
1594
1595    /// Send notification to control plane or queue it if no connection is available.
1596    ///
1597    /// Uses `try_send` to avoid blocking the caller when the bounded channel is
1598    /// full (e.g. due to HTTP/2 back-pressure).  Blocking here would stall the
1599    /// data-plane listener task, preventing it from resolving subscription acks
1600    /// that the control-message handler is waiting on — a deadlock.
1601    async fn send_or_queue_notification(&self, ctrl_msg: ControlMessage, clients: &[ClientConfig]) {
1602        let mut sent = false;
1603
1604        for c in clients {
1605            let tx = match self.inner.tx_channels.read().get(&c.endpoint) {
1606                Some(tx) => tx.clone(),
1607                None => continue,
1608            };
1609
1610            match tx.try_send(Ok(ctrl_msg.clone())) {
1611                Ok(()) => {
1612                    sent = true;
1613                }
1614                Err(mpsc::error::TrySendError::Full(_)) => {
1615                    debug!(
1616                        endpoint = %c.endpoint,
1617                        "channel full, queuing notification instead of blocking"
1618                    );
1619                }
1620                Err(mpsc::error::TrySendError::Closed(_)) => {
1621                    debug!(
1622                        endpoint = %c.endpoint,
1623                        "channel closed, queuing notification"
1624                    );
1625                }
1626            }
1627        }
1628
1629        if !sent {
1630            let mut queue = self.inner.pending_notifications.lock();
1631            if queue.len() >= MAX_QUEUED_NOTIFICATIONS {
1632                queue.pop_front();
1633                debug!("queue full, removed oldest notification");
1634            }
1635            queue.push_back(ctrl_msg);
1636        }
1637    }
1638
1639    /// Get a drain watch clone to pass to a task
1640    fn drain_watch(&self) -> Result<drain::Watch, ControllerError> {
1641        self.inner
1642            .drain_watch
1643            .read()
1644            .clone()
1645            .ok_or(ControllerError::AlreadyStopped)
1646    }
1647
1648    /// Send all queued subscription notifications when connection is restored.
1649    async fn send_queued_notifications(
1650        &self,
1651        tx: &mpsc::Sender<Result<ControlMessage, Status>>,
1652        endpoint: &str,
1653    ) {
1654        let notifications = {
1655            let mut queue = self.inner.pending_notifications.lock();
1656            if queue.is_empty() {
1657                return;
1658            }
1659            queue.drain(..).collect::<Vec<_>>()
1660        };
1661
1662        if notifications.is_empty() {
1663            return;
1664        }
1665
1666        debug!(
1667            "sending {} queued subscription notifications to {}",
1668            notifications.len(),
1669            endpoint
1670        );
1671
1672        let mut failed_notifications = Vec::new();
1673        for notification in notifications {
1674            if let Err(e) = tx.send(Ok(notification)).await {
1675                error!(
1676                    error = %e.chain(),
1677                    %endpoint,
1678                    "failed to send queued notification to control plane",
1679                );
1680
1681                // we can unwrap here because we know we sent a Ok(ControlMessage)
1682                failed_notifications.push(e.0.unwrap());
1683            }
1684        }
1685
1686        // Re-queue any failed notifications
1687        if !failed_notifications.is_empty() {
1688            self.inner
1689                .pending_notifications
1690                .lock()
1691                .extend(failed_notifications);
1692        }
1693    }
1694
1695    /// Replay all locally-registered agent subscriptions to the control plane.
1696    /// Called after (re)connecting so the CP can recreate wildcard route templates.
1697    async fn replay_local_subscriptions(&self, clients: &[ClientConfig]) {
1698        let mut routes: Vec<v1::Route> = Vec::new();
1699        self.inner.message_processor.subscription_table().for_each(
1700            |name, id, local, _remote, _peer, edge| {
1701                if local.is_empty() && edge.is_empty() {
1702                    return;
1703                }
1704                routes.push(v1::Route {
1705                    name: Some(name.clone().with_id(id)),
1706                    link_id: None,
1707                    direction: None,
1708                });
1709            },
1710        );
1711
1712        if routes.is_empty() {
1713            return;
1714        }
1715
1716        info!(
1717            count = routes.len(),
1718            "replaying local subscriptions to control plane"
1719        );
1720
1721        let ctrl = ControlMessage {
1722            message_id: uuid::Uuid::new_v4().to_string(),
1723            payload: Some(Payload::ConfigCommand(v1::ConfigurationCommand {
1724                connections_to_create: vec![],
1725                connections_to_delete: vec![],
1726                routes_to_set: routes,
1727                routes_to_delete: vec![],
1728                reconcile: false,
1729                connections_received: vec![],
1730            })),
1731        };
1732
1733        self.send_or_queue_notification(ctrl, clients).await;
1734    }
1735
1736    /// Process the control message stream.
1737    fn process_control_message_stream(
1738        &self,
1739        config: Option<ClientConfig>,
1740        mut stream: impl Stream<Item = Result<ControlMessage, Status>> + Unpin + Send + 'static,
1741        tx: mpsc::Sender<Result<ControlMessage, Status>>,
1742        cancellation_token: CancellationToken,
1743    ) -> Result<JoinHandle<()>, ControllerError> {
1744        let this = self.clone();
1745        let watch = self.drain_watch()?;
1746
1747        let handle = tokio::spawn(async move {
1748            // Send a register message to the control plane
1749            let endpoint = config
1750                .as_ref()
1751                .map(|c| c.endpoint.clone())
1752                .unwrap_or_else(|| "unknown".to_string());
1753            info!(%endpoint, "connected to control plane");
1754
1755            let mut retry_connect = false;
1756
1757            let mut active_connections = Vec::new();
1758            this.inner
1759                .message_processor
1760                .connection_table()
1761                .for_each(|id, conn| {
1762                    active_connections.push(v1::ConnectionEntry {
1763                        id,
1764                        connection_type: v1::ConnectionType::Remote as i32,
1765                        config_data: match conn.config_data() {
1766                            Some(data) => {
1767                                serde_json::to_string(data).unwrap_or_else(|_| "{}".to_string())
1768                            }
1769                            None => "{}".to_string(),
1770                        },
1771                        link_id: conn.link_id(),
1772                        direction: if conn.is_outgoing() {
1773                            v1::ConnectionDirection::Outgoing as i32
1774                        } else {
1775                            v1::ConnectionDirection::Incoming as i32
1776                        },
1777                        peer_node_id: conn.peer_node_id().map(str::to_string),
1778                    });
1779                });
1780
1781            let active_routes = {
1782                let conn_id_to_link_id: HashMap<u64, String> = this
1783                    .inner
1784                    .link_id_to_conn_id
1785                    .read()
1786                    .iter()
1787                    .map(|(lid, cid)| (*cid, lid.clone()))
1788                    .collect();
1789                this.inner
1790                    .route_subscription_ids
1791                    .lock()
1792                    .iter()
1793                    .map(|((name, conn_id), _sub_id)| v1::Route {
1794                        name: Some(name.clone()),
1795                        link_id: conn_id_to_link_id.get(conn_id).cloned(),
1796                        direction: None,
1797                    })
1798                    .collect::<Vec<_>>()
1799            };
1800
1801            let max_attempts = 10;
1802            let mut credentials = None;
1803            let mut i = 0;
1804            while i < max_attempts && credentials.is_none() {
1805                credentials = match &this.inner.auth_provider {
1806                    Some(provider) => match provider.get_token() {
1807                        Ok(token) => Some(token),
1808                        Err(e) => {
1809                            info!(error = %e, attempt = i + 1, max_attempts, "failed to get auth credentials, will retry");
1810                            tokio::time::sleep(Duration::from_secs(2)).await;
1811                            None
1812                        }
1813                    },
1814                    None => Some(String::new()),
1815                };
1816                i += 1;
1817            }
1818
1819            if credentials.is_none() {
1820                error!(
1821                    attempts = max_attempts,
1822                    "failed to obtain auth credentials, aborting registration"
1823                );
1824                return;
1825            }
1826
1827            let register_request = ControlMessage {
1828                message_id: uuid::Uuid::new_v4().to_string(),
1829                payload: Some(Payload::RegisterNodeRequest(v1::RegisterNodeRequest {
1830                    node_id: this.inner.id.clone(),
1831                    group_name: this.inner.group_name.clone(),
1832                    connection_details: this.inner.connection_details.clone(),
1833                    connections: active_connections,
1834                    routes: active_routes,
1835                    credentials: credentials.unwrap(),
1836                })),
1837            };
1838
1839            // send register request if client
1840            if config.is_some()
1841                && let Err(e) = tx.send(Ok(register_request)).await
1842            {
1843                error!(error = %e.chain(), "failed to send register request");
1844                return;
1845            }
1846
1847            // Send queued notifications after the register request so that
1848            // RegisterNodeRequest is always the first message on the stream.
1849            this.send_queued_notifications(&tx, &endpoint).await;
1850
1851            // Wait for RegisterNodeResponse from control plane before processing commands.
1852            if config.is_some() {
1853                let registration_result = tokio::time::timeout(
1854                    Duration::from_secs(10),
1855                    async {
1856                        loop {
1857                            tokio::select! {
1858                                next = stream.next() => {
1859                                    match next {
1860                                        Some(Ok(msg)) => {
1861                                            if let Some(Payload::RegisterNodeResponse(resp)) = msg.payload {
1862                                                if resp.success {
1863                                                    info!(
1864                                                        connections = resp.connections.len(),
1865                                                        routes = resp.routes.len(),
1866                                                        "registration acknowledged by control plane"
1867                                                    );
1868                                                    return Ok(resp);
1869                                                } else {
1870                                                    return Err("registration rejected by control plane".to_string());
1871                                                }
1872                                            }
1873                                        }
1874                                        Some(Err(_)) => return Err("stream error waiting for registration response".to_string()),
1875                                        None => return Err("stream closed before registration response".to_string()),
1876                                    }
1877                                }
1878                                _ = cancellation_token.cancelled() => {
1879                                    return Err("cancelled while waiting for registration response".to_string());
1880                                }
1881                            }
1882                        }
1883                    }
1884                ).await;
1885
1886                match registration_result {
1887                    Ok(Ok(resp)) => {
1888                        // Apply initial desired state from the control plane.
1889                        if !resp.connections.is_empty() || !resp.routes.is_empty() {
1890                            let init_cmd = ControlMessage {
1891                                message_id: uuid::Uuid::new_v4().to_string(),
1892                                payload: Some(Payload::ConfigCommand(v1::ConfigurationCommand {
1893                                    connections_to_create: resp.connections,
1894                                    routes_to_set: resp.routes,
1895                                    routes_to_delete: vec![],
1896                                    connections_to_delete: vec![],
1897                                    reconcile: true,
1898                                    connections_received: vec![],
1899                                })),
1900                            };
1901                            if let Err(e) = this.handle_new_control_message(init_cmd, &tx).await {
1902                                error!(error = %e.chain(), "failed to apply initial state from registration");
1903                            }
1904                        }
1905                    }
1906                    Ok(Err(e)) => {
1907                        error!(%e, "registration handshake failed");
1908                        return;
1909                    }
1910                    Err(_) => {
1911                        error!("registration handshake timed out");
1912                        return;
1913                    }
1914                }
1915            }
1916
1917            // Replay local agent subscriptions so the CP recreates route templates.
1918            if let Some(ref cfg) = config {
1919                this.replay_local_subscriptions(std::slice::from_ref(cfg))
1920                    .await;
1921            }
1922
1923            let mut drain_fut = std::pin::pin!(watch.clone().signaled());
1924
1925            loop {
1926                tokio::select! {
1927                    next = stream.next() => {
1928                        match next {
1929                            Some(Ok(msg)) => {
1930                                if let Err(e) = this.handle_new_control_message(msg, &tx).await {
1931                                    error!(error = %e.chain(), "error processing incoming control message");
1932                                }
1933                            }
1934                            Some(Err(e)) => {
1935                                if let Some(io_err) = Self::match_for_io_error(&e) {
1936                                    if io_err.kind() == std::io::ErrorKind::BrokenPipe {
1937                                        info!("connection closed by peer");
1938                                    } else {
1939                                        // Handle other IO errors (ConnectionAborted, etc.)
1940                                        error!(
1941                                            error = %e.chain(),
1942                                            io_error_kind = ?io_err.kind(),
1943                                            "IO error receiving control messages"
1944                                        );
1945                                    }
1946                                } else {
1947                                    // Handle non-IO errors (e.g., gRPC Canceled, Unavailable, etc.)
1948                                    error!(error = %e.chain(), "error receiving control messages");
1949                                }
1950
1951                                retry_connect = true;
1952                                break;
1953                            }
1954                            None => {
1955                                debug!("end of stream");
1956                                retry_connect = true;
1957                                break;
1958                            }
1959                        }
1960                    }
1961                    _ = cancellation_token.cancelled() => {
1962                        debug!("shutting down stream on cancellation token");
1963                        break;
1964                    }
1965                    _ = &mut drain_fut => {
1966                        debug!("shutting down stream on drain");
1967                        break;
1968                    }
1969                }
1970            }
1971
1972            info!(%endpoint, "control plane stream closed");
1973
1974            if retry_connect && let Some(config) = config {
1975                info!(%config.endpoint, "retrying connection to control plane");
1976                this.connect(config.clone(), cancellation_token)
1977                    .await
1978                    .map_or_else(
1979                        |e| {
1980                            error!(error = %e.chain(), "failed to reconnect to control plane");
1981                        },
1982                        |tx| {
1983                            info!(%config.endpoint, "reconnected to control plane");
1984
1985                            this.inner
1986                                .tx_channels
1987                                .write()
1988                                .insert(config.endpoint.clone(), tx);
1989                        },
1990                    )
1991            }
1992        });
1993
1994        Ok(handle)
1995    }
1996
1997    /// Connect to the control plane using the provided client configuration.
1998    /// This function attempts to establish a connection to the control plane and returns a sender for control messages.
1999    /// It retries the connection a specified number of times if it fails.
2000    async fn connect(
2001        &self,
2002        config: ClientConfig,
2003        cancellation_token: CancellationToken,
2004    ) -> Result<mpsc::Sender<Result<ControlMessage, Status>>, ControllerError> {
2005        info!(%config.endpoint, "connecting to control plane");
2006
2007        // Disconnect all outgoing remote connections — the CP will create
2008        // fresh links after re-registration and nodes will reconnect.
2009        // This prevents stale connections from causing duplicate links.
2010        let mut remote_conn_ids: Vec<u64> = Vec::new();
2011        self.inner
2012            .message_processor
2013            .connection_table()
2014            .for_each(|id, conn| {
2015                if conn.is_outgoing() && matches!(conn.connection_type(), ConnType::Remote) {
2016                    remote_conn_ids.push(id);
2017                }
2018            });
2019        for conn_id in &remote_conn_ids {
2020            if let Err(e) = self.inner.message_processor.disconnect(*conn_id) {
2021                debug!(conn_id, error = %e.chain(), "failed to disconnect remote connection");
2022            }
2023        }
2024        if !remote_conn_ids.is_empty() {
2025            info!(
2026                count = remote_conn_ids.len(),
2027                "disconnected remote connections for clean restart"
2028            );
2029        }
2030
2031        // Reset connection state before establishing a new session. The CP
2032        // will send fresh ConfigCommands after re-registration to rebuild
2033        // these maps.
2034        self.inner.route_subscription_ids.lock().clear();
2035        self.inner.link_id_to_conn_id.write().clear();
2036
2037        // Obtain drain watch to handle graceful shutdown during connection
2038        let watch = self.drain_watch()?;
2039
2040        let connect_fut = async {
2041            let channel = match config.to_channel().await? {
2042                TransportChannel::Grpc(c) => c,
2043                TransportChannel::Websocket(_) => {
2044                    return Err(ControllerError::ConfigError(
2045                        slim_config::errors::ConfigError::GrpcChannelUnsupportedTransport,
2046                    ));
2047                }
2048            };
2049
2050            let mut client = ControllerServiceClient::new(channel.clone());
2051            let (tx, rx) = mpsc::channel::<Result<ControlMessage, Status>>(128);
2052            let out_stream = ReceiverStream::new(rx).filter_map(|res| match res {
2053                Ok(msg) => Some(msg),
2054                Err(e) => {
2055                    error!(error = %e, "dropping outbound control message due to error");
2056                    None
2057                }
2058            });
2059            let stream = client
2060                .open_control_channel(Request::new(out_stream))
2061                .await?;
2062            Ok((tx, stream))
2063        };
2064
2065        let (tx, stream) = tokio::select! {
2066            result = connect_fut => { result? }
2067            _ = cancellation_token.cancelled() => {
2068                debug!("connection cancelled during setup");
2069                return Err(ControllerError::Canceled);
2070            }
2071            _ = watch.signaled() => {
2072                debug!("drain signal received during connection setup");
2073                return Err(ControllerError::Canceled);
2074            }
2075        };
2076
2077        // start processing the incoming stream
2078        let endpoint_key = config.endpoint.clone();
2079        let handle = self.process_control_message_stream(
2080            Some(config),
2081            stream.into_inner(),
2082            tx.clone(),
2083            cancellation_token.clone(),
2084        )?;
2085        self.inner
2086            .stream_handles
2087            .lock()
2088            .insert(endpoint_key, handle);
2089
2090        // return the sender
2091        Ok(tx)
2092    }
2093
2094    fn match_for_io_error(err_status: &Status) -> Option<&std::io::Error> {
2095        let mut err: &(dyn std::error::Error + 'static) = err_status;
2096
2097        loop {
2098            if let Some(io_err) = err.downcast_ref::<std::io::Error>() {
2099                return Some(io_err);
2100            }
2101
2102            // h2::Error do not expose std::io::Error with `source()`
2103            // https://github.com/hyperium/h2/pull/462
2104            if let Some(h2_err) = err.downcast_ref::<h2::Error>()
2105                && let Some(io_err) = h2_err.get_io()
2106            {
2107                return Some(io_err);
2108            }
2109
2110            err = err.source()?;
2111        }
2112    }
2113}
2114
2115#[tonic::async_trait]
2116impl GrpcControllerService for ControllerService {
2117    type OpenControlChannelStream =
2118        Pin<Box<dyn Stream<Item = Result<ControlMessage, Status>> + Send + 'static>>;
2119
2120    async fn open_control_channel(
2121        &self,
2122        request: Request<tonic::Streaming<ControlMessage>>,
2123    ) -> Result<Response<Self::OpenControlChannelStream>, Status> {
2124        // Get the remote endpoint from the request metadata
2125        let remote_endpoint = request
2126            .remote_addr()
2127            .map(|addr| addr.to_string())
2128            .unwrap_or_else(|| "unknown".to_string());
2129
2130        let stream = request.into_inner();
2131        let (tx, rx) = mpsc::channel::<Result<ControlMessage, Status>>(128);
2132
2133        let cancellation_token = CancellationToken::new();
2134
2135        // Server-side connections don't initiate operations requiring acks, so no timer channel needed
2136        let handle = self
2137            .process_control_message_stream(None, stream, tx.clone(), cancellation_token.clone())
2138            .map_err(|e| {
2139                error!(error = %e.chain(), "error processing control message stream");
2140                Status::unavailable("failed to process control message stream")
2141            })?;
2142        self.inner
2143            .stream_handles
2144            .lock()
2145            .insert(remote_endpoint.clone(), handle);
2146
2147        self.inner
2148            .tx_channels
2149            .write()
2150            .insert(remote_endpoint.clone(), tx);
2151
2152        if let Some(old_token) = self
2153            .inner
2154            .cancellation_tokens
2155            .write()
2156            .insert(remote_endpoint.clone(), cancellation_token)
2157        {
2158            old_token.cancel();
2159        }
2160
2161        let out_stream = ReceiverStream::new(rx);
2162        Ok(Response::new(
2163            Box::pin(out_stream) as Self::OpenControlChannelStream
2164        ))
2165    }
2166}
2167
2168#[cfg(test)]
2169mod tests {
2170    use super::*;
2171    use tracing_test::traced_test;
2172
2173    async fn setup_control_planes(
2174        server_endpoint: &str,
2175        server_name: &str,
2176        client_name: &str,
2177    ) -> (ControlPlane, ControlPlane, ClientConfig) {
2178        let server_config = ServerConfig::with_endpoint(server_endpoint)
2179            .with_tls_settings(slim_config::tls::server::TlsServerConfig::insecure());
2180        let client_config = ClientConfig::with_endpoint(&format!("http://{}", server_endpoint))
2181            .with_tls_setting(slim_config::tls::client::TlsClientConfig::insecure());
2182
2183        let message_processor_server = MessageProcessor::new();
2184        let message_processor_client = MessageProcessor::new();
2185
2186        let control_plane_server = ControlPlane::new(ControlPlaneSettings {
2187            id: server_name.to_string(),
2188            group_name: None,
2189            servers: vec![server_config.clone()],
2190            clients: vec![],
2191            message_processor: message_processor_server,
2192            connection_details: vec![from_server_config(&server_config)],
2193            auth_provider: None,
2194        });
2195
2196        let control_plane_client = ControlPlane::new(ControlPlaneSettings {
2197            id: client_name.to_string(),
2198            group_name: None,
2199            servers: vec![],
2200            clients: vec![client_config.clone()],
2201            message_processor: message_processor_client,
2202            connection_details: vec![],
2203            auth_provider: None,
2204        });
2205
2206        (control_plane_server, control_plane_client, client_config)
2207    }
2208
2209    #[tokio::test]
2210    #[traced_test]
2211    async fn test_end_to_end() {
2212        let (mut control_plane_server, mut control_plane_client, _client_cfg) =
2213            setup_control_planes(
2214                "127.0.0.1:50051",
2215                "test-server-instance",
2216                "test-client-instance",
2217            )
2218            .await;
2219
2220        control_plane_server.run().await.unwrap();
2221        tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
2222        control_plane_client.run().await.unwrap();
2223        tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
2224
2225        assert!(logs_contain("received a register node request"));
2226    }
2227
2228    #[tokio::test]
2229    #[traced_test]
2230    async fn test_subscription_notification_queue_drain() {
2231        // Reuse common setup with a different port to avoid clash with other tests.
2232        let (mut control_plane_server, mut control_plane_client, client_config) =
2233            setup_control_planes(
2234                "127.0.0.1:50061",
2235                "queue-drain-server",
2236                "queue-drain-client",
2237            )
2238            .await;
2239
2240        let controller = control_plane_client.controller.clone();
2241        assert_eq!(controller.inner.pending_notifications.lock().len(), 0);
2242
2243        const N: usize = 5;
2244        for i in 0..N {
2245            let ctrl_msg = ControlMessage {
2246                message_id: uuid::Uuid::new_v4().to_string(),
2247                payload: Some(Payload::ConfigCommand(v1::ConfigurationCommand {
2248                    connections_to_create: vec![],
2249                    connections_to_delete: vec![],
2250                    routes_to_set: vec![v1::Route {
2251                        name: Some(
2252                            ProtoName::from_strings(["queued", "sub", &format!("name-{i}")])
2253                                .with_id(i as u128),
2254                        ),
2255                        link_id: None,
2256                        direction: None,
2257                    }],
2258                    routes_to_delete: vec![],
2259                    reconcile: false,
2260                    connections_received: vec![],
2261                })),
2262            };
2263            controller
2264                .send_or_queue_notification(ctrl_msg, std::slice::from_ref(&client_config))
2265                .await;
2266        }
2267        assert_eq!(controller.inner.pending_notifications.lock().len(), N);
2268
2269        control_plane_server.run().await.expect("server run failed");
2270        tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
2271        control_plane_client.run().await.expect("client run failed");
2272        tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
2273
2274        assert_eq!(controller.inner.pending_notifications.lock().len(), 0);
2275        assert!(
2276            logs_contain(&format!("sending {} queued subscription notifications", N)),
2277            "Expected log about sending queued subscription notifications"
2278        );
2279
2280        drop(controller);
2281        drop(control_plane_server);
2282        drop(control_plane_client);
2283    }
2284
2285    #[tokio::test]
2286    #[traced_test]
2287    async fn test_delete_connection_by_link_id_success_ack() {
2288        let (mut control_plane_server, mut control_plane_client, _client_cfg) =
2289            setup_control_planes(
2290                "127.0.0.1:50081",
2291                "delete-linkid-server",
2292                "delete-linkid-client",
2293            )
2294            .await;
2295
2296        control_plane_server.run().await.unwrap();
2297        tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
2298        control_plane_client.run().await.unwrap();
2299        tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
2300
2301        let controller = control_plane_client.controller.clone();
2302        let link_id = "test-delete-link-id".to_string();
2303
2304        // Insert a pre-negotiated remote connection with a known link_id for testing.
2305        let (tx, _rx) = tokio::sync::mpsc::channel(16);
2306        let conn = slim_datapath::connection::Connection::new(
2307            slim_datapath::tables::ConnType::Remote,
2308            slim_datapath::connection::Channel::Server(tx),
2309        )
2310        .with_negotiation(&link_id, "1.0.0");
2311        controller
2312            .inner
2313            .message_processor
2314            .forwarder()
2315            .on_connection_established(conn, None)
2316            .unwrap();
2317
2318        let ctrl_msg = ControlMessage {
2319            message_id: uuid::Uuid::new_v4().to_string(),
2320            payload: Some(Payload::ConfigCommand(v1::ConfigurationCommand {
2321                connections_to_create: vec![],
2322                connections_to_delete: vec![link_id.clone()],
2323                routes_to_set: vec![],
2324                routes_to_delete: vec![],
2325                reconcile: false,
2326                connections_received: vec![],
2327            })),
2328        };
2329        let (tx, mut rx) = mpsc::channel(1);
2330        controller
2331            .handle_new_control_message(ctrl_msg, &tx)
2332            .await
2333            .expect("config command must be handled");
2334
2335        let ack_msg = rx
2336            .recv()
2337            .await
2338            .expect("expected ack message")
2339            .expect("ack should be ok");
2340        let ack = match ack_msg.payload {
2341            Some(Payload::ConfigCommandAck(ack)) => ack,
2342            _ => panic!("expected ConfigCommandAck payload"),
2343        };
2344        assert_eq!(ack.connections_status.len(), 1);
2345        assert_eq!(ack.connections_status[0].link_id, link_id);
2346        assert!(ack.connections_status[0].success);
2347    }
2348
2349    #[tokio::test]
2350    #[traced_test]
2351    async fn test_delete_connection_by_link_id_unknown_fails_ack() {
2352        let (control_plane_server, control_plane_client, _client_cfg) = setup_control_planes(
2353            "127.0.0.1:50082",
2354            "delete-linkid-server-unknown",
2355            "delete-linkid-client-unknown",
2356        )
2357        .await;
2358
2359        let controller = control_plane_client.controller.clone();
2360        let ctrl_msg = ControlMessage {
2361            message_id: uuid::Uuid::new_v4().to_string(),
2362            payload: Some(Payload::ConfigCommand(v1::ConfigurationCommand {
2363                connections_to_create: vec![],
2364                connections_to_delete: vec!["unknown-link-id".to_string()],
2365                routes_to_set: vec![],
2366                routes_to_delete: vec![],
2367                reconcile: false,
2368                connections_received: vec![],
2369            })),
2370        };
2371        let (tx, mut rx) = mpsc::channel(1);
2372        controller
2373            .handle_new_control_message(ctrl_msg, &tx)
2374            .await
2375            .expect("config command must be handled");
2376
2377        let ack_msg = rx
2378            .recv()
2379            .await
2380            .expect("expected ack message")
2381            .expect("ack should be ok");
2382        let ack = match ack_msg.payload {
2383            Some(Payload::ConfigCommandAck(ack)) => ack,
2384            _ => panic!("expected ConfigCommandAck payload"),
2385        };
2386        assert_eq!(ack.connections_status.len(), 1);
2387        assert_eq!(ack.connections_status[0].link_id, "unknown-link-id");
2388        assert!(!ack.connections_status[0].success);
2389        assert!(ack.connections_status[0].error_msg.contains("not found"));
2390
2391        drop(control_plane_server);
2392    }
2393
2394    #[tokio::test]
2395    #[traced_test]
2396    async fn test_create_connection_with_existing_link_id_reuses_connection_ack() {
2397        let (mut control_plane_server, mut control_plane_client, _client_cfg) =
2398            setup_control_planes(
2399                "127.0.0.1:50083",
2400                "create-linkid-server",
2401                "create-linkid-client",
2402            )
2403            .await;
2404
2405        control_plane_server.run().await.unwrap();
2406        tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
2407        control_plane_client.run().await.unwrap();
2408        tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
2409
2410        let controller = control_plane_client.controller.clone();
2411        let link_id = "test-create-link-id".to_string();
2412
2413        // Insert a pre-negotiated remote connection with a known link_id for testing.
2414        let (tx, _rx) = tokio::sync::mpsc::channel(16);
2415        let conn = slim_datapath::connection::Connection::new(
2416            slim_datapath::tables::ConnType::Remote,
2417            slim_datapath::connection::Channel::Server(tx),
2418        )
2419        .with_negotiation(&link_id, "1.0.0");
2420        controller
2421            .inner
2422            .message_processor
2423            .forwarder()
2424            .on_connection_established(conn, None)
2425            .unwrap();
2426
2427        let endpoint = "http://127.0.0.1:59999";
2428        let connection_config = serde_json::json!({
2429            "endpoint": endpoint,
2430            "link_id": link_id
2431        })
2432        .to_string();
2433
2434        let ctrl_msg = ControlMessage {
2435            message_id: uuid::Uuid::new_v4().to_string(),
2436            payload: Some(Payload::ConfigCommand(v1::ConfigurationCommand {
2437                connections_to_create: vec![v1::Connection {
2438                    link_id: "reuse-existing-link".to_string(),
2439                    config_data: connection_config,
2440                }],
2441                connections_to_delete: vec![],
2442                routes_to_set: vec![],
2443                routes_to_delete: vec![],
2444                reconcile: false,
2445                connections_received: vec![],
2446            })),
2447        };
2448
2449        let (tx, mut rx) = mpsc::channel(1);
2450        controller
2451            .handle_new_control_message(ctrl_msg, &tx)
2452            .await
2453            .expect("config command must be handled");
2454
2455        let ack_msg = rx
2456            .recv()
2457            .await
2458            .expect("expected ack message")
2459            .expect("ack should be ok");
2460        let ack = match ack_msg.payload {
2461            Some(Payload::ConfigCommandAck(ack)) => ack,
2462            _ => panic!("expected ConfigCommandAck payload"),
2463        };
2464        assert_eq!(ack.connections_status.len(), 1);
2465        assert_eq!(ack.connections_status[0].link_id, "reuse-existing-link");
2466        assert!(ack.connections_status[0].success);
2467
2468        assert!(
2469            controller
2470                .inner
2471                .link_id_to_conn_id
2472                .read()
2473                .contains_key(&link_id),
2474            "expected link_id to be mapped to reused connection id"
2475        );
2476    }
2477
2478    #[tokio::test]
2479    #[traced_test]
2480    async fn test_subscription_set_unknown_link_id_fails_ack() {
2481        let (control_plane_server, control_plane_client, _client_cfg) = setup_control_planes(
2482            "127.0.0.1:50084",
2483            "sub-linkid-server-unknown",
2484            "sub-linkid-client-unknown",
2485        )
2486        .await;
2487
2488        let controller = control_plane_client.controller.clone();
2489        let ctrl_msg = ControlMessage {
2490            message_id: uuid::Uuid::new_v4().to_string(),
2491            payload: Some(Payload::ConfigCommand(v1::ConfigurationCommand {
2492                connections_to_create: vec![],
2493                connections_to_delete: vec![],
2494                routes_to_set: vec![v1::Route {
2495                    name: Some(ProtoName::from_strings(["org", "ns", "agent"]).with_id(1u128)),
2496                    link_id: Some("missing-link-id".to_string()),
2497                    direction: None,
2498                }],
2499                routes_to_delete: vec![],
2500                reconcile: false,
2501                connections_received: vec![],
2502            })),
2503        };
2504        let (tx, mut rx) = mpsc::channel(1);
2505        controller
2506            .handle_new_control_message(ctrl_msg, &tx)
2507            .await
2508            .expect("config command must be handled");
2509
2510        let ack_msg = rx
2511            .recv()
2512            .await
2513            .expect("expected ack message")
2514            .expect("ack should be ok");
2515        let ack = match ack_msg.payload {
2516            Some(Payload::ConfigCommandAck(ack)) => ack,
2517            _ => panic!("expected ConfigCommandAck payload"),
2518        };
2519
2520        assert_eq!(ack.routes_status.len(), 1);
2521        assert!(!ack.routes_status[0].success);
2522        assert!(
2523            ack.routes_status[0]
2524                .error_msg
2525                .contains("Connection with link_id missing-link-id not found")
2526        );
2527
2528        drop(control_plane_server);
2529    }
2530
2531    #[tokio::test]
2532    #[traced_test]
2533    async fn test_create_connection_invalid_config_fails_ack() {
2534        let (_control_plane_server, control_plane_client, _client_cfg) = setup_control_planes(
2535            "127.0.0.1:50085",
2536            "create-invalid-config-server",
2537            "create-invalid-config-client",
2538        )
2539        .await;
2540
2541        let controller = control_plane_client.controller.clone();
2542        let ctrl_msg = ControlMessage {
2543            message_id: uuid::Uuid::new_v4().to_string(),
2544            payload: Some(Payload::ConfigCommand(v1::ConfigurationCommand {
2545                connections_to_create: vec![v1::Connection {
2546                    link_id: "invalid-config-conn".to_string(),
2547                    config_data: "{invalid-json".to_string(),
2548                }],
2549                connections_to_delete: vec![],
2550                routes_to_set: vec![],
2551                routes_to_delete: vec![],
2552                reconcile: false,
2553                connections_received: vec![],
2554            })),
2555        };
2556        let (tx, mut rx) = mpsc::channel(1);
2557        controller
2558            .handle_new_control_message(ctrl_msg, &tx)
2559            .await
2560            .expect("config command must be handled");
2561
2562        let ack_msg = rx
2563            .recv()
2564            .await
2565            .expect("expected ack message")
2566            .expect("ack should be ok");
2567        let ack = match ack_msg.payload {
2568            Some(Payload::ConfigCommandAck(ack)) => ack,
2569            _ => panic!("expected ConfigCommandAck payload"),
2570        };
2571        assert_eq!(ack.connections_status.len(), 1);
2572        assert!(!ack.connections_status[0].success);
2573        assert!(
2574            ack.connections_status[0]
2575                .error_msg
2576                .contains("Failed to parse config")
2577        );
2578    }
2579
2580    #[tokio::test]
2581    #[traced_test]
2582    async fn test_subscription_delete_unknown_link_id_fails_ack() {
2583        let (control_plane_server, control_plane_client, _client_cfg) = setup_control_planes(
2584            "127.0.0.1:50086",
2585            "sub-del-linkid-server-unknown",
2586            "sub-del-linkid-client-unknown",
2587        )
2588        .await;
2589
2590        let controller = control_plane_client.controller.clone();
2591        let ctrl_msg = ControlMessage {
2592            message_id: uuid::Uuid::new_v4().to_string(),
2593            payload: Some(Payload::ConfigCommand(v1::ConfigurationCommand {
2594                connections_to_create: vec![],
2595                connections_to_delete: vec![],
2596                routes_to_set: vec![],
2597                routes_to_delete: vec![v1::Route {
2598                    name: Some(ProtoName::from_strings(["org", "ns", "agent"]).with_id(1u128)),
2599                    link_id: Some("missing-link-id-delete".to_string()),
2600                    direction: None,
2601                }],
2602                reconcile: false,
2603                connections_received: vec![],
2604            })),
2605        };
2606        let (tx, mut rx) = mpsc::channel(1);
2607        controller
2608            .handle_new_control_message(ctrl_msg, &tx)
2609            .await
2610            .expect("config command must be handled");
2611
2612        let ack_msg = rx
2613            .recv()
2614            .await
2615            .expect("expected ack message")
2616            .expect("ack should be ok");
2617        let ack = match ack_msg.payload {
2618            Some(Payload::ConfigCommandAck(ack)) => ack,
2619            _ => panic!("expected ConfigCommandAck payload"),
2620        };
2621
2622        assert_eq!(ack.routes_status.len(), 1);
2623        assert!(!ack.routes_status[0].success);
2624        assert!(
2625            ack.routes_status[0]
2626                .error_msg
2627                .contains("Connection with link_id missing-link-id-delete not found")
2628        );
2629
2630        drop(control_plane_server);
2631    }
2632
2633    #[tokio::test]
2634    #[traced_test]
2635    async fn test_shutdown_drains_resources() {
2636        // Use a unique port to avoid conflicts with other tests.
2637        let (mut control_plane_server, mut control_plane_client, _client_cfg) =
2638            setup_control_planes(
2639                "127.0.0.1:50071",
2640                "shutdown-server-instance",
2641                "shutdown-client-instance",
2642            )
2643            .await;
2644
2645        // Run both ends to populate cancellation tokens.
2646        control_plane_server
2647            .run()
2648            .await
2649            .expect("server should start");
2650        tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
2651        control_plane_client
2652            .run()
2653            .await
2654            .expect("client should start");
2655        tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
2656
2657        // Ensure we have at least one cancellation token (server or client side tasks).
2658        let server_tokens_before = control_plane_server
2659            .controller
2660            .inner
2661            .cancellation_tokens
2662            .read()
2663            .len();
2664        assert!(
2665            server_tokens_before > 0,
2666            "expected server to have active cancellation tokens before shutdown"
2667        );
2668
2669        let client_tokens_before = control_plane_client
2670            .controller
2671            .inner
2672            .cancellation_tokens
2673            .read()
2674            .len();
2675        assert!(
2676            client_tokens_before > 0,
2677            "expected client to have active cancellation tokens before shutdown"
2678        );
2679
2680        // Perform shutdown on both.
2681        control_plane_client
2682            .shutdown()
2683            .await
2684            .expect("client shutdown ok");
2685        control_plane_server
2686            .shutdown()
2687            .await
2688            .expect("server shutdown ok");
2689
2690        // After shutdown, all cancellation tokens should be drained.
2691        let server_tokens_after = control_plane_server
2692            .controller
2693            .inner
2694            .cancellation_tokens
2695            .read()
2696            .len();
2697        assert_eq!(
2698            server_tokens_after, 0,
2699            "expected server cancellation tokens to be drained after shutdown"
2700        );
2701
2702        let client_tokens_after = control_plane_client
2703            .controller
2704            .inner
2705            .cancellation_tokens
2706            .read()
2707            .len();
2708        assert_eq!(
2709            client_tokens_after, 0,
2710            "expected client cancellation tokens to be drained after shutdown"
2711        );
2712
2713        // Second shutdown should error because drain_signal has been taken.
2714        assert!(
2715            control_plane_server.shutdown().await.is_err(),
2716            "second shutdown on server should return an error"
2717        );
2718        assert!(
2719            control_plane_client.shutdown().await.is_err(),
2720            "second shutdown on client should return an error"
2721        );
2722    }
2723
2724    #[tokio::test]
2725    #[traced_test]
2726    async fn test_shutdown_without_run() {
2727        // Build a control plane but do NOT call run()
2728        let (control_plane_server, mut _control_plane_client, _client_cfg) = setup_control_planes(
2729            "127.0.0.1:50072",
2730            "shutdown-no-run-server",
2731            "shutdown-no-run-client",
2732        )
2733        .await;
2734
2735        // No tasks should be registered yet
2736        assert_eq!(
2737            control_plane_server
2738                .controller
2739                .inner
2740                .cancellation_tokens
2741                .read()
2742                .len(),
2743            0,
2744            "expected zero cancellation tokens before shutdown when not run"
2745        );
2746
2747        // Shutdown should still succeed gracefully.
2748        control_plane_server
2749            .shutdown()
2750            .await
2751            .expect("shutdown without prior run should succeed");
2752
2753        // Tokens remain zero.
2754        assert_eq!(
2755            control_plane_server
2756                .controller
2757                .inner
2758                .cancellation_tokens
2759                .read()
2760                .len(),
2761            0,
2762            "expected zero cancellation tokens after shutdown when not run"
2763        );
2764
2765        // Second shutdown should fail.
2766        assert!(
2767            control_plane_server.shutdown().await.is_err(),
2768            "second shutdown should error due to missing drain signal"
2769        );
2770    }
2771}