pyezvizapi 1.0.1.6__py3-none-any.whl → 1.0.1.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pyezvizapi might be problematic. Click here for more details.

pyezvizapi/cas.py CHANGED
@@ -1,46 +1,50 @@
1
1
  """pyezvizapi CAS API Functions."""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  from io import BytesIO
4
6
  from itertools import cycle
7
+ import logging
5
8
  import random
6
9
  import socket
7
10
  import ssl
11
+ from typing import Any, cast
8
12
 
9
13
  from Crypto.Cipher import AES
10
14
  import xmltodict
11
15
 
12
16
  from .constants import FEATURE_CODE, XOR_KEY
13
- from .exceptions import InvalidHost
17
+ from .exceptions import InvalidHost, PyEzvizError
18
+
19
+ _LOGGER = logging.getLogger(__name__)
14
20
 
15
21
 
16
- def xor_enc_dec(msg, xor_key=XOR_KEY):
17
- """Xor encodes camera serial."""
22
+ def xor_enc_dec(msg: bytes, xor_key: bytes = XOR_KEY) -> bytes:
23
+ """XOR encode/decode bytes with the given key."""
18
24
  with BytesIO(msg) as stream:
19
- xor_msg = bytes(a ^ b for a, b in zip(stream.read(), cycle(xor_key)))
20
- return xor_msg
25
+ return bytes(a ^ b for a, b in zip(stream.read(), cycle(xor_key)))
21
26
 
22
27
 
23
28
  class EzvizCAS:
24
29
  """Ezviz CAS server client."""
25
30
 
26
- def __init__(self, token) -> None:
31
+ def __init__(self, token: dict[str, Any] | None) -> None:
27
32
  """Initialize the client object."""
28
33
  self._session = None
29
- self._token = token or {
34
+ self._token: dict[str, Any] = token or {
30
35
  "session_id": None,
31
36
  "rf_session_id": None,
32
37
  "username": None,
33
38
  "api_url": "apiieu.ezvizlife.com",
34
39
  }
35
- self._service_urls = token["service_urls"]
36
-
37
- def cas_get_encryption(self, devserial):
38
- """Fetch encryption code from ezviz cas server."""
40
+ if not token or "service_urls" not in token:
41
+ raise PyEzvizError("Missing service_urls in token; call EzvizClient.login() first")
42
+ self._service_urls: dict[str, Any] = token["service_urls"]
39
43
 
44
+ def cas_get_encryption(self, devserial: str) -> dict[str, Any]:
45
+ """Fetch encryption code from EZVIZ CAS server."""
40
46
  # Random hex 64 characters long.
41
- rand_hex = random.randrange(10**80)
42
- rand_hex = "%064x" % rand_hex
43
- rand_hex = rand_hex[:64]
47
+ rand_hex_str = f"{random.randrange(10**80):064x}"[:64]
44
48
 
45
49
  payload = (
46
50
  f"\x9e\xba\xac\xe9\x01\x00\x00\x00\x00\x00"
@@ -55,18 +59,17 @@ class EzvizCAS:
55
59
  f"\n\t<ClientType>0</ClientType>\n</Request>\n"
56
60
  ).encode("latin1")
57
61
 
58
- payload_end_padding = rand_hex.encode("latin1")
62
+ payload_end_padding = rand_hex_str.encode("latin1")
59
63
 
60
64
  context = ssl.SSLContext(ssl.PROTOCOL_TLS)
61
-
62
65
  context.set_ciphers(
63
66
  "DEFAULT:!aNULL:!eNULL:!MD5:!3DES:!DES:!RC4:!IDEA:!SEED:!aDSS:!SRP:!PSK"
64
67
  )
65
68
 
66
69
  # Create a TCP/IP socket
67
- my_socket = socket.create_connection(
68
- (self._service_urls["sysConf"][15], self._service_urls["sysConf"][16])
69
- )
70
+ host = cast(str, self._service_urls["sysConf"][15])
71
+ port = cast(int, self._service_urls["sysConf"][16])
72
+ my_socket = socket.create_connection((host, port))
70
73
  my_socket = context.wrap_socket(
71
74
  my_socket, server_hostname=self._service_urls["sysConf"][15]
72
75
  )
@@ -74,29 +77,22 @@ class EzvizCAS:
74
77
  # Get CAS Encryption Key
75
78
  try:
76
79
  my_socket.send(payload + payload_end_padding)
77
- response = my_socket.recv(1024)
78
- print(f"Get Encryption Key: {response}")
79
-
80
+ response_bytes = my_socket.recv(1024)
81
+ _LOGGER.debug("Get Encryption Key: %r", response_bytes)
80
82
  except (socket.gaierror, ConnectionRefusedError) as err:
81
83
  raise InvalidHost("Invalid IP or Hostname") from err
82
-
83
84
  finally:
84
85
  my_socket.close()
85
86
 
86
87
  # Trim header, tail and convert xml to dict.
87
- response = response[32::]
88
- response = response[:-32:]
89
- response = xmltodict.parse(response)
88
+ body = response_bytes[32:-32]
89
+ doc = xmltodict.parse(body)
90
+ return cast(dict[str, Any], doc)
90
91
 
91
- return response
92
-
93
- def set_camera_defence_state(self, serial, enable=1):
92
+ def set_camera_defence_state(self, serial: str, enable: int = 1) -> bool:
94
93
  """Enable alarm notifications."""
95
-
96
94
  # Random hex 64 characters long.
97
- rand_hex = random.randrange(10**80)
98
- rand_hex = "%064x" % rand_hex
99
- rand_hex = rand_hex[:64]
95
+ rand_hex_str = f"{random.randrange(10**80):064x}"[:64]
100
96
 
101
97
  payload = (
102
98
  f"\x9e\xba\xac\xe9\x01\x00\x00\x00\x00\x00"
@@ -114,7 +110,7 @@ class EzvizCAS:
114
110
  f"\x00\x00\x00\xb0\x00\x00\x00\x00"
115
111
  ).encode("latin1")
116
112
 
117
- payload_end_padding = rand_hex.encode("latin1")
113
+ payload_end_padding = rand_hex_str.encode("latin1")
118
114
 
119
115
  # xor camera serial
120
116
  xor_cam_serial = xor_enc_dec(serial.encode("latin1"))
@@ -130,15 +126,14 @@ class EzvizCAS:
130
126
  ).encode("latin1")
