Skip to content

Commit

Permalink
Merge pull request #419 from ikalchev/v4.5.0
Browse files Browse the repository at this point in the history
V4.5.0
  • Loading branch information
ikalchev authored Jun 28, 2022
2 parents 6c2b95c + d900089 commit fbd1f4b
Show file tree
Hide file tree
Showing 20 changed files with 57 additions and 33 deletions.
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]
python-version: [3.7, 3.8, 3.9, "3.10"]

steps:
- uses: actions/checkout@v1
Expand Down
13 changes: 11 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,25 @@ Sections
### Breaking Changes
### Developers
-->

## [4.5.0] - 2022-06-28

- Speed up "get accessories". [#418](https://github.com/ikalchev/HAP-python/pull/418)
- Increase minimum python version to 3.7. [#417](https://github.com/ikalchev/HAP-python/pull/417)
- Speed up encryption by using ChaCha20Poly1305Reusable. [#413](https://github.com/ikalchev/HAP-python/pull/413)
- Speed up serialization using orjson. [#412](https://github.com/ikalchev/HAP-python/pull/412)
- Avoid redundant parsing of the URL. [#402](https://github.com/ikalchev/HAP-python/pull/402)

## [4.4.0] - 2022-11-01

### Added
- Allow invalid client values when enabled. [#392](https://github.com/ikalchev/HAP- python/pull/392)
- Allow invalid client values when enabled. [#392](https://github.com/ikalchev/HAP-python/pull/392)

## [4.3.0] - 2021-10-07

### Fixed
- Only send the latest state in case of multiple events for the same characteristic. [#385](https://github.com/ikalchev/HAP-python/pull/385)
- Handle invalid formats from clients. [#387](https://github.com/ikalchev/HAP- python/pull/387)
- Handle invalid formats from clients. [#387](https://github.com/ikalchev/HAP-python/pull/387)

## [4.2.1] - 2021-09-06

Expand Down
4 changes: 2 additions & 2 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None
#language = None

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
Expand Down Expand Up @@ -158,4 +158,4 @@
]


# -- Extension configuration -------------------------------------------------
# -- Extension configuration -------------------------------------------------
2 changes: 1 addition & 1 deletion pyhap/accessory.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ def setup_message(self):
)
else:
print(
"To use the QR Code feature, use 'pip install " "HAP-python[QRCode]'",
"To use the QR Code feature, use 'pip install HAP-python[QRCode]'",
flush=True,
)
print(
Expand Down
4 changes: 2 additions & 2 deletions pyhap/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -842,7 +842,7 @@ async def start_stream(self, session_info, stream_config):

return True

async def stop_stream(self, session_info): # pylint: disable=no-self-use
async def stop_stream(self, session_info):
"""Stop the stream for the given ``session_id``.
This method can be implemented if custom stop stream commands are needed. The
Expand Down Expand Up @@ -886,7 +886,7 @@ async def reconfigure_stream(self, session_info, stream_config):
"""
await self.start_stream(session_info, stream_config)

def get_snapshot(self, image_size): # pylint: disable=unused-argument, no-self-use
def get_snapshot(self, image_size): # pylint: disable=unused-argument
"""Return a jpeg of a snapshot from the camera.
Overwrite to implement getting snapshots from your camera.
Expand Down
7 changes: 5 additions & 2 deletions pyhap/characteristic.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@
PROP_UNIT = "unit"
PROP_VALID_VALUES = "ValidValues"

PROP_NUMERIC = (PROP_MAX_VALUE, PROP_MIN_VALUE, PROP_MIN_STEP, PROP_UNIT)
PROP_NUMERIC = {PROP_MAX_VALUE, PROP_MIN_VALUE, PROP_MIN_STEP, PROP_UNIT}

CHAR_BUTTON_EVENT = UUID("00000126-0000-1000-8000-0026BB765291")
CHAR_PROGRAMMABLE_SWITCH_EVENT = UUID("00000073-0000-1000-8000-0026BB765291")
Expand Down Expand Up @@ -358,7 +358,10 @@ def to_HAP(self):
value = self.get_value()
if self.properties[PROP_FORMAT] in HAP_FORMAT_NUMERICS:
hap_rep.update(
{k: self.properties[k] for k in self.properties.keys() & PROP_NUMERIC}
{
k: self.properties[k]
for k in PROP_NUMERIC.intersection(self.properties)
}
)

if PROP_VALID_VALUES in self.properties:
Expand Down
4 changes: 2 additions & 2 deletions pyhap/const.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
"""This module contains constants used by other modules."""
MAJOR_VERSION = 4
MINOR_VERSION = 4
MINOR_VERSION = 5
PATCH_VERSION = 0
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__ = f"{__short_version__}.{PATCH_VERSION}"
REQUIRED_PYTHON_VER = (3, 6)
REQUIRED_PYTHON_VER = (3, 7)

BASE_UUID = "-0000-1000-8000-0026BB765291"

Expand Down
2 changes: 1 addition & 1 deletion pyhap/hap_crypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
import logging
import struct

from chacha20poly1305_reuseable import ChaCha20Poly1305Reusable as ChaCha20Poly1305
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
from cryptography.hazmat.primitives.kdf.hkdf import HKDF

logger = logging.getLogger(__name__)
Expand Down
17 changes: 9 additions & 8 deletions pyhap/hap_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@
"""
import asyncio
from http import HTTPStatus
import json
import logging
from urllib.parse import parse_qs, urlparse
import uuid

from cryptography.exceptions import InvalidSignature, InvalidTag
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ed25519, x25519
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
from chacha20poly1305_reuseable import ChaCha20Poly1305Reusable as ChaCha20Poly1305

from pyhap import tlv
from pyhap.const import (
Expand All @@ -25,7 +24,7 @@
from pyhap.util import long_to_bytes

from .hap_crypto import hap_hkdf, pad_tls_nonce
from .util import to_hap_json
from .util import to_hap_json, from_hap_json

# iOS will terminate the connection if it does not respond within
# 10 seconds, so we only allow 9 seconds to avoid this.
Expand Down Expand Up @@ -145,6 +144,7 @@ def __init__(self, accessory_handler, client_address):
self.command = None
self.headers = None
self.request_body = None
self.parsed_url = None

self.response = None

Expand Down Expand Up @@ -199,6 +199,7 @@ def dispatch(self, request, body=None):
self.command = request.method.decode()
self.headers = {k.decode(): v.decode() for k, v in request.headers}
self.request_body = body
self.parsed_url = urlparse(self.path)
response = HAPResponse()
self.response = response

Expand All @@ -210,7 +211,7 @@ def dispatch(self, request, body=None):
self.headers,
)

path = urlparse(self.path).path
path = self.parsed_url.path
try:
getattr(self, self.HANDLERS[self.command][path])()
except UnprivilegedRequestException:
Expand Down Expand Up @@ -584,7 +585,7 @@ def handle_get_characteristics(self):
raise UnprivilegedRequestException

# Check that char exists and ...
params = parse_qs(urlparse(self.path).query)
params = parse_qs(self.parsed_url.query)
response = self.accessory_handler.get_characteristics(
params["id"][0].split(",")
)
Expand Down Expand Up @@ -612,7 +613,7 @@ def handle_set_characteristics(self):
self.send_response(HTTPStatus.UNAUTHORIZED)
return

requested_chars = json.loads(self.request_body.decode("utf-8"))
requested_chars = from_hap_json(self.request_body.decode("utf-8"))
logger.debug(
"%s: Set characteristics content: %s", self.client_address, requested_chars
)
Expand All @@ -637,7 +638,7 @@ def handle_prepare(self):
self.send_response(HTTPStatus.UNAUTHORIZED)
return

request = json.loads(self.request_body.decode("utf-8"))
request = from_hap_json(self.request_body.decode("utf-8"))
logger.debug("%s: prepare content: %s", self.client_address, request)

response = self.accessory_handler.prepare(request, self.client_address)
Expand Down Expand Up @@ -742,7 +743,7 @@ def _send_tlv_pairing_response(self, data):

def handle_resource(self):
"""Get a snapshot from the camera."""
data = json.loads(self.request_body.decode("utf-8"))
data = from_hap_json(self.request_body.decode("utf-8"))

if self.accessory_handler.accessory.category == CATEGORY_BRIDGE:
accessory = self.accessory_handler.accessory.accessories.get(data["aid"])
Expand Down
2 changes: 1 addition & 1 deletion pyhap/hap_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

logger = logging.getLogger(__name__)

HIGH_WRITE_BUFFER_SIZE = 2 ** 19
HIGH_WRITE_BUFFER_SIZE = 2**19
# We timeout idle connections after 90 hours as we must
# clean up unused sockets periodically. 90 hours was choosen
# as its the longest time we expect a user to be away from
Expand Down
14 changes: 11 additions & 3 deletions pyhap/util.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import asyncio
import base64
import functools
import json
import random
import socket
from uuid import UUID

import orjson

from .const import BASE_UUID

ALPHANUM = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
Expand Down Expand Up @@ -157,9 +158,16 @@ def hap_type_to_uuid(hap_type):

def to_hap_json(dump_obj):
"""Convert an object to HAP json."""
return json.dumps(dump_obj, separators=(",", ":")).encode("utf-8")
return orjson.dumps(dump_obj) # pylint: disable=no-member


def to_sorted_hap_json(dump_obj):
"""Convert an object to sorted HAP json."""
return json.dumps(dump_obj, sort_keys=True, separators=(",", ":")).encode("utf-8")
return orjson.dumps( # pylint: disable=no-member
dump_obj, option=orjson.OPT_SORT_KEYS # pylint: disable=no-member
)


def from_hap_json(json_str):
"""Convert json to an object."""
return orjson.loads(json_str) # pylint: disable=no-member
1 change: 0 additions & 1 deletion pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,5 @@ disable=
too-many-public-methods,
too-many-return-statements,
too-many-statements,
bad-continuation,
unused-argument,
consider-using-with
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
h11
chacha20poly1305-reuseable
cryptography
orjson
zeroconf
1 change: 1 addition & 0 deletions requirements_all.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
base36
cryptography
orjson
pyqrcode
h11
zeroconf
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
README = f.read()


REQUIRES = ["cryptography", "zeroconf>=0.36.2", "h11"]
REQUIRES = ["cryptography", "chacha20poly1305-reuseable", "orjson>=3.7.2", "zeroconf>=0.36.2", "h11"]


setup(
Expand Down
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,5 @@ def __init__(self):
def publish(self, data, client_addr=None, immediate=False):
pass

def add_job(self, target, *args): # pylint: disable=no-self-use
def add_job(self, target, *args):
asyncio.new_event_loop().run_until_complete(target(*args))
2 changes: 1 addition & 1 deletion tests/test_characteristic.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def test_repr():
char = get_char(PROPERTIES.copy())
del char.properties["Permissions"]
assert (
char.__repr__() == "<characteristic display_name=Test Char value=0 "
repr(char) == "<characteristic display_name=Test Char value=0 "
"properties={'Format': 'int'}>"
)

Expand Down
3 changes: 3 additions & 0 deletions tests/test_hap_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import json
from unittest.mock import patch
from urllib.parse import urlparse
from uuid import UUID

import pytest
Expand Down Expand Up @@ -697,6 +698,8 @@ def test_handle_get_characteristics_encrypted(driver):
response = hap_handler.HAPResponse()
handler.response = response
handler.path = "/characteristics?id=1.11"
handler.parsed_url = urlparse(handler.path)

handler.handle_get_characteristics()

assert response.status_code == 200
Expand Down
2 changes: 1 addition & 1 deletion tests/test_hap_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def decrypt(self):
self._crypt_in_buffer = bytearray() # Encrypted buffer
return decrypted

def encrypt(self, data): # pylint: disable=no-self-use
def encrypt(self, data):
"""Mock as plaintext."""
return data

Expand Down
4 changes: 1 addition & 3 deletions tests/test_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,7 @@ def test_repr():
"""Test service representation."""
service = Service(uuid1(), "TestService")
service.characteristics = [get_chars()[0]]
assert (
service.__repr__() == "<service display_name=TestService chars={'Char 1': 0}>"
)
assert repr(service) == "<service display_name=TestService chars={'Char 1': 0}>"


def test_add_characteristic():
Expand Down

0 comments on commit fbd1f4b

Please sign in to comment.