Skip to main content

running_process/broker/server/
control_socket.rs

1//! Shared broker control socket dispatch for Hello and admin frames.
2//!
3//! The v1 broker uses one local socket for both client Hello negotiation and
4//! admin verbs. This module keeps the bounded synchronous serve helpers aligned
5//! with that contract while the long-lived daemon loop is still being built.
6
7use std::io::{Read, Write};
8use std::num::NonZeroUsize;
9use std::sync::atomic::{AtomicBool, Ordering};
10use std::sync::{mpsc, Mutex};
11
12use prost::Message;
13
14use crate::broker::protocol::{
15    read_frame, write_frame, AdminReply, AdminRequest, AdminVerb, ErrorCode, Frame, FramingError,
16    HelloReply, MAX_HELLO_BYTES,
17};
18
19use super::admin::{handle_admin_frame, AdminFrameError, AdminSnapshot, ADMIN_PAYLOAD_PROTOCOL};
20use super::connection::{
21    bind_local_socket, peer_identity_from_stream, refused_reply, reply_for_framing_error,
22    write_response_frame, BrokerConnectionError, HelloResponder, LocalSocketCleanup,
23    PeerCredentialPolicy,
24};
25use super::deadline_stream::{hello_read_deadline, DeadlineStream};
26use super::fd_pressure::{FdPressureDecision, FdPressureGuard};
27use super::hello_handler::PeerIdentity;
28
29/// Result of handling one control socket connection.
30#[derive(Clone, Debug, PartialEq)]
31pub enum ControlSocketReply {
32    /// Peer was rejected by credential policy before any bytes were read.
33    DroppedPeer,
34    /// The connection was handled as a Hello exchange.
35    Hello(HelloReply),
36    /// The connection was handled as an admin request.
37    Admin(AdminReply),
38    /// The connection carried an `ADMIN_VERB_SHUTDOWN` request (soldr#2442
39    /// Option B). The ack was already written to the client; the accept loop
40    /// stops serving on this outcome so the broker process can exit.
41    ShutdownRequested,
42}
43
44/// Decode the admin verb from a control frame, if it is a well-formed admin
45/// request. Used to intercept `ADMIN_VERB_SHUTDOWN` before the normal render.
46fn admin_request_verb(frame: &Frame) -> Option<AdminVerb> {
47    AdminRequest::decode(frame.payload.as_slice())
48        .ok()
49        .and_then(|request| AdminVerb::try_from(request.verb).ok())
50}
51
52/// Best-effort self-connect that unblocks a control-socket accept loop parked
53/// in a blocking `accept()`, so a shutdown flag set by a worker takes effect
54/// without waiting for the next real client (soldr#2442 Option B).
55fn wake_control_socket_accept(socket_path: &str) {
56    if let Ok(endpoint) = crate::platform::ipc::Endpoint::new(socket_path.to_owned()) {
57        let _ = crate::platform::ipc::Stream::connect(&endpoint);
58    }
59}
60
61/// Connection limit for a broker control-socket accept loop.
62#[derive(Clone, Copy, Debug, PartialEq, Eq)]
63pub enum ControlSocketConnectionLimit {
64    /// Accept exactly this many connections, then return.
65    Bounded(NonZeroUsize),
66    /// Continue accepting until the process exits or binding/accepting fails.
67    Unbounded,
68}
69
70impl ControlSocketConnectionLimit {
71    fn should_continue(self, accepted: usize) -> bool {
72        match self {
73            Self::Bounded(limit) => accepted < limit.get(),
74            Self::Unbounded => true,
75        }
76    }
77}
78
79const LAUNCH_CONTROL_SOCKET_WORKERS: usize = 8;
80
81/// Handle one already-accepted broker control connection.
82pub fn handle_control_connection_with_peer_policy<S, R, F>(
83    stream: &mut S,
84    hello_responder: &R,
85    snapshot_provider: &F,
86    peer: PeerIdentity,
87    peer_policy: &PeerCredentialPolicy,
88) -> Result<ControlSocketReply, ControlSocketError>
89where
90    S: Read + Write,
91    R: HelloResponder + ?Sized,
92    F: Fn() -> AdminSnapshot + ?Sized,
93{
94    handle_control_connection_with_peer_policy_and_fd_guard(
95        stream,
96        hello_responder,
97        snapshot_provider,
98        peer,
99        peer_policy,
100        None,
101    )
102}
103
104/// Handle one already-accepted broker control connection, refusing Hello
105/// frames with `ERROR_FD_PRESSURE` while `fd_guard` reports a demotion
106/// (#390). Admin frames are always served so `status` can surface the
107/// demoted state.
108pub fn handle_control_connection_with_peer_policy_and_fd_guard<S, R, F>(
109    stream: &mut S,
110    hello_responder: &R,
111    snapshot_provider: &F,
112    peer: PeerIdentity,
113    peer_policy: &PeerCredentialPolicy,
114    fd_guard: Option<&FdPressureGuard>,
115) -> Result<ControlSocketReply, ControlSocketError>
116where
117    S: Read + Write,
118    R: HelloResponder + ?Sized,
119    F: Fn() -> AdminSnapshot + ?Sized,
120{
121    if !peer_policy.allows(&peer) {
122        return Ok(ControlSocketReply::DroppedPeer);
123    }
124
125    let request_bytes = match read_frame(stream) {
126        Ok(bytes) => bytes,
127        Err(err) => {
128            let reply = reply_for_framing_error(&err);
129            write_response_frame(stream, None, &reply)?;
130            return Ok(ControlSocketReply::Hello(reply));
131        }
132    };
133
134    let request_frame = match Frame::decode(request_bytes.as_slice()) {
135        Ok(frame) => frame,
136        Err(_) => {
137            let reply = refused_reply(ErrorCode::ErrorPeerRejected, "malformed broker Frame", 0);
138            write_response_frame(stream, None, &reply)?;
139            return Ok(ControlSocketReply::Hello(reply));
140        }
141    };
142
143    if request_frame.payload_protocol == ADMIN_PAYLOAD_PROTOCOL {
144        // soldr#2442 Option B: a SHUTDOWN request is acked like any admin verb
145        // (render_admin_reply produces the ack body) but reported as a distinct
146        // outcome so the accept loop stops serving and the broker exits.
147        let is_shutdown = admin_request_verb(&request_frame) == Some(AdminVerb::Shutdown);
148        let snapshot = snapshot_provider();
149        let response_frame = handle_admin_frame(request_frame, &snapshot)?;
150        let reply = write_admin_response_frame(stream, &response_frame)?;
151        return Ok(if is_shutdown {
152            ControlSocketReply::ShutdownRequested
153        } else {
154            ControlSocketReply::Admin(reply)
155        });
156    }
157
158    let reply = if request_bytes.len() > MAX_HELLO_BYTES {
159        refused_reply(
160            ErrorCode::ErrorPeerRejected,
161            "initial Hello frame exceeds 64 KiB",
162            0,
163        )
164    } else if let Some(guard) = fd_guard.filter(|guard| guard.is_demoted()) {
165        guard.refusal_reply()
166    } else {
167        hello_responder.handle_frame(request_frame.clone(), peer)
168    };
169    write_response_frame(stream, Some(&request_frame), &reply)?;
170    Ok(ControlSocketReply::Hello(reply))
171}
172
173/// Run a bounded local-socket accept loop that dispatches Hello and admin
174/// frames on the same endpoint.
175pub fn serve_control_socket_connections_with_policy<R, F>(
176    socket_path: &str,
177    hello_responder: &R,
178    snapshot_provider: F,
179    connection_count: usize,
180    peer_policy: &PeerCredentialPolicy,
181) -> Result<(), ControlSocketError>
182where
183    R: HelloResponder + ?Sized,
184    F: Fn() -> AdminSnapshot,
185{
186    let Some(connection_count) = NonZeroUsize::new(connection_count) else {
187        return Ok(());
188    };
189
190    serve_control_socket_connections_with_limit_and_policy(
191        socket_path,
192        hello_responder,
193        snapshot_provider,
194        ControlSocketConnectionLimit::Bounded(connection_count),
195        peer_policy,
196    )
197}
198
199/// Run a broker control-socket accept loop that dispatches Hello and admin
200/// frames on the same endpoint.
201pub fn serve_control_socket_connections_with_limit_and_policy<R, F>(
202    socket_path: &str,
203    hello_responder: &R,
204    snapshot_provider: F,
205    connection_limit: ControlSocketConnectionLimit,
206    peer_policy: &PeerCredentialPolicy,
207) -> Result<(), ControlSocketError>
208where
209    R: HelloResponder + ?Sized,
210    F: Fn() -> AdminSnapshot,
211{
212    serve_control_socket_connections_with_limit_policy_and_post_hello(
213        socket_path,
214        hello_responder,
215        snapshot_provider,
216        connection_limit,
217        peer_policy,
218        |_stream, _reply| {},
219    )
220}
221
222/// Run a broker control-socket accept loop with a post-Hello connection hook.
223///
224/// `post_hello` runs after a Hello reply has been written, with the client
225/// connection still open. The production serve path uses it to attempt the
226/// optional handle-passing handoff (#387) when negotiation issued a handoff
227/// token; the hook must stay silent toward the client on failure.
228pub fn serve_control_socket_connections_with_limit_policy_and_post_hello<R, F, H>(
229    socket_path: &str,
230    hello_responder: &R,
231    snapshot_provider: F,
232    connection_limit: ControlSocketConnectionLimit,
233    peer_policy: &PeerCredentialPolicy,
234    post_hello: H,
235) -> Result<(), ControlSocketError>
236where
237    R: HelloResponder + ?Sized,
238    F: Fn() -> AdminSnapshot,
239    H: FnMut(&mut interprocess::local_socket::Stream, &HelloReply),
240{
241    let fd_guard = FdPressureGuard::default();
242    serve_control_socket_connections_with_limit_policy_post_hello_and_fd_guard(
243        socket_path,
244        hello_responder,
245        snapshot_provider,
246        connection_limit,
247        peer_policy,
248        post_hello,
249        &fd_guard,
250    )
251}
252
253/// Run a broker control-socket accept loop with fd-pressure self-demotion
254/// (#390).
255///
256/// `fd_guard` is shared so callers can surface the demotion state in admin
257/// snapshots. When `accept()` fails with EMFILE/ENFILE the loop demotes
258/// instead of returning the error: subsequent Hello connections receive a
259/// structured `ERROR_FD_PRESSURE` refusal (admin verbs keep working), and
260/// the guard recovers automatically after a streak of successful accepts.
261#[allow(clippy::too_many_arguments)]
262pub fn serve_control_socket_connections_with_limit_policy_post_hello_and_fd_guard<R, F, H>(
263    socket_path: &str,
264    hello_responder: &R,
265    snapshot_provider: F,
266    connection_limit: ControlSocketConnectionLimit,
267    peer_policy: &PeerCredentialPolicy,
268    mut post_hello: H,
269    fd_guard: &FdPressureGuard,
270) -> Result<(), ControlSocketError>
271where
272    R: HelloResponder + ?Sized,
273    F: Fn() -> AdminSnapshot,
274    H: FnMut(&mut interprocess::local_socket::Stream, &HelloReply),
275{
276    serve_control_socket_connections_with_limit_policy_post_hello_opaque(
277        socket_path,
278        hello_responder,
279        snapshot_provider,
280        connection_limit,
281        peer_policy,
282        move |stream, reply| {
283            let mut legacy_stream =
284                running_process_platform_internal::into_legacy_ipc_stream(stream);
285            post_hello(&mut legacy_stream, reply);
286        },
287        fd_guard,
288    )
289}
290
291/// Internal post-Hello path that retains the opaque platform stream.
292///
293/// The public callback above preserves its 4.x concrete-stream contract at
294/// the final callback boundary. Production broker mechanics use this entry
295/// point and therefore never unwrap the platform transport.
296#[allow(clippy::too_many_arguments)]
297pub(super) fn serve_control_socket_connections_with_limit_policy_post_hello_opaque<R, F, H>(
298    socket_path: &str,
299    hello_responder: &R,
300    snapshot_provider: F,
301    connection_limit: ControlSocketConnectionLimit,
302    peer_policy: &PeerCredentialPolicy,
303    mut post_hello: H,
304    fd_guard: &FdPressureGuard,
305) -> Result<(), ControlSocketError>
306where
307    R: HelloResponder + ?Sized,
308    F: Fn() -> AdminSnapshot,
309    H: FnMut(crate::platform::ipc::Stream, &HelloReply),
310{
311    /// Back-off between accepts while demoted so a hard fd-exhaustion loop
312    /// cannot spin the broker's CPU at 100%.
313    const FD_PRESSURE_ACCEPT_BACKOFF: std::time::Duration = std::time::Duration::from_millis(50);
314
315    let listener = bind_local_socket(socket_path)?;
316    let cleanup = LocalSocketCleanup(socket_path);
317    let result = (|| {
318        let mut accepted = 0;
319        while connection_limit.should_continue(accepted) {
320            let mut stream = match listener.accept() {
321                Ok(stream) => {
322                    fd_guard.on_accept_ok();
323                    stream
324                }
325                Err(err) => {
326                    let was_demoted = fd_guard.is_demoted();
327                    if fd_guard.on_accept_error(&err) == FdPressureDecision::Demoted {
328                        if !was_demoted {
329                            eprintln!(
330                                "running-process-broker: accept on {socket_path} demoted \
331                                 under fd pressure: {err}"
332                            );
333                        }
334                        accepted += 1;
335                        std::thread::sleep(FD_PRESSURE_ACCEPT_BACKOFF);
336                        continue;
337                    }
338                    return Err(BrokerConnectionError::Io(err).into());
339                }
340            };
341            accepted += 1;
342            let peer = peer_identity_from_stream(&stream)?;
343            // Bound the Hello/admin read against a deadline (issue #590,
344            // cluster G) so a silent or trickle peer cannot stall this
345            // single-threaded accept loop. Set the accepted stream
346            // nonblocking for the deadline-bounded handler, then restore
347            // blocking mode for the post_hello callback below.
348            let nonblocking_set = stream.set_nonblocking(true).is_ok();
349            let reply_result = {
350                let mut deadline_stream = DeadlineStream::new(&mut stream, hello_read_deadline());
351                handle_control_connection_with_peer_policy_and_fd_guard(
352                    &mut deadline_stream,
353                    hello_responder,
354                    &snapshot_provider,
355                    peer.clone(),
356                    peer_policy,
357                    Some(fd_guard),
358                )
359            };
360            if nonblocking_set {
361                let _ = stream.set_nonblocking(false);
362            }
363            let reply = reply_result?;
364            if reply == ControlSocketReply::DroppedPeer {
365                eprintln!(
366                    "running-process-broker: dropped connection on {socket_path} from peer \
367                     pid={} uid_or_sid={:?}: credential policy refused",
368                    peer.pid, peer.uid_or_sid
369                );
370            }
371            if let ControlSocketReply::Hello(hello_reply) = &reply {
372                post_hello(stream, hello_reply);
373            }
374        }
375        Ok(())
376    })();
377    drop(listener);
378    drop(cleanup);
379    result
380}
381
382/// Run the launch-backed control socket with a bounded worker pool.
383///
384/// Backend launch may perform image verification, placement, process spawn,
385/// and readiness checks. Keeping that work on the accept thread serializes
386/// unrelated service roots, so this variant dispatches accepted connections
387/// to workers while retaining a fixed upper bound on broker threads.
388pub(super) fn serve_launch_control_socket_connections_concurrently<R, F>(
389    socket_path: &str,
390    hello_responder: &R,
391    snapshot_provider: F,
392    connection_limit: ControlSocketConnectionLimit,
393    peer_policy: &PeerCredentialPolicy,
394    fd_guard: &FdPressureGuard,
395) -> Result<(), ControlSocketError>
396where
397    R: HelloResponder + Sync + ?Sized,
398    F: Fn() -> AdminSnapshot + Sync,
399{
400    const FD_PRESSURE_ACCEPT_BACKOFF: std::time::Duration = std::time::Duration::from_millis(50);
401
402    let listener = bind_local_socket(socket_path)?;
403    let cleanup = LocalSocketCleanup(socket_path);
404    let bounded = matches!(connection_limit, ControlSocketConnectionLimit::Bounded(_));
405    let (job_sender, job_receiver) = mpsc::sync_channel(LAUNCH_CONTROL_SOCKET_WORKERS);
406    let job_receiver = Mutex::new(job_receiver);
407    let (result_sender, result_receiver) = mpsc::channel();
408    // soldr#2442 Option B: set by a worker that handles an `ADMIN_VERB_SHUTDOWN`
409    // request; the accept loop observes it and stops serving so the broker exits.
410    let shutdown = AtomicBool::new(false);
411    let result = std::thread::scope(|scope| {
412        let mut workers = Vec::with_capacity(LAUNCH_CONTROL_SOCKET_WORKERS);
413
414        for _ in 0..LAUNCH_CONTROL_SOCKET_WORKERS {
415            let result_sender = result_sender.clone();
416            let job_receiver = &job_receiver;
417            let snapshot_provider = &snapshot_provider;
418            let shutdown = &shutdown;
419            workers.push(scope.spawn(move || loop {
420                let job = {
421                    let receiver = job_receiver
422                        .lock()
423                        .unwrap_or_else(|poisoned| poisoned.into_inner());
424                    receiver.recv()
425                };
426                let Ok((mut stream, peer)) = job else {
427                    break;
428                };
429                let outcome = handle_accepted_control_connection(
430                    &mut stream,
431                    hello_responder,
432                    snapshot_provider,
433                    peer,
434                    peer_policy,
435                    fd_guard,
436                );
437                if matches!(outcome, Ok(ControlSocketReply::ShutdownRequested)) {
438                    shutdown.store(true, Ordering::SeqCst);
439                    // Unblock the accept loop parked in `accept()` so it sees the
440                    // flag now instead of on the next real connection.
441                    wake_control_socket_accept(socket_path);
442                }
443                let result = outcome.map(|_| ());
444                if bounded {
445                    let _ = result_sender.send(result);
446                } else if let Err(error) = result {
447                    eprintln!(
448                        "running-process-broker: control connection failed on {socket_path}: {error}"
449                    );
450                }
451            }));
452        }
453        drop(result_sender);
454
455        let mut accepted = 0;
456        let mut dispatched = 0;
457        let accept_result: Result<(), ControlSocketError> = loop {
458            // soldr#2442 Option B: a worker set this after acking a SHUTDOWN
459            // request. Stop serving so the broker process can exit; in-flight
460            // worker connections drain as the thread scope joins them below.
461            if shutdown.load(Ordering::SeqCst) {
462                break Ok(());
463            }
464            if !connection_limit.should_continue(accepted) {
465                break Ok(());
466            }
467            let stream = match listener.accept() {
468                Ok(stream) => {
469                    fd_guard.on_accept_ok();
470                    stream
471                }
472                Err(error) => {
473                    let was_demoted = fd_guard.is_demoted();
474                    if fd_guard.on_accept_error(&error) == FdPressureDecision::Demoted {
475                        if !was_demoted {
476                            eprintln!(
477                                "running-process-broker: accept on {socket_path} demoted \
478                                 under fd pressure: {error}"
479                            );
480                        }
481                        accepted += 1;
482                        std::thread::sleep(FD_PRESSURE_ACCEPT_BACKOFF);
483                        continue;
484                    }
485                    break Err(BrokerConnectionError::Io(error).into());
486                }
487            };
488            // The accept above may have been unblocked by the shutdown
489            // self-connect rather than a real client; drop it and stop.
490            if shutdown.load(Ordering::SeqCst) {
491                break Ok(());
492            }
493            accepted += 1;
494            let peer = match peer_identity_from_stream(&stream) {
495                Ok(peer) => peer,
496                Err(error) => break Err(error.into()),
497            };
498            if job_sender.send((stream, peer)).is_err() {
499                break Err(BrokerConnectionError::WorkerPanic.into());
500            }
501            dispatched += 1;
502        };
503        drop(job_sender);
504
505        let mut connection_error = None;
506        if bounded {
507            for _ in 0..dispatched {
508                match result_receiver.recv() {
509                    Ok(Ok(())) => {}
510                    Ok(Err(error)) if connection_error.is_none() => connection_error = Some(error),
511                    Ok(Err(_)) => {}
512                    Err(_) => break,
513                }
514            }
515        }
516
517        let mut worker_panicked = false;
518        for worker in workers {
519            worker_panicked |= worker.join().is_err();
520        }
521        if worker_panicked {
522            return Err(BrokerConnectionError::WorkerPanic.into());
523        }
524        accept_result?;
525        if let Some(error) = connection_error {
526            return Err(error);
527        }
528        Ok(())
529    });
530    drop(listener);
531    drop(cleanup);
532    result
533}
534
535fn handle_accepted_control_connection<R, F>(
536    stream: &mut crate::platform::ipc::Stream,
537    hello_responder: &R,
538    snapshot_provider: &F,
539    peer: PeerIdentity,
540    peer_policy: &PeerCredentialPolicy,
541    fd_guard: &FdPressureGuard,
542) -> Result<ControlSocketReply, ControlSocketError>
543where
544    R: HelloResponder + ?Sized,
545    F: Fn() -> AdminSnapshot + ?Sized,
546{
547    let peer_for_log = peer.clone();
548    let nonblocking_set = stream.set_nonblocking(true).is_ok();
549    let reply_result = {
550        let mut deadline_stream = DeadlineStream::new(stream, hello_read_deadline());
551        handle_control_connection_with_peer_policy_and_fd_guard(
552            &mut deadline_stream,
553            hello_responder,
554            snapshot_provider,
555            peer,
556            peer_policy,
557            Some(fd_guard),
558        )
559    };
560    if nonblocking_set {
561        let _ = stream.set_nonblocking(false);
562    }
563    let reply = reply_result?;
564    if reply == ControlSocketReply::DroppedPeer {
565        eprintln!(
566            "running-process-broker: dropped connection from peer pid={} uid_or_sid={:?}: \
567             credential policy refused",
568            peer_for_log.pid, peer_for_log.uid_or_sid
569        );
570    }
571    Ok(reply)
572}
573
574fn write_admin_response_frame<W: Write>(
575    writer: &mut W,
576    response_frame: &Frame,
577) -> Result<AdminReply, ControlSocketError> {
578    let mut response_bytes = Vec::new();
579    response_frame
580        .encode(&mut response_bytes)
581        .map_err(ControlSocketError::EncodeFrame)?;
582    write_frame(writer, &response_bytes)?;
583    AdminReply::decode(response_frame.payload.as_slice())
584        .map_err(ControlSocketError::DecodeAdminReply)
585}
586
587/// Errors raised while dispatching a shared broker control socket frame.
588#[derive(Debug, thiserror::Error)]
589pub enum ControlSocketError {
590    /// Hello/local-socket connection handling failed.
591    #[error(transparent)]
592    Connection(#[from] BrokerConnectionError),
593    /// Frame read/write failed.
594    #[error(transparent)]
595    Framing(#[from] FramingError),
596    /// Admin frame validation or dispatch failed.
597    #[error(transparent)]
598    AdminFrame(#[from] AdminFrameError),
599    /// The response frame could not be encoded.
600    #[error("failed to encode broker control response Frame: {0}")]
601    EncodeFrame(prost::EncodeError),
602    /// The admin response payload could not be decoded after dispatch.
603    #[error("failed to decode admin reply payload: {0}")]
604    DecodeAdminReply(prost::DecodeError),
605}
606
607#[cfg(test)]
608mod cluster_g_tests {
609    use super::*;
610    use std::time::{Duration, Instant};
611
612    struct NeverReady;
613    impl Read for NeverReady {
614        fn read(&mut self, _buf: &mut [u8]) -> std::io::Result<usize> {
615            Err(std::io::Error::new(
616                std::io::ErrorKind::WouldBlock,
617                "never ready",
618            ))
619        }
620    }
621    impl Write for NeverReady {
622        fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
623            Ok(buf.len())
624        }
625        fn flush(&mut self) -> std::io::Result<()> {
626            Ok(())
627        }
628    }
629
630    #[test]
631    fn deadline_stream_read_times_out_on_silent_peer() {
632        let mut inner = NeverReady;
633        let mut ds = DeadlineStream::new(&mut inner, Instant::now() + Duration::from_millis(100));
634        let mut buf = [0u8; 4];
635        let start = Instant::now();
636        let err = ds.read(&mut buf).unwrap_err();
637        assert_eq!(err.kind(), std::io::ErrorKind::TimedOut);
638        assert!(start.elapsed() < Duration::from_secs(2), "must be bounded");
639    }
640
641    #[test]
642    fn deadline_stream_passes_ready_data_through() {
643        let data = b"hello";
644        let mut cursor = std::io::Cursor::new(data.to_vec());
645        let mut ds = DeadlineStream::new(&mut cursor, Instant::now() + Duration::from_secs(1));
646        let mut buf = [0u8; 5];
647        ds.read_exact(&mut buf).unwrap();
648        assert_eq!(&buf, data);
649    }
650}
651
652#[cfg(test)]
653#[path = "../../tests/control_socket_coverage.rs"]
654mod coverage_tests;