Skip to content

Commit

Permalink
Add defensive checks against unexpected schedule responses (#305)
Browse files Browse the repository at this point in the history
Add defensive checks against unexpected schedule responses. I do not yet
know the valid version of responses for this command, so for now it
seems best to just check for out of bounds messages and assume that the
schedules are not supported. This can be revisited as we capture traces
for these devices.

For home-assistant/home-assistant.io#29766
  • Loading branch information
allenporter committed Nov 21, 2023
1 parent 1f926a4 commit c333124
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 1 deletion.
8 changes: 8 additions & 0 deletions pyrainbird/rainbird.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ def decode_schedule(data: str, cmd_template: dict[str, Any]) -> dict[str, Any]:
subcommand = int(data[4:6], 16)
rest = data[6:]
if subcommand == 0:
if len(rest) < 8:
return {}
# Delay, Snooze, Rainsensor
return {
"controllerInfo": {
Expand All @@ -46,6 +48,8 @@ def decode_schedule(data: str, cmd_template: dict[str, Any]) -> dict[str, Any]:
}

if subcommand & 16 == 16:
if len(rest) < 10:
return {}
program = subcommand & ~16
fields = list(int(rest[i : i + 2], 16) for i in range(0, len(rest), 2))
return {
Expand All @@ -61,6 +65,8 @@ def decode_schedule(data: str, cmd_template: dict[str, Any]) -> dict[str, Any]:
}

if subcommand & 96 == 96:
if len(rest) < 4:
return {}
program = subcommand & ~96
# Note: 65535 is disabled
entries = list(int(rest[i : i + 4], 16) for i in range(0, len(rest), 4))
Expand All @@ -72,6 +78,8 @@ def decode_schedule(data: str, cmd_template: dict[str, Any]) -> dict[str, Any]:
}

if subcommand & 128 == 128:
if len(rest) < 4:
return {}
station = subcommand & ~128
rest = bytes(data[6:], "utf-8")
durations = list(int(rest[i : i + 4], 16) for i in range(0, len(rest), 4))
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[metadata]
name = pyrainbird
version = 4.0.0
version = 4.0.1
description = Rain Bird Controller
long_description = file: README.md
long_description_content_type = text/markdown
Expand Down
40 changes: 40 additions & 0 deletions tests/test_async_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1271,3 +1271,43 @@ async def test_custom_schedule_in_past(
datetime.datetime(2023, 1, 30, 4, 0, 0),
datetime.datetime(2023, 2, 5, 4, 0, 0),
]



@freeze_time("2023-01-25 20:00:00")
async def test_get_schedule_parse_failure(
rainbird_controller: Callable[[], Awaitable[AsyncRainbirdController]],
api_response: Callable[[...], Awaitable[None]],
sip_data_responses: Callable[[list[str]], None],
) -> None:
"""Test a schedule that fails to parse."""
controller = await rainbird_controller()

api_response("82", modelID=0x0A, protocolRevisionMajor=1, protocolRevisionMinor=3)
api_response("83", pageNumber=1, setStations=0x1F000000) # 5 stations
sip_data_responses(
[
"A0000080",
"A00010",
"A00011",
"A00012",
"A00060",
"A00061",
"A00062",
"A00080",
"A00081",
"A00082",
"A00083",
"A00084",
"A00085",
"A00086",
"A00087",
"A00088",
"A00089",
"A00080",
"A0008A",
]
)

schedule = await controller.get_schedule()
assert len(schedule.programs) == 0

0 comments on commit c333124

Please sign in to comment.