Skip to main content

esphome_native_api/
esphomeapi.rs

1//! Low-level ESPHome native API implementation.
2//!
3//! This module provides [`EspHomeApi`], which handles the core protocol communication
4//! with ESPHome devices. It manages connection establishment, encryption handshakes,
5//! message framing, and protocol state.
6//!
7//! # Examples
8//!
9//! ## Plaintext Connection
10//!
11//! ```rust,no_run
12//! use esphome_native_api::esphomeapi::EspHomeApi;
13//! use tokio::net::TcpStream;
14//!
15//! #[tokio::main]
16//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
17//!     let stream = TcpStream::connect("192.168.1.100:6053").await?;
18//!     
19//!     let mut api = EspHomeApi::builder()
20//!         .name("my-client".to_string())
21//!         .build()?;
22//!     
23//!     let connection = api.start(stream).await?;
24//!     let tx = connection.sender();
25//!     let mut rx = connection.receiver();
26//!     # let _ = (tx, &mut rx);
27//!     Ok(())
28//! }
29//! ```
30//!
31//! ## Encrypted Connection
32//!
33//! ```rust,no_run
34//! use esphome_native_api::esphomeapi::EspHomeApi;
35//! use tokio::net::TcpStream;
36//!
37//! #[tokio::main]
38//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
39//!     let stream = TcpStream::connect("192.168.1.100:6053").await?;
40//!     
41//!     let mut api = EspHomeApi::builder()
42//!         .name("my-client".to_string())
43//!         .encryption_key("your-base64-encoded-key".to_string())
44//!         .build()?;
45//!     
46//!     let connection = api.start(stream).await?;
47//!     let tx = connection.sender();
48//!     let mut rx = connection.receiver();
49//!     # let _ = (tx, &mut rx);
50//!     Ok(())
51//! }
52//! ```
53
54use base64::prelude::*;
55use futures::sink::SinkExt;
56use log::debug;
57use log::error;
58use log::info;
59use log::trace;
60use noise_protocol::CipherState;
61use noise_protocol::ErrorKind;
62use noise_protocol::HandshakeState;
63use noise_protocol::patterns::noise_nn_psk0;
64use noise_rust_crypto::ChaCha20Poly1305;
65use noise_rust_crypto::Sha256;
66use noise_rust_crypto::X25519;
67use std::sync::Arc;
68use tokio::io::{AsyncBufReadExt, AsyncRead, AsyncWrite, AsyncWriteExt, BufReader};
69use tokio::sync::Mutex;
70use tokio::sync::broadcast;
71use tokio::sync::mpsc;
72use tokio::sync::oneshot;
73use tokio_stream::StreamExt;
74use tokio_util::codec::FramedRead;
75use tokio_util::codec::FramedWrite;
76use typed_builder::TypedBuilder;
77
78use crate::connection::Connection;
79use crate::error::{DisconnectReason, Error, FrameError, HandshakeError};
80use crate::frame::FrameCodec;
81use crate::packet_encrypted;
82use crate::packet_plaintext;
83use crate::parser::ProtoMessage;
84use crate::proto::{
85    self, AuthenticationResponse, DeviceInfoResponse, DisconnectResponse, HelloResponse,
86    PingResponse,
87};
88
89async fn write_error_and_disconnect<W>(mut writer: FramedWrite<W, FrameCodec>, message: &str)
90where
91    W: AsyncWrite + Unpin,
92{
93    error!("API Failure: {}. Disconnecting.", message);
94    let packet = [[1].to_vec(), message.as_bytes().to_vec()].concat();
95    if let Err(err) = writer.send(packet).await {
96        debug!("failed to send error frame to peer: {:?}", err);
97    }
98    if let Err(err) = writer.flush().await {
99        debug!("failed to flush error frame to peer: {:?}", err);
100    }
101    let mut tcp_write = writer.into_inner();
102    if let Err(err) = tcp_write.shutdown().await {
103        error!("failed to shutdown socket: {:?}", err);
104    }
105}
106
107/// Classify an I/O error surfaced by the framed reader into a typed [`Error`].
108///
109/// A peer going away (`ConnectionReset`, `BrokenPipe`, …) is an expected
110/// terminal condition, not a failure; malformed framing is a [`FrameError`];
111/// anything else is a genuine I/O fault.
112fn classify_read_error(err: std::io::Error) -> Error {
113    use std::io::ErrorKind::*;
114    match err.kind() {
115        ConnectionReset | BrokenPipe | ConnectionAborted | NotConnected | UnexpectedEof => {
116            Error::Disconnected(DisconnectReason::Reset(err.kind()))
117        }
118        InvalidData => Error::Frame(FrameError::Malformed(err.to_string())),
119        _ => Error::Io(err),
120    }
121}
122
123/// Whether a writer-side I/O error means the peer has gone away (so we should
124/// terminate quietly rather than log a fault).
125fn is_peer_gone(err: &std::io::Error) -> bool {
126    use std::io::ErrorKind::*;
127    matches!(
128        err.kind(),
129        ConnectionReset | BrokenPipe | ConnectionAborted | NotConnected
130    )
131}
132
133/// Map an I/O error during the handshake into a typed [`Error`], treating a
134/// peer that went away as [`HandshakeError::Aborted`].
135fn handshake_io_err(err: std::io::Error) -> Error {
136    if is_peer_gone(&err) || err.kind() == std::io::ErrorKind::UnexpectedEof {
137        Error::Handshake(HandshakeError::Aborted)
138    } else {
139        Error::Io(err)
140    }
141}
142
143/// Read the next frame during the handshake phase, mapping a mid-handshake
144/// disconnect to [`HandshakeError::Aborted`].
145async fn read_handshake_frame<R>(reader: &mut FramedRead<R, FrameCodec>) -> Result<Vec<u8>, Error>
146where
147    R: AsyncRead + Unpin,
148{
149    match reader.next().await {
150        Some(Ok(frame)) => Ok(frame),
151        Some(Err(e)) => Err(match classify_read_error(e) {
152            Error::Disconnected(_) => HandshakeError::Aborted.into(),
153            other => other,
154        }),
155        None => Err(HandshakeError::Aborted.into()),
156    }
157}
158
159/// Fallible output of [`EspHomeApi`]'s builder. An `Err` means the configuration
160/// was invalid — currently, an encryption key that is not valid base64.
161pub type EspHomeApiBuildResult = Result<EspHomeApi, Error>;
162
163/// Low-level ESPHome native API client.
164///
165/// `EspHomeApi` provides direct access to the ESPHome native API protocol,
166/// handling connection setup, encryption, and message framing. This is the
167/// lower-level API that [`crate::esphomeserver::EspHomeServer`] builds upon.
168///
169/// This struct supports both encrypted and plaintext connections and uses
170/// the builder pattern for configuration via [`TypedBuilder`].
171///
172/// # Builder Options
173///
174/// - `name`: Device name (required)
175/// - `encryption_key`: Base64-encoded encryption key (optional, enables encryption)
176/// - `api_version_major`: API version major number (default: 1)
177/// - `api_version_minor`: API version minor number (default: 10)
178/// - `server_info`: Server identification string (default: "Rust: esphome-native-api")
179/// - `friendly_name`: Human-readable device name (optional)
180/// - `mac`: MAC address (optional)
181/// - `model`: Device model (optional)
182/// - `manufacturer`: Device manufacturer (optional)
183/// - `suggested_area`: Suggested area for the device (optional)
184/// - `bluetooth_mac_address`: Bluetooth MAC address (optional)
185///
186/// # Examples
187///
188/// ```rust
189/// use esphome_native_api::esphomeapi::EspHomeApi;
190///
191/// let api = EspHomeApi::builder()
192///     .name("bedroom-light".to_string())
193///     .api_version_major(1)
194///     .api_version_minor(10)
195///     .friendly_name("Bedroom Light".to_string())
196///     .build().unwrap();
197/// ```
198#[derive(TypedBuilder, Clone)]
199#[builder(build_method(into = EspHomeApiBuildResult))]
200pub struct EspHomeApi {
201    // Private fields
202    name: String,
203
204    #[builder(default = None, setter(strip_option(fallback=encryption_key_opt)))]
205    encryption_key: Option<String>,
206
207    /// Decoded pre-shared key, populated from `encryption_key` at build time so
208    /// that a misconfigured key fails during `build()` rather than at connect.
209    #[builder(default, setter(skip))]
210    noise_psk: Option<Vec<u8>>,
211
212    #[builder(default = 1)]
213    api_version_major: u32,
214    #[builder(default = 10)]
215    api_version_minor: u32,
216    #[builder(default="Rust: esphome-native-api".to_string())]
217    server_info: String,
218
219    #[builder(default = None, setter(strip_option(fallback=friendly_name_opt)))]
220    friendly_name: Option<String>,
221
222    #[builder(default = None, setter(strip_option(fallback=mac_opt)))]
223    mac: Option<String>,
224
225    #[builder(default = None, setter(strip_option(fallback=model_opt)))]
226    model: Option<String>,
227
228    #[builder(default = None, setter(strip_option(fallback=manufacturer_opt)))]
229    manufacturer: Option<String>,
230    #[builder(default = None, setter(strip_option(fallback=suggested_area_opt)))]
231    suggested_area: Option<String>,
232    #[builder(default = None, setter(strip_option(fallback=bluetooth_mac_address_opt)))]
233    bluetooth_mac_address: Option<String>,
234
235    #[builder(default = None, setter(strip_option(fallback=project_name_opt)))]
236    project_name: Option<String>,
237
238    #[builder(default = None, setter(strip_option(fallback=project_version_opt)))]
239    project_version: Option<String>,
240    #[builder(default = None, setter(strip_option(fallback=compilation_time_opt)))]
241    compilation_time: Option<String>,
242
243    #[builder(default = 0)]
244    legacy_bluetooth_proxy_version: u32,
245    #[builder(default = 0)]
246    bluetooth_proxy_feature_flags: u32,
247    #[builder(default = 0)]
248    legacy_voice_assistant_version: u32,
249    #[builder(default = 0)]
250    voice_assistant_feature_flags: u32,
251}
252
253/// Validates and decodes the configured encryption key when the builder's
254/// `build()` runs, so a misconfiguration surfaces as [`Error::Config`] at
255/// construction rather than partway through the connection handshake.
256impl From<EspHomeApi> for EspHomeApiBuildResult {
257    fn from(mut api: EspHomeApi) -> Self {
258        api.noise_psk =
259            match api.encryption_key.as_deref() {
260                Some(key) => Some(BASE64_STANDARD.decode(key).map_err(|e| {
261                    Error::Config(format!("encryption key is not valid base64: {e}"))
262                })?),
263                None => None,
264            };
265        Ok(api)
266    }
267}
268
269/// Handles the ESPHome API protocol with encryption support.
270impl EspHomeApi {
271    /// Starts the API client and establishes communication with an ESPHome device.
272    ///
273    /// This method performs the complete connection handshake, including:
274    /// 1. Detecting whether encryption is required
275    /// 2. Performing encryption handshake if needed
276    /// 3. Exchanging hello messages
277    /// 4. Setting up message routing
278    ///
279    /// # Arguments
280    ///
281    /// * `tcp_stream` - An established TCP connection to the ESPHome device
282    ///
283    /// # Returns
284    ///
285    /// Returns a [`Connection`] handle. Use [`Connection::sender`] to send
286    /// messages to the device, [`Connection::receiver`] to receive messages from
287    /// it, and [`Connection::wait`] to observe when — and why — the connection
288    /// ends.
289    ///
290    /// # Errors
291    ///
292    /// Returns an [`Error`] if:
293    /// - The connection fails ([`Error::Io`])
294    /// - The encryption handshake fails ([`Error::Handshake`])
295    /// - The device requires encryption but no key was provided
296    /// - The peer disconnects mid-handshake ([`Error::Disconnected`])
297    ///
298    /// # Examples
299    ///
300    /// ```rust,no_run
301    /// # use esphome_native_api::esphomeapi::EspHomeApi;
302    /// # use tokio::net::TcpStream;
303    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
304    /// let stream = TcpStream::connect("192.168.1.100:6053").await?;
305    /// let api = EspHomeApi::builder().name("client".to_string()).build()?;
306    /// let connection = api.start(stream).await?;
307    /// let tx = connection.sender();
308    /// let mut rx = connection.receiver();
309    /// # let _ = (tx, &mut rx);
310    /// # Ok(())
311    /// # }
312    /// ```
313    pub async fn start<S>(&self, stream: S) -> Result<Connection, Error>
314    where
315        S: AsyncRead + AsyncWrite + Unpin + Send + 'static,
316    {
317        // Channel for messages
318        let (answer_messages_tx, mut answer_messages_rx) = mpsc::channel::<ProtoMessage>(16);
319        let (outgoing_messages_tx, outgoing_messages_rx) = broadcast::channel::<ProtoMessage>(16);
320
321        #[allow(deprecated)]
322        let device_info = DeviceInfoResponse {
323            api_encryption_supported: self.encryption_key.is_some(),
324            uses_password: false,
325            name: self.name.clone(),
326            mac_address: self.mac.clone().unwrap_or_default(),
327            esphome_version: proto::VERSION.to_owned(),
328            compilation_time: self.compilation_time.clone().unwrap_or_default(),
329            model: self.model.clone().unwrap_or_default(),
330            has_deep_sleep: false,
331            project_name: self.project_name.clone().unwrap_or_default(),
332            project_version: self.project_version.clone().unwrap_or_default(),
333            webserver_port: 0,
334            // See https://github.com/esphome/aioesphomeapi/blob/c1fee2f4eaff84d13ca71996bb272c28b82314fc/aioesphomeapi/model.py#L154
335            legacy_bluetooth_proxy_version: self.legacy_bluetooth_proxy_version,
336            bluetooth_proxy_feature_flags: self.bluetooth_proxy_feature_flags,
337            manufacturer: self.manufacturer.clone().unwrap_or_default(),
338            friendly_name: self.friendly_name.clone().unwrap_or(self.name.clone()),
339            legacy_voice_assistant_version: self.legacy_voice_assistant_version,
340            voice_assistant_feature_flags: self.voice_assistant_feature_flags,
341            suggested_area: self.suggested_area.clone().unwrap_or_default(),
342            bluetooth_mac_address: self.bluetooth_mac_address.clone().unwrap_or_default(),
343            areas: vec![],
344            devices: vec![],
345            area: None,
346            zwave_proxy_feature_flags: 0,
347            zwave_home_id: 0,
348            serial_proxies: vec![],
349        };
350
351        let hello_response = HelloResponse {
352            api_version_major: self.api_version_major,
353            api_version_minor: self.api_version_minor,
354            server_info: self.server_info.clone(),
355            name: self.name.clone(),
356        };
357
358        // Per-connection cipher state — created fresh so multiple concurrent connections
359        // never share encryption context.
360        let encrypt_cypher: Arc<Mutex<Option<CipherState<ChaCha20Poly1305>>>> =
361            Arc::new(Mutex::new(None));
362        let decrypt_cypher: Arc<Mutex<Option<CipherState<ChaCha20Poly1305>>>> =
363            Arc::new(Mutex::new(None));
364
365        // Stage 1: Initialization
366        trace!("Init Connection: Stage 1");
367
368        let (stream_read, stream_write) = tokio::io::split(stream);
369        let mut stream_read = BufReader::new(stream_read);
370
371        let peeked_bytes = stream_read.fill_buf().await.map_err(handshake_io_err)?;
372        if peeked_bytes.is_empty() {
373            return Err(HandshakeError::NoData.into());
374        }
375
376        trace!("TCP Peeked: {:02X?}", &peeked_bytes[0..1]);
377
378        let preamble = peeked_bytes[0] as usize;
379
380        let plaintext_communication = match preamble {
381            0 => {
382                debug!("Cleartext messaging");
383                true
384            }
385            1 => {
386                trace!("Encrypted messaging");
387                false
388            }
389            _ => {
390                return Err(HandshakeError::InvalidMarker(preamble as u8).into());
391            }
392        };
393        let encrypted = !plaintext_communication;
394
395        let decoder = FrameCodec::new(encrypted);
396        let encoder = FrameCodec::new(encrypted);
397        let mut reader = FramedRead::new(stream_read, decoder);
398        let mut writer = FramedWrite::new(stream_write, encoder);
399
400        if plaintext_communication {
401            if self.encryption_key.is_some() {
402                let encoder = FrameCodec::new(true);
403                let writer = FramedWrite::new(writer.into_inner(), encoder);
404                // The literal string goes on the wire to the connecting ESPHome
405                // client; the returned error is for this crate's caller.
406                write_error_and_disconnect(writer, "Only key encryption is enabled").await;
407                return Err(HandshakeError::EncryptionProtocolMismatch(
408                    "a client connected in plaintext, but an encryption key is configured (encryption is required)",
409                )
410                .into());
411            }
412        } else {
413            if self.encryption_key.is_none() {
414                let encoder = FrameCodec::new(false);
415                let writer = FramedWrite::new(writer.into_inner(), encoder);
416                // The plaintext framing (0x00 preamble) tells the encrypted
417                // client that this device speaks plaintext, so it can raise a
418                // precise error instead of a generic socket failure.
419                write_error_and_disconnect(writer, "No encrypted communication allowed").await;
420                return Err(HandshakeError::EncryptionProtocolMismatch(
421                    "a client requested an encrypted connection, but no encryption key is configured",
422                )
423                .into());
424            }
425
426            let frame_noise_hello = read_handshake_frame(&mut reader).await?;
427            debug!("Frame 1: {:02X?}", &frame_noise_hello);
428
429            let message_server_hello =
430                packet_encrypted::generate_server_hello_frame(self.name.clone(), self.mac.clone());
431
432            writer
433                .send(message_server_hello.clone())
434                .await
435                .map_err(handshake_io_err)?;
436            writer.flush().await.map_err(handshake_io_err)?;
437
438            let frame_handshake_request = read_handshake_frame(&mut reader).await?;
439            debug!("Frame 2: {:02X?}", &frame_handshake_request);
440
441            // Similar to https://github.com/esphome/aioesphomeapi/blob/60bcd1698dd622aeac6f4b5ec448bab0e3467c4f/aioesphomeapi/_frame_helper/noise.py#L248C17-L255
442            let mut handshake_state: HandshakeState<X25519, ChaCha20Poly1305, Sha256> =
443                HandshakeState::new(
444                    noise_nn_psk0(),
445                    false,
446                    // NEXT: This is somehow set from the first api message?
447                    b"NoiseAPIInit\0\0",
448                    None,
449                    None,
450                    None,
451                    None,
452                );
453
454            let psk = match self.noise_psk.as_ref() {
455                Some(psk) => psk,
456                // Unreachable: we return above when no key is configured but the
457                // peer requested encryption. Handled defensively to avoid a panic.
458                None => return Err(Error::Config("encryption key missing".to_string())),
459            };
460
461            handshake_state.push_psk(psk);
462            // Ignore message type byte
463            let handshake_payload = frame_handshake_request
464                .get(1..)
465                .ok_or(HandshakeError::MalformedFrame)?;
466            match handshake_state.read_message_vec(handshake_payload) {
467                Ok(_) => {}
468                Err(e) => match e.kind() {
469                    ErrorKind::Decryption => {
470                        // The literal string goes on the wire to the connecting
471                        // ESPHome client; the returned error is for this crate's
472                        // caller.
473                        write_error_and_disconnect(writer, "Handshake MAC failure").await;
474                        return Err(HandshakeError::MacFailure.into());
475                    }
476                    _ => {
477                        debug!("Failed to read message: {}", e);
478                    }
479                },
480            }
481
482            let out = handshake_state
483                .write_message_vec(b"")
484                .map_err(|e| Error::Handshake(HandshakeError::Crypto(e.to_string())))?;
485            {
486                let mut encrypt_cipher_changer = encrypt_cypher.lock().await;
487                let mut decrypt_cipher_changer = decrypt_cypher.lock().await;
488                let (decrypt_cipher, encrypt_cipher) = handshake_state.get_ciphers();
489                *encrypt_cipher_changer = Some(encrypt_cipher);
490                *decrypt_cipher_changer = Some(decrypt_cipher);
491            }
492
493            let mut message_handshake = vec![0];
494            message_handshake.extend(out);
495
496            debug!("Sending handshake");
497            writer
498                .send(message_handshake.clone())
499                .await
500                .map_err(handshake_io_err)?;
501            writer.flush().await.map_err(handshake_io_err)?;
502        }
503
504        debug!("Initialization done.");
505
506        // Asynchronously wait for an inbound socket.
507        let (cancellation_write_tx, mut cancellation_write_rx) = oneshot::channel();
508
509        // Reports the read loop's terminal outcome to `Connection::wait`.
510        let (done_tx, done_rx) = oneshot::channel::<Result<(), Error>>();
511
512        // Write Loop
513        let encrypt_cypher_for_write = encrypt_cypher;
514        tokio::spawn(async move {
515            loop {
516                // Wait for any new message
517                let answer_message = tokio::select! {
518                    biased; // Poll cancellation_write_rx first
519                    cancel_message = &mut cancellation_write_rx => {
520                        match cancel_message {
521                            Ok(reason) => debug!("Write loop received cancellation signal ({reason}), exiting."),
522                            Err(_) => debug!("Write loop cancellation channel dropped, exiting."),
523                        }
524                        break;
525                    }
526                    message = answer_messages_rx.recv() => match message {
527                        Some(message) => message,
528                        None => {
529                            debug!("Write loop: all senders dropped, exiting.");
530                            break;
531                        }
532                    }
533                };
534
535                debug!("Answer message: {:?}", answer_message);
536
537                let packet = if plaintext_communication {
538                    match packet_plaintext::message_to_packet(&answer_message) {
539                        Ok(packet) => packet,
540                        Err(e) => {
541                            error!("Write loop: failed to encode outgoing message: {e}");
542                            break;
543                        }
544                    }
545                } else {
546                    let mut encrypt_cipher_changer = encrypt_cypher_for_write.lock().await;
547                    let cipher = match encrypt_cipher_changer.as_mut() {
548                        Some(cipher) => cipher,
549                        None => {
550                            error!("Write loop: encryption cipher not initialized, exiting.");
551                            break;
552                        }
553                    };
554                    match packet_encrypted::message_to_packet(&answer_message, cipher) {
555                        Ok(packet) => packet,
556                        Err(e) => {
557                            error!("Write loop: failed to encode outgoing message: {e}");
558                            break;
559                        }
560                    }
561                };
562
563                if let Err(e) = writer.send(packet).await {
564                    if is_peer_gone(&e) {
565                        debug!("Write loop: peer gone while sending, exiting.");
566                    } else {
567                        error!("Write loop: failed to send message: {e}");
568                    }
569                    break;
570                }
571                if let Err(e) = writer.flush().await {
572                    if is_peer_gone(&e) {
573                        debug!("Write loop: peer gone while flushing, exiting.");
574                    } else {
575                        error!("Write loop: failed to flush message: {e}");
576                    }
577                    break;
578                }
579
580                if matches!(answer_message, ProtoMessage::DisconnectResponse(_)) {
581                    debug!("Disconnecting");
582                    let mut tcp_write = writer.into_inner();
583                    if let Err(err) = tcp_write.shutdown().await {
584                        error!("failed to shutdown socket: {:?}", err);
585                    }
586                    break;
587                }
588            }
589        });
590
591        // Clone all necessary data before spawning the task
592        let answer_messages_tx_clone = answer_messages_tx.clone();
593        // Read Loop
594        tokio::spawn(async move {
595            let outcome: Result<(), Error> = async move {
596                let mut disconnect_requested = false;
597                loop {
598                    let frame = match reader.next().await {
599                        None => {
600                            return Err(Error::Disconnected(if disconnect_requested {
601                                DisconnectReason::Requested
602                            } else {
603                                DisconnectReason::Eof
604                            }));
605                        }
606                        Some(Ok(frame)) => frame,
607                        Some(Err(e)) => return Err(classify_read_error(e)),
608                    };
609                    trace!("TCP Receive: {:02X?}", &frame);
610
611                    let decoded = if encrypted {
612                        let mut decrypt_cipher_changer = decrypt_cypher.lock().await;
613                        match decrypt_cipher_changer.as_mut() {
614                            Some(cipher) => packet_encrypted::packet_to_message(&frame, cipher),
615                            None => Err(FrameError::Malformed(
616                                "decryption cipher not initialized".to_string(),
617                            )),
618                        }
619                    } else {
620                        packet_plaintext::packet_to_message(&frame)
621                    };
622
623                    let message = match decoded {
624                        Ok(message) => message,
625                        // Forward-compatible: an unknown message type (for example
626                        // one introduced by a newer ESPHome release) is skipped so
627                        // the connection stays healthy instead of being torn down.
628                        Err(FrameError::UnknownMessageType(message_type)) => {
629                            debug!("Ignoring unknown message type {message_type}");
630                            continue;
631                        }
632                        Err(e) => return Err(e.into()),
633                    };
634
635                    // Protocol-level messages we answer ourselves produce a
636                    // response; everything else is forwarded to the consumer.
637                    let response: Option<ProtoMessage> = match &message {
638                        ProtoMessage::DisconnectRequest(disconnect_request) => {
639                            debug!("DisconnectRequest: {:?}", disconnect_request);
640                            disconnect_requested = true;
641                            Some(ProtoMessage::DisconnectResponse(DisconnectResponse {}))
642                        }
643                        ProtoMessage::PingRequest(ping_request) => {
644                            debug!("PingRequest: {:?}", ping_request);
645                            Some(ProtoMessage::PingResponse(PingResponse {}))
646                        }
647                        ProtoMessage::DeviceInfoRequest(device_info_request) => {
648                            debug!("DeviceInfoRequest: {:?}", device_info_request);
649                            Some(ProtoMessage::DeviceInfoResponse(device_info.clone()))
650                        }
651                        ProtoMessage::HelloRequest(hello_request) => {
652                            debug!("HelloRequest: {:?}", hello_request);
653                            Some(ProtoMessage::HelloResponse(hello_response.clone()))
654                        }
655                        ProtoMessage::AuthenticationRequest(authentication_request) => {
656                            debug!("AuthenticationRequest: {:?}", authentication_request);
657                            if !authentication_request.password.is_empty() {
658                                info!("Password Authentication is not supported");
659                                None
660                            } else {
661                                Some(ProtoMessage::AuthenticationResponse(
662                                    AuthenticationResponse {
663                                        invalid_password: false,
664                                    },
665                                ))
666                            }
667                        }
668                        other => {
669                            // No active receivers is normal (the consumer dropped
670                            // its handle); the message is simply dropped.
671                            let _ = outgoing_messages_tx.send(other.clone());
672                            None
673                        }
674                    };
675
676                    if let Some(response) = response
677                        && answer_messages_tx_clone.send(response).await.is_err()
678                    {
679                        // The write loop has stopped; the connection is finished.
680                        return Err(Error::Disconnected(if disconnect_requested {
681                            DisconnectReason::Requested
682                        } else {
683                            DisconnectReason::WriteClosed
684                        }));
685                    }
686                }
687            }
688            .await;
689
690            match &outcome {
691                Ok(()) => {}
692                Err(Error::Disconnected(reason)) => info!("Read loop stopped: {reason}"),
693                Err(e) => error!("Read loop stopped with error: {e}"),
694            }
695            // If sending fails, the write loop is probably already closed.
696            let _ = cancellation_write_tx.send("read loop finished");
697            // Report the terminal outcome to the consumer (ignored if dropped).
698            let _ = done_tx.send(outcome);
699        });
700
701        Ok(Connection::new(
702            answer_messages_tx,
703            outgoing_messages_rx,
704            done_rx,
705        ))
706    }
707}