Skip to content

Commit

Permalink
fixed dtls, some improvments added
Browse files Browse the repository at this point in the history
  • Loading branch information
hrdasdominik committed Oct 19, 2024
1 parent 5560a45 commit e62b763
Show file tree
Hide file tree
Showing 12 changed files with 123 additions and 170 deletions.
8 changes: 5 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
venv/
__pycache__/
logs/
/.idea/
.idea/
data/
/.run/
.run/
dist
*.egg-info
*.egg-info
qodana.yaml
docs/wireshark
5 changes: 1 addition & 4 deletions example/example_new.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,9 @@ def example():
hue_pykit.set_color_space("xyb")

light_list = []
light1 = LightXYB(1, 0.4, 0.3, 0.7)
light1 = LightXYB(1, "", 0.4, 0.3, 1.0)
light_list.append(light1)

light2 = LightXYB(2, 0.2, 0.5, 0.6)
light_list.append(light2)

hue_pykit.set_lights_functions(light_list)

time.sleep(0.1)
Expand Down
16 changes: 8 additions & 8 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[build-system]
requires = ["setuptools>=61.0.0", "wheel"]
requires = ["setuptools>=75.1.0", "wheel"]
build-backend = "setuptools.build_meta"

[project]
Expand All @@ -21,26 +21,26 @@ classifiers = [
"Topic :: Utilities"
]
dependencies = [
"certifi==2024.7.4",
"charset-normalizer==3.3.2",
"certifi==2024.8.30",
"charset-normalizer==3.4.0",
"click==8.1.7",
"colorama==0.4.6",
"idna==3.7",
"idna==3.10",
"ifaddr==0.2.0",
"mypy-extensions==1.0.0",
"packaging==24.1",
"pathspec==0.12.1",
"platformdirs==4.2.2",
"platformdirs==4.3.6",
"python-mbedtls==2.10.1",
"requests==2.32.3",
"typing_extensions==4.12.2",
"urllib3==2.2.2",
"zeroconf==0.132.2"
"urllib3==2.2.3",
"zeroconf==0.135.0"
]
keywords = ["philips", "hue", "lights", "entertainment", "api", "python"]

[project.urls]
Repository = "https://github.com/hrdasdominik/hue-entertainment-pykit"

[options]
package_dir = "src"
package_dir = "src/hue_entertainment_pykit"
Binary file modified requirements.txt
Binary file not shown.
3 changes: 2 additions & 1 deletion src/hue_entertainment_pykit/bridge/bridge_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@
from typing import Any

import requests
from requests import Response

from src.hue_entertainment_pykit.exceptions.bridge_exception import BridgeException
from src.hue_entertainment_pykit.models.entertainment_configuration import EntertainmentConfiguration, EntertainmentChannel
from src.hue_entertainment_pykit.models.light import LightXYB, LightBase
from src.hue_entertainment_pykit.utils.endpoint import Endpoint
from src.hue_entertainment_pykit.utils.file_handler import FileHandler
from src.hue_entertainment_pykit.utils.http_method import HttpMethod
from src.hue_entertainment_pykit.utils.status_code import StatusCode
from requests import Response


# pylint: disable=too-few-public-methods
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,12 @@
import logging

import requests
from requests import Response

from src.hue_entertainment_pykit.exceptions.api_exception import ApiException
from src.hue_entertainment_pykit.models.bridge import Bridge
from src.hue_entertainment_pykit.models.payload import Payload
from src.hue_entertainment_pykit.models.entertainment_configuration import EntertainmentConfiguration
from src.hue_entertainment_pykit.exceptions.api_exception import ApiException

from src.hue_entertainment_pykit.models.payload import Payload
from src.hue_entertainment_pykit.utils.status_code import StatusCode
from requests import Response


