Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
yozik04 committed Nov 28, 2023
2 parents 6f18e46 + 719368d commit c2af06c
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 67 deletions.
88 changes: 61 additions & 27 deletions paradox/console_scripts/ip150_connection_decrypt.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,32 @@
#!/usr/bin/env python3
import argparse
import binascii
import traceback
from collections import OrderedDict
import re
import traceback

import yaml

from paradox.connections.ip.parsers import (IPMessageCommand, IPMessageRequest,
IPMessageResponse, IPMessageType,
IPPayloadConnectResponse)
from paradox.connections.ip.parsers import (
IPMessageCommand,
IPMessageRequest,
IPMessageResponse,
IPMessageType,
IPPayloadConnectResponse,
)
from paradox.hardware import create_panel
from paradox.hardware.parsers import InitiateCommunicationResponse


class Colors: # You may need to change color settings
RED = '\033[31m'
ENDC = '\033[m'
GREEN = '\033[32m'
YELLOW = '\033[33m'
BLUE = '\033[34m'
MAGENTA = '\033[95m'
CYAN = '\033[96m'
ON_WHITE = '\033[47m'
class Colors: # You may need to change color settings
RED = "\033[31m"
ENDC = "\033[m"
GREEN = "\033[32m"
YELLOW = "\033[33m"
BLUE = "\033[34m"
MAGENTA = "\033[95m"
CYAN = "\033[96m"
ON_WHITE = "\033[30m\033[47m"


def ordered_load(stream, Loader=yaml.loader.SafeLoader, object_pairs_hook=OrderedDict):
Expand Down Expand Up @@ -54,14 +59,22 @@ def parse(self, parsed):
elif message_type == IPMessageType.ip_response:
self._parse_ip_response(parsed)
except Exception:
print(f"{Colors.RED}Failed to parse message: \n{parsed}{Colors.ENDC}")
traceback.print_exc()

def _parse_ip_response(self, parsed):
if parsed.header.command == IPMessageCommand.connect and parsed.header.sub_command == 0:
print(f"{Colors.ON_WHITE}{IPPayloadConnectResponse.parse(parsed.payload)}{Colors.ENDC}")
if (
parsed.header.command == IPMessageCommand.connect
and parsed.header.sub_command == 0
):
print(
f"{Colors.ON_WHITE}{IPPayloadConnectResponse.parse(parsed.payload)}{Colors.ENDC}"
)
if len(parsed.payload) > 25:
p = parsed.payload[25:62]
print(f"{Colors.ON_WHITE}InitiateCommunicationResponse:\n{InitiateCommunicationResponse.parse(p)}{Colors.ENDC}")
print(
f"{Colors.ON_WHITE}InitiateCommunicationResponse:\n{InitiateCommunicationResponse.parse(p)}{Colors.ENDC}"
)
elif parsed.header.command == IPMessageCommand.multicommand:
self._parse_multicommand(parsed, "frompanel")
else:
Expand All @@ -74,7 +87,7 @@ def _parse_multicommand(self, parsed, direction):
self._parse_generic_multicommand(parsed, direction)
elif parsed.header.sub_command == 0x1: # MGSP multiread
self._parse_generic_mgsp_multiread(parsed, direction)
elif parsed.header.sub_command == 0xdd: # Unified multiread
elif parsed.header.sub_command == 0xDD: # Unified multiread
pass

