Skip to content

Commit

Permalink
Properly handle several Klap Protocol edge cases
Browse files Browse the repository at this point in the history
Fixes #140.
  • Loading branch information
mihai-dinculescu committed Dec 20, 2023
1 parent 0380952 commit a87a062
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 18 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,20 @@ file. This change log follows the conventions of

- The `anyhow::anyhow!("Local hash does not match server hash")` error has been replaced with the more specific `tapo::TapoResponseError::InvalidCredentials` error.

### Fixed

- A handful of edge cases around the Klap Protocol that were causing panics have been fixed to return `tapo::TapoResponseError::SessionTimeout` or `tapo::TapoResponseError::InvalidResponse` errors instead.

## [Python Unreleased][Unreleased]

### Changed

- The `anyhow::anyhow!("Local hash does not match server hash")` error has been replaced with the more specific `tapo::TapoResponseError::InvalidCredentials` error.

### Fixed

- A handful of edge cases around the Klap Protocol that were causing panics have been fixed to return `tapo::TapoResponseError::SessionTimeout` or `tapo::TapoResponseError::InvalidResponse` errors instead.

## [Rust v0.7.6][v0.7.6] - 2023-11-25

### Added
Expand Down
46 changes: 28 additions & 18 deletions tapo/src/api/protocol/klap_protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,24 @@ impl TapoProtocolExt for KlapProtocol {
.body(payload)
.map_err(isahc::Error::from)?;

let response = self
.client
.send_async(request)
.await?
.bytes()
.await
.map_err(anyhow::Error::from)?;

let response_decrypted = cipher.decrypt(seq, response)?;
let mut response = self.client.send_async(request).await?;

if !response.status().is_success() {
warn!("Response error: {}", response.status());

let error = match response.status() {
isahc::http::StatusCode::UNAUTHORIZED | isahc::http::StatusCode::FORBIDDEN => {
TapoResponseError::SessionTimeout
}
_ => TapoResponseError::InvalidResponse,
};

return Err(Error::Tapo(error));
}

let response_body = response.bytes().await.map_err(anyhow::Error::from)?;

let response_decrypted = cipher.decrypt(seq, response_body)?;
debug!("Device responded with: {response_decrypted:?}");

let inner_response: TapoResponse<R> = serde_json::from_str(&response_decrypted)?;
Expand Down Expand Up @@ -144,16 +153,16 @@ impl KlapProtocol {
.body(local_seed)
.map_err(isahc::Error::from)?;

let response = self
.client
.send_async(request)
.await?
.bytes()
.await
.map_err(anyhow::Error::from)?;
let mut response = self.client.send_async(request).await?;

if !response.status().is_success() {
warn!("Handshake1 error: {}", response.status());
return Err(Error::Tapo(TapoResponseError::InvalidResponse));
}

let (remote_seed, server_hash) = response.split_at(16);
let response_body = response.bytes().await.map_err(anyhow::Error::from)?;

let (remote_seed, server_hash) = response_body.split_at(16);
let local_hash = KlapCipher::sha256(&[local_seed, remote_seed, auth_hash].concat());

if local_hash != server_hash {
Expand Down Expand Up @@ -186,7 +195,8 @@ impl KlapProtocol {
let response = self.client.send_async(request).await?;

if !response.status().is_success() {
return Err(anyhow::anyhow!("").into());
warn!("Handshake2 error: {}", response.status());
return Err(Error::Tapo(TapoResponseError::InvalidResponse));
}

debug!("Handshake2 OK");
Expand Down
2 changes: 2 additions & 0 deletions tapo/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
pub enum TapoResponseError {
/// Invalid request.
InvalidRequest,
/// Invalid response.
InvalidResponse,
/// Malformed request.
MalformedRequest,
/// Invalid public key.
Expand Down

0 comments on commit a87a062

Please sign in to comment.