Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Complete hap_protocol coverage #332

Merged
merged 1 commit into from
Mar 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 18 additions & 9 deletions pyhap/hap_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,12 @@ def send_response(self, response: HAPResponse) -> None:
+ self.conn.send(h11.Data(data=response.body))
+ self.conn.send(h11.EndOfMessage())
)
self.transport.resume_reading()

def finish_and_close(self):
"""Cleanly finish and close the connection."""
self.conn.send(h11.ConnectionClosed())
self.close()

def check_idle(self, now) -> None:
"""Abort when do not get any data within the timeout."""
Expand Down Expand Up @@ -141,19 +147,18 @@ def data_received(self, data: bytes) -> None:
self.conn.receive_data(data)
logger.debug("%s: Recv unencrypted: %s", self.peername, data)

while self._process_one_event():
pass
try:
while self._process_one_event():
if self.conn.our_state is h11.MUST_CLOSE:
self.finish_and_close()
except h11.ProtocolError as protocol_ex:
self._handle_invalid_conn_state(protocol_ex)

def _process_one_event(self) -> bool:
"""Process one http event."""
if self.conn.our_state is h11.MUST_CLOSE:
return self._handle_invalid_conn_state("connection state is must close")

event = self.conn.next_event()

logger.debug("%s: h11 Event: %s", self.peername, event)

if event is h11.NEED_DATA:
if event in (h11.NEED_DATA, h11.ConnectionClosed):
return False

if event is h11.PAUSED:
Expand All @@ -170,6 +175,7 @@ def _process_one_event(self) -> bool:
return True

if isinstance(event, h11.EndOfMessage):
self.transport.pause_reading()
response = self.handler.dispatch(self.request, bytes(self.request_body))
self._process_response(response)
self.request = None
Expand Down Expand Up @@ -203,7 +209,10 @@ def _handle_response_ready(self, task: asyncio.Task) -> None:
self.response = None
try:
response.body = task.result()
except Exception: # pylint: disable=broad-except
except Exception as ex: # pylint: disable=broad-except
logger.debug(
"%s: exception during delayed response", self.peername, exc_info=ex
)
response = self.handler.generic_failure_response()
self.send_response(response)

Expand Down
51 changes: 51 additions & 0 deletions tests/test_hap_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,57 @@ def test_http10_close(driver):
hap_proto.close()


def test_invalid_content_length(driver):
"""Test we handle invalid content length."""
loop = MagicMock()
transport = MagicMock()
connections = {}
driver.add_accessory(Accessory(driver, "TestAcc"))

hap_proto = hap_protocol.HAPServerProtocol(loop, connections, driver)
hap_proto.connection_made(transport)

with patch.object(hap_proto.transport, "write") as writer:
hap_proto.data_received(
b"POST /pair-setup HTTP/1.0\r\nConnection:close\r\nHost: Bridge\\032C77C47._hap._tcp.local\r\nContent-Length: 2\r\nContent-Type: application/pairing+tlv8\r\n\r\n\x00\x01\x00\x06\x01\x01" # pylint: disable=line-too-long
)
hap_proto.data_received(
b"POST /pair-setup HTTP/1.0\r\nConnection:close\r\nHost: Bridge\\032C77C47._hap._tcp.local\r\nContent-Length: 2\r\nContent-Type: application/pairing+tlv8\r\n\r\n\x00\x01\x00\x06\x01\x01" # pylint: disable=line-too-long
)

assert (
writer.call_args_list[0][0][0].startswith(
b"HTTP/1.1 500 Internal Server Error\r\n"
)
is True
)
assert len(writer.call_args_list) == 1
assert connections == {}
hap_proto.close()


def test_invalid_client_closes_connection(driver):
"""Test we handle client closing the connection."""
loop = MagicMock()
transport = MagicMock()
connections = {}
driver.add_accessory(Accessory(driver, "TestAcc"))

hap_proto = hap_protocol.HAPServerProtocol(loop, connections, driver)
hap_proto.connection_made(transport)

with patch.object(hap_proto.transport, "write") as writer:
hap_proto.data_received(
b"POST /pair-setup HTTP/1.0\r\nConnection:close\r\nHost: Bridge\\032C77C47._hap._tcp.local\r\nContent-Length: 6\r\nContent-Type: application/pairing+tlv8\r\n\r\n\x00\x01\x00\x06\x01\x01" # pylint: disable=line-too-long
)
hap_proto.data_received(b"")

assert writer.call_args_list[0][0][0].startswith(b"HTTP/1.1 200 OK\r\n") is True
assert len(writer.call_args_list) == 1
assert connections == {}
hap_proto.close()


def test_pair_setup_split_between_packets(driver):
"""Verify an non-encrypt request."""
loop = MagicMock()
Expand Down