Skip to content

Commit 63310d3

Browse files
committed
Updates following review
1 parent 90bf240 commit 63310d3

File tree

7 files changed

+164
-167
lines changed

7 files changed

+164
-167
lines changed

kasa/aestransport.py

Lines changed: 60 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,11 @@
44
under compatible GNU GPL3 license.
55
"""
66

7-
import asyncio
87
import base64
98
import hashlib
109
import logging
1110
import time
12-
from typing import Optional
11+
from typing import Optional, Union
1312

1413
import httpx
1514
from cryptography.hazmat.primitives import padding, serialization
@@ -56,60 +55,51 @@ def __init__(
5655
) -> None:
5756
super().__init__(host=host)
5857

59-
self.credentials = (
60-
credentials
61-
if credentials and credentials.username and credentials.password
62-
else Credentials(username="", password="")
63-
)
64-
65-
self._local_seed: Optional[bytes] = None
66-
self.kasa_setup_auth_hash = None
67-
self.blank_auth_hash = None
68-
self.handshake_lock = asyncio.Lock()
58+
self._credentials = credentials or Credentials(username="", password="")
6959

70-
self.handshake_done = False
60+
self._handshake_done = False
7161

72-
self.encryption_session: Optional[AesEncyptionSession] = None
73-
self.session_expire_at: Optional[float] = None
62+
self._encryption_session: Optional[AesEncyptionSession] = None
63+
self._session_expire_at: Optional[float] = None
7464

75-
self.timeout = timeout if timeout else self.DEFAULT_TIMEOUT
76-
self.session_cookie = None
65+
self._timeout = timeout if timeout else self.DEFAULT_TIMEOUT
66+
self._session_cookie = None
7767

78-
self.http_client: httpx.AsyncClient = httpx.AsyncClient()
79-
self.login_token = None
68+
self._http_client: httpx.AsyncClient = httpx.AsyncClient()
69+
self._login_token = None
8070

8171
_LOGGER.debug("Created AES object for %s", self.host)
8272

83-
def hash_credentials(self, credentials, try_login_version2):
73+
def hash_credentials(self, login_v2):
8474
"""Hash the credentials."""
85-
if try_login_version2:
75+
if login_v2:
8676
un = base64.b64encode(
87-
_sha1(credentials.username.encode()).encode()
77+
_sha1(self._credentials.username.encode()).encode()
8878
).decode()
8979
pw = base64.b64encode(
90-
_sha1(credentials.password.encode()).encode()
80+
_sha1(self._credentials.password.encode()).encode()
9181
).decode()
9282
else:
9383
un = base64.b64encode(
94-
_sha1(credentials.username.encode()).encode()
84+
_sha1(self._credentials.username.encode()).encode()
9585
).decode()
96-
pw = base64.b64encode(credentials.password.encode()).decode()
86+
pw = base64.b64encode(self._credentials.password.encode()).decode()
9787
return un, pw
9888

9989
async def client_post(self, url, params=None, data=None, json=None, headers=None):
10090
"""Send an http post request to the device."""
10191
response_data = None
10292
cookies = None
103-
if self.session_cookie:
93+
if self._session_cookie:
10494
cookies = httpx.Cookies()
105-
cookies.set(self.SESSION_COOKIE_NAME, self.session_cookie)
106-
self.http_client.cookies.clear()
107-
resp = await self.http_client.post(
95+
cookies.set(self.SESSION_COOKIE_NAME, self._session_cookie)
96+
self._http_client.cookies.clear()
97+
resp = await self._http_client.post(
10898
url,
10999
params=params,
110100
data=data,
111101
json=json,
112-
timeout=self.timeout,
102+
timeout=self._timeout,
113103
cookies=cookies,
114104
headers=self.COMMON_HEADERS,
115105
)
@@ -121,63 +111,67 @@ async def client_post(self, url, params=None, data=None, json=None, headers=None
121111
async def send_secure_passthrough(self, request: str):
122112
"""Send encrypted message as passthrough."""
123113
url = f"http://{self.host}/app"
124-
if self.login_token:
125-
url += f"?token={self.login_token}"
114+
if self._login_token:
115+
url += f"?token={self._login_token}"
126116

127-
encrypted_payload = self.encryption_session.encrypt(request.encode()) # type: ignore
117+
encrypted_payload = self._encryption_session.encrypt(request.encode()) # type: ignore
128118
passthrough_request = {
129119
"method": "securePassthrough",
130120
"params": {"request": encrypted_payload.decode()},
131121
}
132122
status_code, resp_dict = await self.client_post(url, json=passthrough_request)
133123
_LOGGER.debug(f"secure_passthrough response is {status_code}: {resp_dict}")
134124
if status_code == 200 and resp_dict["error_code"] == 0:
135-
response = self.encryption_session.decrypt( # type: ignore
125+
response = self._encryption_session.decrypt( # type: ignore
136126
resp_dict["result"]["response"].encode()
137127
)
138128
_LOGGER.debug(f"decrypted secure_passthrough response is {response}")
139129
resp_dict = json_loads(response)
140130
return resp_dict
141131
else:
142-
self.handshake_done = False
143-
self.login_token = None
132+
self._handshake_done = False
133+
self._login_token = None
144134
raise AuthenticationException("Could not complete send")
145135

146-
async def perform_login(self, login_request, login_v2):
136+
async def perform_login(self, login_request: Union[str, dict], *, login_v2: bool):
147137
"""Login to the device."""
148-
self.login_token = None
138+
self._login_token = None
149139

150140
if isinstance(login_request, str):
151-
login_request = json_loads(login_request)
141+
login_request_dict: dict = json_loads(login_request)
142+
else:
143+
login_request_dict = login_request
152144

153-
un, pw = self.hash_credentials(self.credentials, login_v2)
154-
login_request["params"] = {"password": pw, "username": un}
155-
request = json_dumps(login_request)
145+
un, pw = self.hash_credentials(login_v2)
146+
login_request_dict["params"] = {"password": pw, "username": un}
147+
request = json_dumps(login_request_dict)
156148
try:
157149
resp_dict = await self.send_secure_passthrough(request)
158150
except SmartDeviceException as ex:
159151
raise AuthenticationException(ex) from ex
160-
self.login_token = resp_dict["result"]["token"]
152+
self._login_token = resp_dict["result"]["token"]
161153

154+
@property
162155
def needs_login(self) -> bool:
163156
"""Return true if the transport needs to do a login."""
164-
return self.login_token is None
157+
return self._login_token is None
165158

166159
async def login(self, request: str) -> None:
167160
"""Login to the device."""
168161
try:
169-
if self.needs_handshake():
162+
if self.needs_handshake:
170163
raise SmartDeviceException(
171164
"Handshake must be complete before trying to login"
172165
)
173-
await self.perform_login(request, False)
166+
await self.perform_login(request, login_v2=False)
174167
except AuthenticationException:
175168
await self.perform_handshake()
176-
await self.perform_login(request, True)
169+
await self.perform_login(request, login_v2=True)
177170

171+
@property
178172
def needs_handshake(self) -> bool:
179173
"""Return true if the transport needs to do a handshake."""
180-
return not self.handshake_done or self.handshake_session_expired()
174+
return not self._handshake_done or self._handshake_session_expired()
181175

182176
async def handshake(self) -> None:
183177
"""Perform the encryption handshake."""
@@ -188,9 +182,9 @@ async def perform_handshake(self):
188182
_LOGGER.debug("Will perform handshaking...")
189183
_LOGGER.debug("Generating keypair")
190184

191-
self.handshake_done = False
192-
self.session_expire_at = None
193-
self.session_cookie = None
185+
self._handshake_done = False
186+
self._session_expire_at = None
187+
self._session_cookie = None
194188

195189
url = f"http://{self.host}/app"
196190
key_pair = KeyPair.create_key_pair()
@@ -215,54 +209,55 @@ async def perform_handshake(self):
215209
_LOGGER.debug("Decoding handshake key...")
216210
handshake_key = resp_dict["result"]["key"]
217211

218-
self.session_cookie = self.http_client.cookies.get( # type: ignore
212+
self._session_cookie = self._http_client.cookies.get( # type: ignore
219213
self.SESSION_COOKIE_NAME
220214
)
221-
if not self.session_cookie:
222-
self.session_cookie = self.http_client.cookies.get( # type: ignore
215+
if not self._session_cookie:
216+
self._session_cookie = self._http_client.cookies.get( # type: ignore
223217
"SESSIONID"
224218
)
225219

226-
self.session_expire_at = time.time() + 86400
227-
self.encryption_session = AesEncyptionSession.create_from_keypair(
220+
self._session_expire_at = time.time() + 86400
221+
self._encryption_session = AesEncyptionSession.create_from_keypair(
228222
handshake_key, key_pair
229223
)
230224

231-
self.handshake_done = True
225+
self._handshake_done = True
232226

233227
_LOGGER.debug("Handshake with %s complete", self.host)
234228

235229
else:
236230
raise AuthenticationException("Could not complete handshake")
237231

238-
def handshake_session_expired(self):
232+
def _handshake_session_expired(self):
239233
"""Return true if session has expired."""
240234
return (
241-
self.session_expire_at is None or self.session_expire_at - time.time() <= 0
235+
self._session_expire_at is None
236+
or self._session_expire_at - time.time() <= 0
242237
)
243238

244239
async def send(self, request: str):
245240
"""Send the request."""
246-
if self.needs_handshake():
241+
if self.needs_handshake:
247242
raise SmartDeviceException(
248243
"Handshake must be complete before trying to send"
249244
)
250-
if self.needs_login():
245+
if self.needs_login:
251246
raise SmartDeviceException("Login must be complete before trying to send")
252247

253248
resp_dict = await self.send_secure_passthrough(request)
254249
if resp_dict["error_code"] != 0:
255-
self.handshake_done = False
256-
self.login_token = None
250+
self._handshake_done = False
251+
self._login_token = None
257252
raise SmartDeviceException(
258253
f"Could not complete send, response was {resp_dict}",
259254
)
260255
return resp_dict
261256

262257
async def close(self) -> None:
263258
"""Close the protocol."""
264-
client = self.http_client
265-
self.http_client = None
259+
client = self._http_client
260+
self._http_client = None
266261
if client:
267262
await client.aclose()
268263

kasa/iotprotocol.py

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,24 +29,22 @@ def __init__(
2929
) -> None:
3030
super().__init__(host=host, port=self.DEFAULT_PORT)
3131

32-
self.credentials: Credentials = (
33-
credentials
34-
if credentials and credentials.username and credentials.password
35-
else Credentials(username="", password="")
32+
self._credentials: Credentials = credentials or Credentials(
33+
username="", password=""
3634
)
37-
self.transport: BaseTransport = transport or KlapTransport(
38-
host, credentials=self.credentials, timeout=timeout
35+
self._transport: BaseTransport = transport or KlapTransport(
36+
host, credentials=self._credentials, timeout=timeout
3937
)
4038

41-
self.query_lock = asyncio.Lock()
39+
self._query_lock = asyncio.Lock()
4240

4341
async def query(self, request: Union[str, Dict], retry_count: int = 3) -> Dict:
4442
"""Query the device retrying for retry_count on failure."""
4543
if isinstance(request, dict):
4644
request = json_dumps(request)
4745
assert isinstance(request, str) # noqa: S101
4846

49-
async with self.query_lock:
47+
async with self._query_lock:
5048
return await self._query(request, retry_count)
5149

5250
async def _query(self, request: str, retry_count: int = 3) -> Dict:
@@ -87,16 +85,16 @@ async def _query(self, request: str, retry_count: int = 3) -> Dict:
8785
raise SmartDeviceException("Query reached somehow to unreachable")
8886

8987
async def _execute_query(self, request: str, retry_count: int) -> Dict:
90-
if self.transport.needs_handshake():
91-
await self.transport.handshake()
88+
if self._transport.needs_handshake:
89+
await self._transport.handshake()
9290

93-
if self.transport.needs_login(): # This shouln't happen
91+
if self._transport.needs_login: # This shouln't happen
9492
raise SmartDeviceException(
9593
"IOT Protocol needs to login to transport but is not login aware"
9694
)
9795

98-
return await self.transport.send(request)
96+
return await self._transport.send(request)
9997

10098
async def close(self) -> None:
10199
"""Close the protocol."""
102-
await self.transport.close()
100+
await self._transport.close()

0 commit comments

Comments
 (0)