1use 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
107fn 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
123fn 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
133fn 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
143async 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
159pub type EspHomeApiBuildResult = Result<EspHomeApi, Error>;
162
163#[derive(TypedBuilder, Clone)]
199#[builder(build_method(into = EspHomeApiBuildResult))]
200pub struct EspHomeApi {
201 name: String,
203
204 #[builder(default = None, setter(strip_option(fallback=encryption_key_opt)))]
205 encryption_key: Option<String>,
206
207 #[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
253impl 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
269impl EspHomeApi {
271 pub async fn start<S>(&self, stream: S) -> Result<Connection, Error>
314 where
315 S: AsyncRead + AsyncWrite + Unpin + Send + 'static,
316 {
317 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 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 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 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 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 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 let mut handshake_state: HandshakeState<X25519, ChaCha20Poly1305, Sha256> =
443 HandshakeState::new(
444 noise_nn_psk0(),
445 false,
446 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 None => return Err(Error::Config("encryption key missing".to_string())),
459 };
460
461 handshake_state.push_psk(psk);
462 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 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 let (cancellation_write_tx, mut cancellation_write_rx) = oneshot::channel();
508
509 let (done_tx, done_rx) = oneshot::channel::<Result<(), Error>>();
511
512 let encrypt_cypher_for_write = encrypt_cypher;
514 tokio::spawn(async move {
515 loop {
516 let answer_message = tokio::select! {
518 biased; 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 let answer_messages_tx_clone = answer_messages_tx.clone();
593 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 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 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 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 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 let _ = cancellation_write_tx.send("read loop finished");
697 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}