class EntertainmentConfigurationRepository:
Expand Down Expand Up @@ -86,7 +84,7 @@ def _send_request(self, method: str, url: str, payload: Payload = None) -> Respo
headers=self._headers,
json=payload.get_data() if payload else None,
verify=False,
timeout=10,
timeout=5,
)
if response.status_code != StatusCode.OK.value:
raise ApiException(
Expand Down
18 changes: 9 additions & 9 deletions src/hue_entertainment_pykit/hue_entertainment_pykit.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,19 @@
import re
from typing import Optional, Union

from hue_entertainment_pykit.bridge.bridge_repository import BridgeRepository
from hue_entertainment_pykit.bridge.entertainment_configuration_repository import (
from src.hue_entertainment_pykit.bridge.bridge_repository import BridgeRepository
from src.hue_entertainment_pykit.bridge.entertainment_configuration_repository import (
EntertainmentConfigurationRepository,
)
from hue_entertainment_pykit.models.bridge import Bridge
from hue_entertainment_pykit.models.entertainment_configuration import (
from src.hue_entertainment_pykit.models.bridge import Bridge
from src.hue_entertainment_pykit.models.entertainment_configuration import (
EntertainmentConfiguration,
)
from hue_entertainment_pykit.network.dtls import Dtls
from hue_entertainment_pykit.network.mdns import Mdns
from hue_entertainment_pykit.services.discovery_service import DiscoveryService
from hue_entertainment_pykit.services.streaming_service import StreamingService
from hue_entertainment_pykit.utils.logger import setup_logging
from src.hue_entertainment_pykit.network.dtls import Dtls
from src.hue_entertainment_pykit.network.mdns import Mdns
from src.hue_entertainment_pykit.services.discovery_service import DiscoveryService
from src.hue_entertainment_pykit.services.streaming_service import StreamingService
from src.hue_entertainment_pykit.utils.logger import setup_logging


def setup_logs(
Expand Down
8 changes: 6 additions & 2 deletions src/hue_entertainment_pykit/hue_entertainment_pykit_new.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import logging

from src.hue_entertainment_pykit.bridge.bridge_repository import BridgeRepository
from src.hue_entertainment_pykit.bridge.entertainment_configuration_repository import EntertainmentConfigurationRepository
from src.hue_entertainment_pykit.bridge.entertainment_configuration_repository import \
EntertainmentConfigurationRepository
from src.hue_entertainment_pykit.models.bridge import Bridge
from src.hue_entertainment_pykit.models.entertainment_configuration import EntertainmentConfiguration
from src.hue_entertainment_pykit.models.light import LightBase
Expand Down Expand Up @@ -58,7 +59,10 @@ def set_color_space(self, color_space: str):
def set_lights_functions(self,
light_list: list[LightBase],
transition_time: float = 0.0):
self._streaming.set_input(light_list, transition_time)
self._streaming.set_input(
light_list,
# transition_time
)

def get_all_bridges(self) -> dict[str, Bridge]:
if not self._bridges:
Expand Down
16 changes: 8 additions & 8 deletions src/hue_entertainment_pykit/models/bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,12 +170,12 @@ def from_dict(cls, data: dict):
Bridge: An instance of Bridge with attributes set according to the data dictionary.
"""
return cls(
identification=data.get("id"),
rid=data.get("rid"),
ip_address=data.get("internalipaddress"),
username=data.get("username"),
client_key=data.get("clientkey"),
name=data.get("name"),
swversion=data.get("swversion"),
hue_app_id=data.get("hue-application-id"),
identification=data["id"],
rid=data["rid"],
ip_address=data["internalipaddress"],
username=data["username"],
client_key=data["clientkey"],
name=data["name"],
swversion=data["swversion"],
hue_app_id=data["hue-application-id"],
)
83 changes: 71 additions & 12 deletions src/hue_entertainment_pykit/network/dtls.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@
Classes:
- Dtls: Handles DTLS connections with Philips Hue Bridges using pre-shared keys.
"""

import errno
import logging
import os
import socket
import time
from typing import Tuple, List

from mbedtls.tls import TLSWrappedSocket, DTLSConfiguration, ClientContext
from mbedtls._tls import WantReadError, WantWriteError, HandshakeStep
from mbedtls.tls import TLSWrappedSocket, DTLSConfiguration, ClientContext, DTLSVersion

from src.hue_entertainment_pykit.models.bridge import Bridge

Expand Down Expand Up @@ -56,7 +59,7 @@ def __init__(self, bridge: Bridge):
self._psk_key = bytes.fromhex(bridge.get_client_key())
self._psk_identity: str = bridge.get_hue_application_id()
self._ciphers: List[str] = [
"TLS-PSK-WITH-AES-256-GCM-SHA384",
"TLS-PSK-WITH-AES-128-GCM-SHA256",
]
self._dtls_socket: TLSWrappedSocket | None = None
self._sock_timeout: int = 5
Expand Down Expand Up @@ -98,8 +101,6 @@ def get_socket(self) -> TLSWrappedSocket | None:
TLSWrappedSocket | None: The DTLS socket, or None if unable to create
"""

if not self._dtls_socket:
self._create_dtls_socket()
return self._dtls_socket

def _create_dtls_socket(self):
Expand All @@ -113,14 +114,15 @@ def _create_dtls_socket(self):
config = DTLSConfiguration(
pre_shared_key=(self._psk_identity, self._psk_key),
ciphers=self._ciphers,
lowest_supported_version=DTLSVersion.DTLSv1_2
)
dtls_client = ClientContext(config)
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udp_socket.settimeout(self._sock_timeout)
udp_socket.connect(self._server_address)
self._dtls_socket = dtls_client.wrap_socket(
udp_socket, server_hostname=self._server_address[0]
)
udp_socket.settimeout(self._sock_timeout)
buffer = dtls_client.wrap_buffers(server_hostname=self._server_address[0])

self._dtls_socket = self.PatchedTLSWrappedSocket(udp_socket, buffer)

def do_handshake(self):
"""
Expand All @@ -129,10 +131,9 @@ def do_handshake(self):
Establishes a DTLS socket if not already present and performs a handshake to initiate secure communication.
"""

if self._dtls_socket is None:
self._create_dtls_socket()
self._create_dtls_socket()
logging.info("Starting DTLS handshake")
self._dtls_socket.do_handshake()
self._dtls_socket.do_handshake(self._server_address)
logging.info("DTLS handshake established")

def close_socket(self):
Expand All @@ -143,3 +144,61 @@ def close_socket(self):
if self._dtls_socket:
self._dtls_socket.close()
self._dtls_socket = None

class PatchedTLSWrappedSocket(TLSWrappedSocket):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._handshake_retries = 0 # Track the number of handshake attempts

def do_handshake(self, *args):
# pylint: disable=too-many-branches
if args and self.type is not socket.SOCK_DGRAM:
raise OSError(errno.ENOTCONN, os.strerror(errno.ENOTCONN))

if len(args) == 0:
flags, address = 0, None
elif len(args) == 1:
flags, address = 0, args[0]
elif len(args) == 2:
assert isinstance(args[0], int)
flags, address = args
else:
raise TypeError("do_handshake() takes 0, 1, or 2 arguments")

while self._handshake_state is not HandshakeStep.HANDSHAKE_OVER:
try:
self._buffer.do_handshake()
except WantReadError as exc:
if address is None:
data = self._socket.recv(TLSWrappedSocket.CHUNK_SIZE, flags)
else:
data, addr = self._socket.recvfrom(TLSWrappedSocket.CHUNK_SIZE, flags)
if addr != address:
raise OSError(
errno.ENOTCONN, os.strerror(errno.ENOTCONN)
) from exc
self._buffer.receive_from_network(data)
except WantWriteError:
in_transit = self._buffer.peek_outgoing(TLSWrappedSocket.CHUNK_SIZE)
if address is None:
amt = self._socket.send(in_transit, flags)
else:
amt = self._socket.sendto(in_transit, flags, address)
self._buffer.consume_outgoing(amt)

self._handshake_retries += 1
print(f"Retransmission attempt: {self._handshake_retries}")

if self._handshake_retries < 3:
print("Resending second ClientHello")
time.sleep(0.3)
if address is None:
amt = self._socket.send(in_transit, flags)
else:
amt = self._socket.sendto(in_transit, flags, address)
self._buffer.consume_outgoing(amt)

if self._handshake_retries > 3:
raise Exception("Maximum handshake retries exceeded")


16 changes: 9 additions & 7 deletions src/hue_entertainment_pykit/services/streaming_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,14 +126,14 @@ def start_stream(self):

try:
self._dtls_service.do_handshake()
except Exception:
logging.exception("Error during handshake for DTLS connection.")
except Exception as e:
logging.exception(f"Error during handshake for DTLS connection.\n error: {e}")
self._dtls_service.close_socket()
stop_payload = Payload()
stop_payload.set_key_and_or_value("id", self._entertainment_configuration.id)
stop_payload.set_key_and_or_value("action", "stop")
self._entertainment_configuration_repository.put_configuration(stop_payload)
raise ConnectionException("Failed DTLS handshake")
raise ConnectionException("Failed DTLS handshake cause of ", e)

self._is_connection_alive = True

Expand Down Expand Up @@ -188,8 +188,8 @@ def set_input(
"""
processed_user_input = []
for light in light_list:
rgb16_and_light_id: tuple[int, int, int, int] = Converter.xyb_or_rgb8_to_rgb16(light.get_colors()) + (
light.get_id())
rgb16_and_light_id: tuple[int, int, int, int] = Converter.xyb_or_rgb8_to_rgb16(
light.get_colors()) + (light.get_id(),)
processed_user_input.append(rgb16_and_light_id)

self._input_queue.put(processed_user_input)
Expand Down Expand Up @@ -266,7 +266,7 @@ def _watch_user_input(self):
while self._is_connection_alive:
try:
user_input = self._input_queue.get(
timeout=0.5
timeout=1
) # using timeout to avoid busy waiting
self._process_user_input(user_input)
except queue.Empty:
Expand Down Expand Up @@ -360,7 +360,9 @@ def _pack_color_data(
channel_data = b""
channel_data += struct.pack(">B", light_id)

rx = color[0], gy = color[1], bb = color[2]
rx = color[0]
gy = color[1]
bb = color[2]

logging.debug("Converted values: %s, %s, %s", rx, gy, bb)
return channel_data + struct.pack(">HHH", rx, gy, bb)
Loading

0 comments on commit e62b763

Please sign in to comment.