131
127
 
132
128
  context = ssl.SSLContext(ssl.PROTOCOL_TLS)
133
-
134
129
  context.set_ciphers(
135
130
  "DEFAULT:!aNULL:!eNULL:!MD5:!3DES:!DES:!RC4:!IDEA:!SEED:!aDSS:!SRP:!PSK"
136
131
  )
137
132
 
138
133
  # Create a TCP/IP socket
139
- my_socket = socket.create_connection(
140
- (self._service_urls["sysConf"][15], self._service_urls["sysConf"][16])
141
- )
134
+ host = cast(str, self._service_urls["sysConf"][15])
135
+ port = cast(int, self._service_urls["sysConf"][16])
136
+ my_socket = socket.create_connection((host, port))
142
137
  my_socket = context.wrap_socket(
143
138
  my_socket, server_hostname=self._service_urls["sysConf"][15]
144
139
  )
@@ -156,13 +151,11 @@ class EzvizCAS:
156
151
  cipher = AES.new(aes_key, AES.MODE_CBC, iv_value)
157
152
 
158
153
  try:
159
- defence_msg_string = cipher.encrypt(defence_msg_string)
160
- my_socket.send(payload + defence_msg_string + payload_end_padding)
161
- print(f"Set camera response: {my_socket.recv()}")
162
-
154
+ enc_bytes = cipher.encrypt(defence_msg_string)
155
+ my_socket.send(payload + enc_bytes + payload_end_padding)
156
+ _LOGGER.debug("Set camera response: %r", my_socket.recv())
163
157
  except (socket.gaierror, ConnectionRefusedError) as err:
164
158
  raise InvalidHost("Invalid IP or Hostname") from err
165
-
166
159
  finally:
167
160
  my_socket.close()
168
161