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 devtools/parse_pcap.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from dpkt.ethernet import ETH_TYPE_IP, Ethernet

from kasa.cli.main import echo
from kasa.xortransport import XorEncryption
from kasa.transports.xortransport import XorEncryption


def read_payloads_from_file(file):
Expand Down
2 changes: 1 addition & 1 deletion devtools/parse_pcap_klap.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@
DeviceEncryptionType,
DeviceFamily,
)
from kasa.klaptransport import KlapEncryptionSession, KlapTransportV2
from kasa.protocol import DEFAULT_CREDENTIALS, get_default_credentials
from kasa.transports.klaptransport import KlapEncryptionSession, KlapTransportV2


def _get_seq_from_query(packet):
Expand Down
10 changes: 5 additions & 5 deletions docs/source/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,35 +107,35 @@
```

```{eval-rst}
.. autoclass:: kasa.protocol.BaseTransport
.. autoclass:: kasa.transports.BaseTransport
:members:
:inherited-members:
:undoc-members:
```

```{eval-rst}
.. autoclass:: kasa.xortransport.XorTransport
.. autoclass:: kasa.transports.XorTransport
:members:
:inherited-members:
:undoc-members:
```

```{eval-rst}
.. autoclass:: kasa.klaptransport.KlapTransport
.. autoclass:: kasa.transports.KlapTransport
:members:
:inherited-members:
:undoc-members:
```

```{eval-rst}
.. autoclass:: kasa.klaptransport.KlapTransportV2
.. autoclass:: kasa.transports.KlapTransportV2
:members:
:inherited-members:
:undoc-members:
```

```{eval-rst}
.. autoclass:: kasa.aestransport.AesTransport
.. autoclass:: kasa.transports.AesTransport
:members:
:inherited-members:
:undoc-members:
Expand Down
3 changes: 2 additions & 1 deletion kasa/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@
_deprecated_TPLinkSmartHomeProtocol, # noqa: F401
)
from kasa.module import Module
from kasa.protocol import BaseProtocol, BaseTransport
from kasa.protocol import BaseProtocol
from kasa.smartprotocol import SmartProtocol
from kasa.transports import BaseTransport

__version__ = version("python-kasa")

Expand Down
2 changes: 1 addition & 1 deletion kasa/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@
from .iotprotocol import IotProtocol
from .module import Module
from .protocol import BaseProtocol
from .xortransport import XorTransport
from .transports import XorTransport

if TYPE_CHECKING:
from .modulemapping import ModuleMapping, ModuleName
Expand Down
11 changes: 7 additions & 4 deletions kasa/device_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import time
from typing import Any

from .aestransport import AesTransport
from .device import Device
from .device_type import DeviceType
from .deviceconfig import DeviceConfig
Expand All @@ -24,14 +23,18 @@
IotWallSwitch,
)
from .iotprotocol import IotProtocol
from .klaptransport import KlapTransport, KlapTransportV2
from .protocol import (
BaseProtocol,
BaseTransport,
)
from .smart import SmartDevice
from .smartprotocol import SmartProtocol
from .xortransport import XorTransport
from .transports import (
AesTransport,
BaseTransport,
KlapTransport,
KlapTransportV2,
XorTransport,
)

_LOGGER = logging.getLogger(__name__)

Expand Down
7 changes: 4 additions & 3 deletions kasa/discover.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,6 @@
from pydantic.v1 import BaseModel, ValidationError