def _parse_serial_passthrough_response(self, parsed):
Expand All @@ -95,7 +108,9 @@ def _parse_serial_passthrough_eeprom_read(self, parsed_payload):
ram_address = parsed_payload.fields.value.address
ram_parser = self.panel.get_message("RAMDataParserMap").get(ram_address)
if ram_parser is not None:
print(f"{Colors.ON_WHITE}{ram_parser.parse(parsed_payload.fields.value.data)}{Colors.ENDC}")
print(
f"{Colors.ON_WHITE}{ram_parser.parse(parsed_payload.fields.value.data)}{Colors.ENDC}"
)
else:
print(
f"{Colors.RED}No parser for {ram_address} ram address, data: {binascii.hexlify(parsed_payload.fields.value.data)}{Colors.ENDC}"
Expand All @@ -105,14 +120,16 @@ def _parse_ip_request(self, parsed):
if parsed.header.command == IPMessageCommand.multicommand:
self._parse_multicommand(parsed, "topanel")
else:
print(f"{Colors.RED}No parser for ip_request payload: {binascii.hexlify(parsed.payload)}{Colors.ENDC}")
print(
f"{Colors.RED}No parser for ip_request payload: {binascii.hexlify(parsed.payload)}{Colors.ENDC}"
)

def _parse_generic_multicommand(self, parsed, direction: str):
i = 0
while i < len(parsed.payload):
cmd_len = parsed.payload[i]
i += 1
cmd = parsed.payload[i:i + cmd_len]
cmd = parsed.payload[i : i + cmd_len]
assert len(cmd) == cmd_len
i += cmd_len
print(f"{Colors.ON_WHITE}Multicommand: {cmd}{Colors.ENDC}")
Expand All @@ -130,19 +147,37 @@ def _parse_message(self, message, direction):
return parsed_payload

def _parse_generic_mgsp_multiread(self, parsed, direction):
print(
f"{Colors.RED}No parser for {direction} mgsp_multiread{Colors.ENDC}"
)
print(f"{Colors.RED}No parser for {direction} mgsp_multiread{Colors.ENDC}")


def old_yaml_format_traverser(data):
re_key = re.compile(r"peer(\d+)_(\d+)")
for key, value in data.items():
# example peer0_0, where 0 is peer number and 0 is index number
matches = re_key.match(key)

yield {
"peer": int(matches.group(1)),
"data": value,
"index": int(matches.group(2)),
}


def decrypt_file(file, password, max_packets: int = None):
try:
data = ordered_load(file, yaml.loader.SafeLoader)
parser = PayloadParser()

if "peers" in data and "packets" in data:
iterator = data["packets"]
else:
iterator = old_yaml_format_traverser(data)

n = 0
for key, value in data.items():
if not value[0] == 0xaa:
for packet in iterator:
value = packet["data"]
key = packet["index"]
if not value[0] == 0xAA:
print(f"{Colors.RED}Not an IP packet: {value}{Colors.ENDC}")
continue
header = value[0:16]
Expand All @@ -167,7 +202,6 @@ def decrypt_file(file, password, max_packets: int = None):
if parsed.header.sub_command == 0:
assert password == parsed.payload, "Wrong decryption password"


if (
parsed.header.command == IPMessageCommand.connect
and parsed.header.message_type == IPMessageType.ip_response
Expand All @@ -187,7 +221,7 @@ def decrypt_file(file, password, max_packets: int = None):

print(
f"\tpayload: {binascii.hexlify(parsed.payload)}\n",
f"\tpayload_raw: {parsed.payload}"
f"\tpayload_raw: {parsed.payload}",
)

print(parsed)
Expand Down
1 change: 1 addition & 0 deletions paradox/hardware/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ def create_panel(core, start_communication_response: Container = None) -> Panel:
elif product_id in [
"SPECTRA_SP4000",
"SPECTRA_SP5500",
"SPECTRA_SP550_PLUS",
"SPECTRA_SP6000",
"SPECTRA_SP7000",
"SPECTRA_SP65",
Expand Down
1 change: 1 addition & 0 deletions paradox/hardware/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ def PacketChecksum(subcons):
SPECTRA_SP7000=23,
SPECTRA_SP4000=26,
SPECTRA_SP65=27,
SPECTRA_SP550_PLUS=28,
SPECTRA_SP6000_PLUS=29,
SPECTRA_SP7000_PLUS=30,
MAGELLAN_MG5000=64,
Expand Down
68 changes: 40 additions & 28 deletions paradox/hardware/evo/panel.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-

import binascii
import inspect
import logging
Expand All @@ -10,17 +8,26 @@
from paradox.config import config as cfg
from paradox.exceptions import AuthenticationFailed, StatusRequestException

from ..panel import Panel as PanelBase
from . import parsers
from ..panel import Panel as PanelBase
from .event import event_map
from .property import property_map

logger = logging.getLogger("PAI").getChild(__name__)

ZONE_ACTIONS = dict(
bypass={"flags": {"bypassed": True}, "operation": "set",},
clear_bypass={"flags": {"bypassed": True}, "operation": "clear",},
clear_alarm_memory={"flags": {"generated_alarm": True}, "operation": "clear",},
bypass={
"flags": {"bypassed": True},
"operation": "set",
},
clear_bypass={
"flags": {"bypassed": True},
"operation": "clear",
},
clear_alarm_memory={
"flags": {"generated_alarm": True},
"operation": "clear",
},
)


Expand All @@ -33,14 +40,14 @@ class Panel_EVOBase(PanelBase):
def __init__(
self, core, start_communication_response, variable_message_length=True
):
super(Panel_EVOBase, self).__init__(core, variable_message_length)
super().__init__(core, variable_message_length)

self._populate_settings(start_communication_response)

def _populate_settings(self, start_communication_response):
raw_data = (
start_communication_response.fields.data
+ start_communication_response.checksum
start_communication_response.fields.data
+ start_communication_response.checksum
)
parsed = parsers.InitializeCommunication.parse(raw_data)
if cfg.LOGGING_DUMP_MESSAGES:
Expand All @@ -55,7 +62,7 @@ def get_message(self, name: str) -> Construct:
except ResourceWarning:
pass

return super(Panel_EVOBase, self).get_message(name)
return super().get_message(name)

async def dump_memory(self, file, memory_type):
"""
Expand All @@ -73,6 +80,12 @@ async def dump_memory_to_file(self, file, range_, ram=False):
mem_type = "RAM" if ram else "EEPROM"
logger.info("Dump " + mem_type)

def expect(command, address):
return (
lambda m: m.fields.value.po.command == command
and m.fields.value.address == address
)

packet_length = 64 # 64 is max
for address in range_:
args = dict(
Expand All @@ -82,8 +95,7 @@ async def dump_memory_to_file(self, file, range_, ram=False):
reply = await self.core.send_wait(
parsers.ReadEEPROM,
args,
reply_expected=lambda m: m.fields.value.po.command == 0x5
and m.fields.value.address == address,
reply_expected=expect(0x5, address),
)

if reply is None:
Expand All @@ -101,7 +113,7 @@ def parse_message(
if message is None or len(message) == 0:
return None

parent_parsed = super(Panel_EVOBase, self).parse_message(message, direction)
parent_parsed = super().parse_message(message, direction)
if parent_parsed:
return parent_parsed

Expand Down Expand Up @@ -147,13 +159,9 @@ def parse_message(
return parsers.RequestedEvent.parse(message)

except ChecksumError as e:
logger.error(
"ChecksumError %s, message: %s" % (str(e), binascii.hexlify(message))
)
except:
logger.exception(
"Exception parsing message: %s" % (binascii.hexlify(message))
)
logger.error("ChecksumError %s, message: %s", e, binascii.hexlify(message))
except Exception:
logger.exception("Exception parsing message: %s", binascii.hexlify(message))

return None

Expand All @@ -179,13 +187,17 @@ async def initialize_communication(self, password) -> bool:
"(Babyware) Tab."
)
raise AuthenticationFailed("Wrong PASSWORD")
else: # command == 0x1
if reply.fields.value.po.status.Winload_connected:
logger.info("Authentication Success")
return True
else:
logger.error("Authentication Failed")
return False

if reply.fields.value.po.command == 0x1:
logger.info("Authentication Success")

if not reply.fields.value.po.status.Winload_connected:
logger.warning("Winload_connected is still False")

return True

logger.error("Invalid response to InitializeCommunication: %s", reply)
return False

@staticmethod
def _request_status_reply_check(message: Container, address: int):
Expand Down Expand Up @@ -223,7 +235,7 @@ async def control_partitions(self, partitions: list, command: str) -> bool:
:param str command: textual command
:return: True if we have at least one success
"""
args = dict(partitions=dict((i, command) for i in partitions))
args = dict(partitions={i: command for i in partitions})

try:
reply = await self.core.send_wait(
Expand Down
20 changes: 8 additions & 12 deletions paradox/interfaces/ip_interface/interface.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-

# IP Interface

import asyncio
Expand Down Expand Up @@ -46,16 +44,14 @@ def start(self):
async def run(self):
try:
self.server = await asyncio.start_server(
self.handle_client, self.addr, self.port, loop=self.alarm.work_loop
self.handle_client, self.addr, self.port
)
logger.info(
"IP Interface: serving on {}".format(
self.server.sockets[0].getsockname()
)
"IP Interface: serving on %s", self.server.sockets[0].getsockname()
)
logger.info("IP Interface started")
except Exception as e:
logger.error("Failed to start IP Interface {}".format(e))
logger.error("Failed to start IP Interface: %s", e)

async def handle_client(self, reader, writer):
"""
Expand All @@ -67,10 +63,10 @@ async def handle_client(self, reader, writer):
"""

self.client_nr = (self.client_nr + 1) % 256
logger.info("Client %d connected" % self.client_nr)
logger.info("Client %d connected", self.client_nr)

connection = ClientConnection(reader, writer, self.alarm, self.key)
handler_name = "%s_%d" % (self.name, self.client_nr)
handler_name = f"{self.name}_{self.client_nr}"
self.alarm.connection.register_raw_handler(
PersistentHandler(connection.handle_panel_raw_message, name=handler_name)
)
Expand All @@ -79,10 +75,10 @@ async def handle_client(self, reader, writer):

try:
await connection.handle()
except:
logger.exception("Client %d connection raised exception" % self.client_nr)
except Exception:
logger.exception("Client %d connection raised exception", self.client_nr)
finally:
self.alarm.connection.deregister_raw_handler(handler_name)

asyncio.get_event_loop().create_task(self.alarm.resume())
logger.info("Client %d disconnected" % self.client_nr)
logger.info("Client %d disconnected", self.client_nr)

0 comments on commit c2af06c

Please sign in to comment.