Skip to content
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.6, 3.7, 3.8, 3.9, "3.10.0-alpha.5"]
python-version: [3.6, 3.7, 3.8, 3.9]

steps:
- uses: actions/checkout@v1
Expand Down
8 changes: 6 additions & 2 deletions pyhap/hap_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@
from .hap_crypto import hap_hkdf, pad_tls_nonce
from .util import to_hap_json

SNAPSHOT_TIMEOUT = 10

# iOS will terminate the connection if it does not respond within
# 10 seconds, so we only allow 9 seconds to avoid this.
RESPONSE_TIMEOUT = 9


logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -737,7 +741,7 @@ def handle_resource(self):
'does not define a "get_snapshot" or "async_get_snapshot" method'
)

task = asyncio.ensure_future(asyncio.wait_for(coro, SNAPSHOT_TIMEOUT))
task = asyncio.ensure_future(asyncio.wait_for(coro, RESPONSE_TIMEOUT))
self.send_response(HTTPStatus.OK)
self.send_header("Content-Type", "image/jpeg")
self.response.task = task
2 changes: 0 additions & 2 deletions pyhap/hap_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,6 @@ 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."""
Expand Down Expand Up @@ -194,7 +193,6 @@ 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
36 changes: 35 additions & 1 deletion tests/test_hap_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from cryptography.exceptions import InvalidTag
import pytest

from pyhap import hap_protocol
from pyhap import hap_protocol, hap_handler
from pyhap.accessory import Accessory, Bridge
from pyhap.hap_handler import HAPResponse

Expand Down Expand Up @@ -451,6 +451,40 @@ async def _async_get_snapshot(*_):
hap_proto.close()


@pytest.mark.asyncio
async def test_camera_snapshot_timeout_async(driver):
"""Test camera snapshot timeout is handled."""
loop = MagicMock()
transport = MagicMock()
connections = {}

async def _async_get_snapshot(*_):
await asyncio.sleep(10)
return b"fakesnap"

acc = Accessory(driver, "TestAcc")
acc.async_get_snapshot = _async_get_snapshot
driver.add_accessory(acc)

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

hap_proto.hap_crypto = MockHAPCrypto()
hap_proto.handler.is_encrypted = True

with patch.object(hap_handler, "RESPONSE_TIMEOUT", 0.1), patch.object(
hap_proto.transport, "write"
) as writer:
hap_proto.data_received(
b'POST /resource HTTP/1.1\r\nHost: HASS\\032Bridge\\032BROZ\\0323BF435._hap._tcp.local\r\nContent-Length: 79\r\nContent-Type: application/hap+json\r\n\r\n{"image-height":360,"resource-type":"image","image-width":640,"aid":1411620844}' # pylint: disable=line-too-long
)
await asyncio.sleep(0.3)

assert b"-70402" in writer.call_args_list[0][0][0]

hap_proto.close()


def test_upgrade_to_encrypted(driver):
"""Test we switch to encrypted wen we get a shared_key."""
loop = MagicMock()
Expand Down