Skip to content

Commit

Permalink
handle unknown KNXIP service types
Browse files Browse the repository at this point in the history
and moved Unhandled and Unsupported KNXIPFrame logs to xknx.knx
  • Loading branch information
farmio committed Dec 21, 2020
1 parent 1534129 commit c7f9301
Show file tree
Hide file tree
Showing 8 changed files with 64 additions and 11 deletions.
1 change: 1 addition & 0 deletions changelog.md
Expand Up @@ -19,6 +19,7 @@
- CEMIFrame: remove `CEMIFrame.cmd`, which can be derived from `CEMIFrame.payload`.
- Farewell Travis CI; Welcome Github Actions!
- StateUpdater allow float values for `register_remote_value(tracker_options)` attribute.
- Handle exceptions from received unsupported or not implemented KNXIP Service Type identifiers

## 0.15.6 Bugfix for StateUpater 2020-11-26

Expand Down
9 changes: 9 additions & 0 deletions test/knxip_tests/header_test.py
Expand Up @@ -72,3 +72,12 @@ def test_from_knx_wrong_header3(self):
header = KNXIPHeader(xknx)
with self.assertRaises(CouldNotParseKNXIP):
header.from_knx(raw)

def test_from_knx_wrong_header4(self):
"""Test parsing and streaming wrong Header (unsupported service type)."""
# 0x0000 as service type
raw = (0x06, 0x10, 0x00, 0x00, 0x00, 0x0A)
xknx = XKNX()
header = KNXIPHeader(xknx)
with self.assertRaises(CouldNotParseKNXIP):
header.from_knx(raw)
7 changes: 6 additions & 1 deletion test/knxip_tests/knxip_test.py
Expand Up @@ -5,6 +5,7 @@
from xknx import XKNX
from xknx.exceptions import CouldNotParseKNXIP
from xknx.knxip import KNXIPFrame
from xknx.knxip.knxip_enum import KNXIPServiceType


class Test_KNXIP(unittest.TestCase):
Expand All @@ -25,9 +26,13 @@ def test_wrong_init(self):
"""Testing init method with wrong service_type_ident."""
xknx = XKNX()
knxipframe = KNXIPFrame(xknx)
with self.assertRaises(TypeError):
with self.assertRaises(AttributeError):
knxipframe.init(23)

with self.assertRaises(CouldNotParseKNXIP):
# this is not yet implemented in xknx
knxipframe.init(KNXIPServiceType.SEARCH_REQUEST_EXTENDED)

