Clone pyezviz from https://github.com/BaQs/pyEzviz
This commit is contained in:
+160
@@ -0,0 +1,160 @@
|
||||
"""Ezviz CAS API Functions."""
|
||||
|
||||
import random
|
||||
import socket
|
||||
import ssl
|
||||
from io import BytesIO
|
||||
from itertools import cycle
|
||||
|
||||
import xmltodict
|
||||
from Crypto.Cipher import AES
|
||||
from pyezviz.constants import FEATURE_CODE, XOR_KEY
|
||||
from pyezviz.exceptions import InvalidHost
|
||||
|
||||
|
||||
def xor_enc_dec(msg, xor_key=XOR_KEY):
|
||||
"""Xor encodes camera serial"""
|
||||
with BytesIO(msg) as stream:
|
||||
xor_msg = bytes(a ^ b for a, b in zip(stream.read(), cycle(xor_key)))
|
||||
return xor_msg
|
||||
|
||||
|
||||
class EzvizCAS:
|
||||
"""Ezviz CAS server client."""
|
||||
|
||||
def __init__(self, token):
|
||||
"""Initialize the client object."""
|
||||
self._session = None
|
||||
self._token = token or {
|
||||
"session_id": None,
|
||||
"rf_session_id": None,
|
||||
"username": None,
|
||||
"api_url": "apiieu.ezvizlife.com",
|
||||
}
|
||||
self._service_urls = token["service_urls"]
|
||||
|
||||
def cas_get_encryption(self, devserial):
|
||||
"""Fetch encryption code from ezviz cas server"""
|
||||
|
||||
# Random hex 64 characters long.
|
||||
rand_hex = random.randrange(10 ** 80)
|
||||
rand_hex = "%064x" % rand_hex
|
||||
rand_hex = rand_hex[:64]
|
||||
|
||||
payload = (
|
||||
f"\x9e\xba\xac\xe9\x01\x00\x00\x00\x00\x00"
|
||||
f"\x00\x02" # Check or order?
|
||||
f"\x00\x00\x00\x00\x00\x00 "
|
||||
f"\x01" # Check or order?
|
||||
f"\x00\x00\x00\x00\x00\x00\x02\t\x00\x00\x00\x00"
|
||||
f'<?xml version="1.0" encoding="utf-8"?>\n<Request>\n\t'
|
||||
f'<ClientID>{self._token["session_id"]}</ClientID>'
|
||||
f"\n\t<Sign>{FEATURE_CODE}</Sign>\n\t"
|
||||
f"<DevSerial>{devserial}</DevSerial>"
|
||||
f"\n\t<ClientType>0</ClientType>\n</Request>\n"
|
||||
).encode("latin1")
|
||||
|
||||
payload_end_padding = rand_hex.encode("latin1")
|
||||
|
||||
context = ssl.SSLContext(cert_reqs=ssl.CERT_NONE)
|
||||
|
||||
# Create a TCP/IP socket
|
||||
my_socket = socket.create_connection(
|
||||
(self._service_urls["sysConf"][15], self._service_urls["sysConf"][16])
|
||||
)
|
||||
my_socket = context.wrap_socket(
|
||||
my_socket, server_hostname=self._service_urls["sysConf"][15]
|
||||
)
|
||||
|
||||
# Get CAS Encryption Key
|
||||
try:
|
||||
my_socket.send(payload + payload_end_padding)
|
||||
response = my_socket.recv(1024)
|
||||
print(f"Get Encryption Key: {response}")
|
||||
|
||||
except (socket.gaierror, ConnectionRefusedError) as err:
|
||||
raise InvalidHost("Invalid IP or Hostname") from err
|
||||
|
||||
finally:
|
||||
my_socket.close()
|
||||
|
||||
# Trim header, tail and convert xml to dict.
|
||||
response = response[32::]
|
||||
response = response[:-32:]
|
||||
response = xmltodict.parse(response)
|
||||
|
||||
return response
|
||||
|
||||
def set_camera_defence_state(self, serial, enable=1):
|
||||
"""Enable alarm notifications."""
|
||||
|
||||
# Random hex 64 characters long.
|
||||
rand_hex = random.randrange(10 ** 80)
|
||||
rand_hex = "%064x" % rand_hex
|
||||
rand_hex = rand_hex[:64]
|
||||
|
||||
payload = (
|
||||
f"\x9e\xba\xac\xe9\x01\x00\x00\x00\x00\x00"
|
||||
f"\x00\x14" # Check or order?
|
||||
f"\x00\x00\x00\x00\x00\x00 "
|
||||
f"\x05"
|
||||
f"\x00\x00\x00\x00\x00\x00\x02\xd0\x00\x00\x01\xe0"
|
||||
f'<?xml version="1.0" encoding="utf-8"?>\n<Request>\n\t'
|
||||
f'<Verify ClientSession="{self._token["session_id"]}" '
|
||||
f'ToDevice="{serial}" ClientType="0" />\n\t'
|
||||
f'<Message Length="240" />\n</Request>\n'
|
||||
f"\x9e\xba\xac\xe9\x01\x00\x00\x00\x00\x00"
|
||||
f"\x00\x13"
|
||||
f"\x00\x00\x00\x00\x00\x000\x0f\xff\xff\xff\xff"
|
||||
f"\x00\x00\x00\xb0\x00\x00\x00\x00"
|
||||
).encode("latin1")
|
||||
|
||||
payload_end_padding = rand_hex.encode("latin1")
|
||||
|
||||
# xor camera serial
|
||||
xor_cam_serial = xor_enc_dec(serial.encode("latin1"))
|
||||
|
||||
defence_msg_string = (
|
||||
f'{xor_cam_serial.decode()}2+,*xdv.0" '
|
||||
f'encoding="utf-8"?>\n'
|
||||
f"<Request>\n"
|
||||
f"\t<OperationCode>ABCDEFG</OperationCode>\n"
|
||||
f'\t<Defence Type="Global" Status="{enable}" Actor="V" Channel="0" />\n'
|
||||
f"</Request>\n"
|
||||
f"\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10"
|
||||
).encode("latin1")
|
||||
|
||||
context = ssl.SSLContext(cert_reqs=ssl.CERT_NONE)
|
||||
|
||||
# Create a TCP/IP socket
|
||||
my_socket = socket.create_connection(
|
||||
(self._service_urls["sysConf"][15], self._service_urls["sysConf"][16])
|
||||
)
|
||||
my_socket = context.wrap_socket(
|
||||
my_socket, server_hostname=self._service_urls["sysConf"][15]
|
||||
)
|
||||
|
||||
cas_client = self.cas_get_encryption(serial)
|
||||
|
||||
aes_key = cas_client["Response"]["Session"]["@Key"].encode("latin1")
|
||||
iv_value = (
|
||||
f"{serial}{cas_client['Response']['Session']['@OperationCode']}".encode(
|
||||
"latin1"
|
||||
)
|
||||
)
|
||||
|
||||
# Message encryption
|
||||
cipher = AES.new(aes_key, AES.MODE_CBC, iv_value)
|
||||
|
||||
try:
|
||||
defence_msg_string = cipher.encrypt(defence_msg_string)
|
||||
my_socket.send(payload + defence_msg_string + payload_end_padding)
|
||||
print(f"Set camera response: {my_socket.recv()}")
|
||||
|
||||
except (socket.gaierror, ConnectionRefusedError) as err:
|
||||
raise InvalidHost("Invalid IP or Hostname") from err
|
||||
|
||||
finally:
|
||||
my_socket.close()
|
||||
|
||||
return True
|
||||
Reference in New Issue
Block a user