8000 Merge pull request #449 from ikalchev/v4.7.1 · ikalchev/HAP-python@5f45a5e · GitHub
[go: up one dir, main page]

Skip to content

Commit 5f45a5e

Browse files
authored
Merge pull request #449 from ikalchev/v4.7.1
V4.7.1
2 parents 54d174d + 1f9fbc7 commit 5f45a5e

File tree

9 files changed

+63
-33
lines changed

9 files changed

+63
-33
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ Sections
1616
### Developers
1717
-->
1818

19+
## [4.7.1] - 2023-07-31
20+
21+
- Improve encryption performance. [#448](https://github.com/ikalchev/HAP-python/pull/448)
22+
- Switch timeouts to use `async_timeout`. [#447](https://github.com/ikalchev/HAP-python/pull/447)
23+
1924
## [4.7.0] - 2023-06-18
2025

2126
- Allow passing multiple ip to advertise on to AccessoryDriver. [#442](https://github.com/ikalchev/HAP-python/pull/442)

pyhap/accessory.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
"""Module for the Accessory classes."""
22
import itertools
33
import logging
4-
54
from uuid import UUID
5+
66
from pyhap import SUPPORT_QR_CODE, util
77
from pyhap.const import (
88
CATEGORY_BRIDGE,
99
CATEGORY_OTHER,
10+
HAP_PROTOCOL_VERSION,
1011
HAP_REPR_AID,
1112
HAP_REPR_IID,
12-
HAP_PROTOCOL_VERSION,
1313
HAP_REPR_SERVICES,
1414
HAP_REPR_VALUE,
1515
STANDALONE_AID,

pyhap/camera.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,18 @@
1818

1919
import asyncio
2020
import functools
21-
import os
2221
import ipaddress
2322
import logging
23+
import os
2424
import struct
2525
from uuid import UUID
2626

27-
from pyhap import RESOURCE_DIR
27+
import async_timeout
28+
29+
from pyhap import RESOURCE_DIR, tlv
2830
from pyhap.accessory import Accessory
2931
from pyhap.const import CATEGORY_CAMERA
30-
from pyhap.util import to_base64_str, byte_bool
31-
from pyhap import tlv
32-
32+
from pyhap.util import byte_bool, to_base64_str
3333

3434
SETUP_TYPES = {
3535
'SESSION_ID': b'\x01',
@@ -859,8 +859,8 @@ async def stop_stream(self, session_info):
859859
logger.info('[%s] Stopping stream.', session_id)
860860
try:
861861
ffmpeg_process.terminate()
862-
_, stderr = await asyncio.wait_for(
863-
ffmpeg_process.communicate(), timeout=2.0)
862+
async with async_timeout.tim 10000 eout(2.0):
863+
_, stderr = await ffmpeg_process.communicate()
864864
logger.debug('Stream command stderr: %s', stderr)
865865
except asyncio.TimeoutError:
866866
logger.error(

pyhap/const.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""This module contains constants used by other modules."""
22
MAJOR_VERSION = 4
33
MINOR_VERSION = 7
4-
PATCH_VERSION = 0
4+
PATCH_VERSION = 1
55
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
66
__version__ = f"{__short_version__}.{PATCH_VERSION}"
77
REQUIRED_PYTHON_VER = (3, 7)

pyhap/hap_crypto.py

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
"""This module partially implements crypto for HAP."""
22
import logging
33
import struct
4-
4+
from functools import partial
5+
from typing import List
6+
from struct import Struct
57
from chacha20poly1305_reuseable import ChaCha20Poly1305Reusable as ChaCha20Poly1305
68
from cryptography.hazmat.backends import default_backend
79
from cryptography.hazmat.primitives import hashes
@@ -11,6 +13,9 @@
1113

1214
CRYPTO_BACKEND = default_backend()
1315

16+
PACK_NONCE = partial(Struct("<LQ").pack, 0)
17+
PACK_LENGTH = Struct("H").pack
18+
1419

1520
class HAP_CRYPTO:
1621
HKDF_KEYLEN = 32 # bytes, length of expanded HKDF keys
@@ -74,51 +79,54 @@ def decrypt(self) -> bytes:
7479
blocks are buffered locally.
7580
"""
7681
result = b""
82+
crypt_in_buffer = self._crypt_in_buffer
83+
length_length = self.LENGTH_LENGTH
84+
tag_length = HAP_CRYPTO.TAG_LENGTH
7785

78-
while len(self._crypt_in_buffer) > self.MIN_BLOCK_LENGTH:
79-
block_length_bytes = self._crypt_in_buffer[: self.LENGTH_LENGTH]
86+
while len(crypt_in_buffer) > self.MIN_BLOCK_LENGTH:
87+
block_length_bytes = crypt_in_buffer[:length_length]
8088
block_size = struct.unpack("H", block_length_bytes)[0]
81-
block_size_with_length = (
82-
self.LENGTH_LENGTH + block_size + HAP_CRYPTO.TAG_LENGTH
83-
)
89+
block_size_with_length = length_length + block_size + tag_length
8490

85-
if len(self._crypt_in_buffer) < block_size_with_length:
91+
if len(crypt_in_buffer) < block_size_with_length:
8692
logger.debug("Incoming buffer does not have the full block")
8793
return result
8894

8995
# Trim off the length
90-
del self._crypt_in_buffer[: self.LENGTH_LENGTH]
96+
del crypt_in_buffer[:length_length]
9197

92-
data_size = block_size + HAP_CRYPTO.TAG_LENGTH
93-
nonce = pad_tls_nonce(struct.pack("Q", self._in_count))
98+
data_size = block_size + tag_length
99+
nonce = PACK_NONCE(self._in_count)
94100

95101
result += self._in_cipher.decrypt(
96102
nonce,
97-
bytes(self._crypt_in_buffer[:data_size]),
103+
bytes(crypt_in_buffer[:data_size]),
98104
bytes(block_length_bytes),
99105
)
100106

101107
self._in_count += 1
102108

103109
# Now trim out the decrypted data
104-
del self._crypt_in_buffer[:data_size]
110+
del crypt_in_buffer[:data_size]
105111

106112
return result
107113

108114
def encrypt(self, data: bytes) -> bytes:
109115
"""Encrypt and send the return bytes."""
110-
result = b""
116+
result: List[bytes] = []
111117
offset = 0
112118
total = len(data)
113119
while offset < total:
114120
length = min(total - offset, self.MAX_BLOCK_LENGTH)
115-
length_bytes = struct.pack("H", length)
121+
length_bytes = PACK_LENGTH(length)
116122
block = bytes(data[offset : offset + length])
117-
nonce = pad_tls_nonce(struct.pack("Q", self._out_count))
118-
ciphertext = length_bytes + self._out_cipher.encrypt(
119-
nonce, block, length_bytes
120-
)
123+
nonce = PACK_NONCE(self._out_count)
124+
result.append(length_bytes)
125+
result.append(self._out_cipher.encrypt(nonce, block, length_bytes))
121126
offset += length
122127
self._out_count += 1
123-
result += ciphertext
124-
return result
128+
129+
# Join the result once instead of concatenating each time
130+
# as this is much faster than generating an new immutable
131+
# byte string each time.
132+
return b"".join(result)

pyhap/hap_handler.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from urllib.parse import ParseResult, parse_qs, urlparse
1010
import uuid
1111

12+
import async_timeout
1213
from chacha20poly1305_reuseable import ChaCha20Poly1305Reusable as ChaCha20Poly1305
1314
from cryptography.exceptions import InvalidSignature, InvalidTag
1415
from cryptography.hazmat.primitives import serialization
@@ -93,6 +94,12 @@ class UnprivilegedRequestException(Exception):
9394
pass
9495

9596

97+
async def _run_with_timeout(coro, timeout: float) -> bytes< 77FB /span>:
98+
"""Run a coroutine with a timeout."""
99+
async with async_timeout.timeout(timeout):
100+
return await coro
101+
102+
96103
class HAPServerHandler:
97104
"""Manages HAP connection state and handles incoming HTTP requests."""
98105

@@ -820,7 +827,7 @@ def handle_resource(self) -> None:
820827
'does not define a "get_snapshot" or "async_get_snapshot" method'
821828
)
822829

823-
task = asyncio.ensure_future(asyncio.wait_for(coro, RESPONSE_TIMEOUT))
830+
task = asyncio.ensure_future(_run_with_timeout(coro, RESPONSE_TIMEOUT))
824831
self.send_response(HTTPStatus.OK)
825832
self.send_header("Content-Type", "image/jpeg")
826833
assert self.response is not None # nosec

pyhap/hsrp.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Server Side SRP implementation
22

33
import os
4+
45
from .util import long_to_bytes
56

67

pyhap/util.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import socket
66
from uuid import UUID
77

8+
import async_timeout
89
import orjson
910

1011
from .const import BASE_UUID
@@ -135,7 +136,8 @@ async def event_wait(event, timeout):
135136
:rtype: bool
136137
"""
137138
try:
138-
await asyncio.wait_for(event.wait(), timeout)
139+
async with async_timeout.timeout(timeout):
140+
await event.wait()
139141
except asyncio.TimeoutError:
140142
pass
141143
return event.is_set()

setup.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,14 @@
2222
README = f.read()
2323

2424

25-
REQUIRES = ["cryptography", "chacha20poly1305-reuseable", "orjson>=3.7.2", "zeroconf>=0.36.2", "h11"]
25+
REQUIRES = [
26+
"async_timeout",
27+
"cryptography",
28+
"chacha20poly1305-reuseable",
29+
"orjson>=3.7.2",
30+
"zeroconf>=0.36.2",
31+
"h11",
32+
]
2633

2734

2835
setup(

0 commit comments

Comments
 (0)
0