def test_parsing_too_long_knxip(self):
"""Test parsing and streaming connection state request KNX/IP packet."""
raw = (
Expand Down
4 changes: 2 additions & 2 deletions test/str_test.py
Expand Up @@ -536,7 +536,7 @@ def test_header(self):
header.total_length = 42
self.assertEqual(
str(header),
'<KNXIPHeader HeaderLength="6" ProtocolVersion="16" KNXIPServiceType="KNXIPServiceType.ROUTING_INDICATION" Reserve="0" TotalLength="42" '
'<KNXIPHeader HeaderLength="6" ProtocolVersion="16" KNXIPServiceType="ROUTING_INDICATION" Reserve="0" TotalLength="42" '
"/>",
)

Expand Down Expand Up @@ -680,7 +680,7 @@ def test_knxip_frame(self):
knxipframe.init(KNXIPServiceType.SEARCH_REQUEST)
self.assertEqual(
str(knxipframe),
'<KNXIPFrame <KNXIPHeader HeaderLength="6" ProtocolVersion="16" KNXIPServiceType="KNXIPServiceType.SEARCH_REQUEST" Reserve="0" TotalLeng'
'<KNXIPFrame <KNXIPHeader HeaderLength="6" ProtocolVersion="16" KNXIPServiceType="SEARCH_REQUEST" Reserve="0" TotalLeng'
'th="0" />\n'
' body="<SearchRequest discovery_endpoint="<HPAI 224.0.23.12:3671 />" />" />',
)
Expand Down
10 changes: 8 additions & 2 deletions xknx/io/udp_client.py
Expand Up @@ -97,7 +97,11 @@ def data_received_callback(self, raw: bytes):
knx_logger.debug("Received: %s", knxipframe)
self.handle_knxipframe(knxipframe)
except CouldNotParseKNXIP as couldnotparseknxip:
logger.exception(couldnotparseknxip)
knx_logger.debug(
"Unsupported KNXIPFrame: %s in %s",
couldnotparseknxip.description,
raw.hex(),
)

def handle_knxipframe(self, knxipframe: KNXIPFrame) -> None:
"""Handle KNXIP Frame and call all callbacks which watch for the service type ident."""
Expand All @@ -107,7 +111,9 @@ def handle_knxipframe(self, knxipframe: KNXIPFrame) -> None:
callback.callback(knxipframe, self)
handled = True
if not handled:
logger.debug("UNHANDLED: %s", knxipframe.header.service_type_ident)
knx_logger.debug(
"Unhandled %s: %s", knxipframe.header.service_type_ident, knxipframe
)

def register_callback(self, callback, service_types=None) -> Callback:
"""Register callback."""
Expand Down
9 changes: 7 additions & 2 deletions xknx/knxip/header.py
Expand Up @@ -31,7 +31,12 @@ def from_knx(self, data):

self.header_length = data[0]
self.protocol_version = data[1]
self.service_type_ident = KNXIPServiceType(data[2] * 256 + data[3])
try:
self.service_type_ident = KNXIPServiceType(data[2] * 256 + data[3])
except ValueError:
raise CouldNotParseKNXIP(
"KNXIPServiceType unknown: {}".format(hex(data[2] * 256 + data[3]))
)
self.b4_reserve = data[4]
self.total_length = data[5]
return KNXIPHeader.HEADERLENGTH
Expand Down Expand Up @@ -60,7 +65,7 @@ def __str__(self):
'KNXIPServiceType="{}" Reserve="{}" TotalLength="{}" />'.format(
self.header_length,
self.protocol_version,
self.service_type_ident,
self.service_type_ident.name,
self.b4_reserve,
self.total_length,
)
Expand Down
6 changes: 4 additions & 2 deletions xknx/knxip/knxip.py
Expand Up @@ -31,7 +31,7 @@ def __init__(self, xknx):
self.header = KNXIPHeader(xknx)
self.body = None

def init(self, service_type_ident):
def init(self, service_type_ident: KNXIPServiceType) -> None:
"""Init object by service_type_ident. Will instanciate a body object depending on service_type_ident."""
self.header.service_type_ident = service_type_ident

Expand All @@ -58,7 +58,9 @@ def init(self, service_type_ident):
elif service_type_ident == KNXIPServiceType.CONNECTIONSTATE_RESPONSE:
self.body = ConnectionStateResponse(self.xknx)
else:
raise TypeError(self.header.service_type_ident)
raise CouldNotParseKNXIP(
f"KNXIPServiceType not implemented: {service_type_ident.name}"
)

@staticmethod
def init_from_body(knxip_body: KNXIPBody):
Expand Down
29 changes: 27 additions & 2 deletions xknx/knxip/knxip_enum.py
Expand Up @@ -6,6 +6,7 @@
class KNXIPServiceType(Enum):
"""Enum class for KNX/IP Service Types."""

# 0x02 Core services
SEARCH_REQUEST = 0x0201
SEARCH_RESPONSE = 0x0202
DESCRIPTION_REQUEST = 0x0203
Expand All @@ -16,13 +17,37 @@ class KNXIPServiceType(Enum):
CONNECTIONSTATE_RESPONSE = 0x0208
DISCONNECT_REQUEST = 0x0209
DISCONNECT_RESPONSE = 0x020A
SEARCH_REQUEST_EXTENDED = 0x020B
SEARCH_RESPONSE_EXTENDED = 0x020C
# 0x03 Device Management services
DEVICE_CONFIGURATION_REQUEST = 0x0310
DEVICE_CONFIGURATION_ACK = 0x0111
DEVICE_CONFIGURATION_ACK = 0x0311
# 0x04 Tunneling services
TUNNELLING_REQUEST = 0x0420
TUNNELLING_ACK = 0x0421
TUNNELLING_FEATURE_GET = 0x0422
TUNNELLING_FEATURE_RESPONSE = 0x0423
TUNNELLING_FEATURE_SET = 0x0424
TUNNELLING_FEATURE_INFO = 0x0425
# 0x05 Routing services
ROUTING_INDICATION = 0x0530
ROUTING_LOST_MESSAGE = 0x0531
UNKNOWN = 0x0000
ROUTING_BUSY = 0x0532
ROUTING_SYSTEM_BROADCAST = 0x0533
# 0x06 Remote Logging services
# 0x07 Remote Configuration and Diagnosis services
REMOTE_DIAG_REQUEST = 0x0740
REMOTE_DIAG_RESPONSE = 0x0741
REMOTE_CONFIG_REQUEST = 0x0742
REMOTE_RESET_REQUEST = 0x0743
# 0x08 Object Server services
# 0x09 Security services
SECURE_WRAPPER = 0x0950
SECURE_SESSION_REQUEST = 0x0951
SECURE_SESSION_RESPONSE = 0x0952
SECURE_SESSION_AUTHENTICATE = 0x0953
SECURE_SESSION_STATUS = 0x0954
SECURE_TIMER_NOTIFY = 0x0955


class CEMIMessageCode(Enum):
Expand Down

0 comments on commit c7f9301

Please sign in to comment.