from kasa import Device
from kasa.aestransport import AesEncyptionSession, KeyPair
from kasa.credentials import Credentials
from kasa.device_factory import (
get_device_class_from_family,
Expand All @@ -134,12 +133,14 @@
from kasa.json import dumps as json_dumps
from kasa.json import loads as json_loads
from kasa.protocol import mask_mac, redact_data
from kasa.xortransport import XorEncryption
from kasa.transports.aestransport import AesEncyptionSession, KeyPair
from kasa.transports.xortransport import XorEncryption

_LOGGER = logging.getLogger(__name__)

if TYPE_CHECKING:
from kasa import BaseProtocol, BaseTransport
from kasa import BaseProtocol
from kasa.transports import BaseTransport


class ConnectAttempt(NamedTuple):
Expand Down
4 changes: 2 additions & 2 deletions kasa/experimental/sslaestransport.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@

from yarl import URL

from ..aestransport import AesEncyptionSession
from ..credentials import Credentials
from ..deviceconfig import DeviceConfig
from ..exceptions import (
Expand All @@ -28,7 +27,8 @@
from ..httpclient import HttpClient
from ..json import dumps as json_dumps
from ..json import loads as json_loads
from ..protocol import DEFAULT_CREDENTIALS, BaseTransport, get_default_credentials
from ..protocol import DEFAULT_CREDENTIALS, get_default_credentials
from ..transports import AesEncyptionSession, BaseTransport

_LOGGER = logging.getLogger(__name__)

Expand Down
9 changes: 6 additions & 3 deletions kasa/iotprotocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import asyncio
import logging
from pprint import pformat as pf
from typing import Any, Callable
from typing import TYPE_CHECKING, Any, Callable

from .deviceconfig import DeviceConfig
from .exceptions import (
Expand All @@ -16,8 +16,11 @@
_RetryableError,
)
from .json import dumps as json_dumps
from .protocol import BaseProtocol, BaseTransport, mask_mac, redact_data
from .xortransport import XorEncryption, XorTransport
from .protocol import BaseProtocol, mask_mac, redact_data
from .transports import XorEncryption, XorTransport

if TYPE_CHECKING:
from .transports import BaseTransport

_LOGGER = logging.getLogger(__name__)

Expand Down
49 changes: 5 additions & 44 deletions kasa/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import logging
import struct
from abc import ABC, abstractmethod
from typing import Any, Callable, TypeVar, cast
from typing import TYPE_CHECKING, Any, Callable, TypeVar, cast

# When support for cpython older than 3.11 is dropped
# async_timeout can be replaced with asyncio.timeout
Expand All @@ -32,6 +32,10 @@
_T = TypeVar("_T")


if TYPE_CHECKING:
from .transports import BaseTransport


def redact_data(data: _T, redactors: dict[str, Callable[[Any], Any] | None]) -> _T:
"""Redact sensitive data for logging."""
if not isinstance(data, (dict, list)):
Expand Down Expand Up @@ -75,49 +79,6 @@ def md5(payload: bytes) -> bytes:
return hashlib.md5(payload).digest() # noqa: S324


class BaseTransport(ABC):
"""Base class for all TP-Link protocol transports."""

DEFAULT_TIMEOUT = 5

def __init__(
self,
*,
config: DeviceConfig,
) -> None:
"""Create a protocol object."""
self._config = config
self._host = config.host
self._port = config.port_override or self.default_port
self._credentials = config.credentials
self._credentials_hash = config.credentials_hash
if not config.timeout:
config.timeout = self.DEFAULT_TIMEOUT
self._timeout = config.timeout

@property
@abstractmethod
def default_port(self) -> int:
"""The default port for the transport."""

@property
@abstractmethod
def credentials_hash(self) -> str | None:
"""The hashed credentials used by the transport."""

@abstractmethod
async def send(self, request: str) -> dict:
"""Send a message to the device and return a response."""

@abstractmethod
async def close(self) -> None:
"""Close the transport. Abstract method to be overriden."""

@abstractmethod
async def reset(self) -> None:
"""Reset internal state."""


class BaseProtocol(ABC):
"""Base class for all TP-Link Smart Home communication."""

Expand Down
2 changes: 1 addition & 1 deletion kasa/smart/smartdevice.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
from datetime import datetime, timedelta, timezone, tzinfo
from typing import TYPE_CHECKING, Any, cast

from ..aestransport import AesTransport
from ..device import Device, WifiNetwork
from ..device_type import DeviceType
from ..deviceconfig import DeviceConfig
Expand All @@ -18,6 +17,7 @@
from ..module import Module
from ..modulemapping import ModuleMapping, ModuleName
from ..smartprotocol import SmartProtocol
from ..transports import AesTransport
from .modules import (
ChildDevice,
Cloud,
Expand Down
8 changes: 6 additions & 2 deletions kasa/smartprotocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import time
import uuid
from pprint import pformat as pf
from typing import Any, Callable
from typing import TYPE_CHECKING, Any, Callable

from .exceptions import (
SMART_AUTHENTICATION_ERRORS,
Expand All @@ -26,7 +26,11 @@
_RetryableError,
)
from .json import dumps as json_dumps
from .protocol import BaseProtocol, BaseTransport, mask_mac, md5, redact_data
from .protocol import BaseProtocol, mask_mac, md5, redact_data

if TYPE_CHECKING:
from .transports import BaseTransport


_LOGGER = logging.getLogger(__name__)

Expand Down
16 changes: 16 additions & 0 deletions kasa/transports/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""Package containing all supported transports."""

from .aestransport import AesEncyptionSession, AesTransport
from .basetransport import BaseTransport
from .klaptransport import KlapTransport, KlapTransportV2
from .xortransport import XorEncryption, XorTransport

__all__ = [
"AesTransport",
"AesEncyptionSession",
"BaseTransport",
"KlapTransport",
"KlapTransportV2",
"XorTransport",
"XorEncryption",
]
16 changes: 9 additions & 7 deletions kasa/aestransport.py → kasa/transports/aestransport.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from yarl import URL

from .credentials import Credentials
from .deviceconfig import DeviceConfig
from .exceptions import (
from kasa.credentials import Credentials
from kasa.deviceconfig import DeviceConfig
from kasa.exceptions import (
SMART_AUTHENTICATION_ERRORS,
SMART_RETRYABLE_ERRORS,
AuthenticationError,
Expand All @@ -33,10 +33,12 @@
_ConnectionError,
_RetryableError,
)
from .httpclient import HttpClient
from .json import dumps as json_dumps
from .json import loads as json_loads
from .protocol import DEFAULT_CREDENTIALS, BaseTransport, get_default_credentials
from kasa.httpclient import HttpClient
from kasa.json import dumps as json_dumps
from kasa.json import loads as json_loads
from kasa.protocol import DEFAULT_CREDENTIALS, get_default_credentials

from .basetransport import BaseTransport

_LOGGER = logging.getLogger(__name__)

Expand Down
55 changes: 55 additions & 0 deletions kasa/transports/basetransport.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"""Base class for all transport implementations.

All transport classes must derive from this to implement the common interface.
"""

from __future__ import annotations

from abc import ABC, abstractmethod
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from kasa import DeviceConfig


class BaseTransport(ABC):
"""Base class for all TP-Link protocol transports."""

DEFAULT_TIMEOUT = 5

def __init__(
self,
*,
config: DeviceConfig,
) -> None:
"""Create a protocol object."""
self._config = config
self._host = config.host
self._port = config.port_override or self.default_port
self._credentials = config.credentials
self._credentials_hash = config.credentials_hash
if not config.timeout:
config.timeout = self.DEFAULT_TIMEOUT
self._timeout = config.timeout

@property
@abstractmethod
def default_port(self) -> int:
"""The default port for the transport."""

@property
@abstractmethod
def credentials_hash(self) -> str | None:
"""The hashed credentials used by the transport."""

@abstractmethod
async def send(self, request: str) -> dict:
"""Send a message to the device and return a response."""

@abstractmethod
async def close(self) -> None:
"""Close the transport. Abstract method to be overriden."""

@abstractmethod
async def reset(self) -> None:
"""Reset internal state."""
Loading
Loading