"""Base class for HAP based channels (connections)."""
from abc import ABC, abstractmethod
import asyncio
import logging
from typing import Callable, Tuple, cast

from pyatv.auth.hap_pairing import PairVerifyProcedure
from pyatv.auth.hap_session import HAPSession
from pyatv.support import log_binary

_LOGGER = logging.getLogger(__name__)


class AbstractHAPChannel(ABC, asyncio.Protocol):
    """Abstract base class for connections using HAP encryption and segmenting."""

    def __init__(self, output_key: bytes, input_key: bytes) -> None:
        """Initialize a new AbstractHAPChannel instance."""
        self.buffer = b""
        self.transport = None
        self.session: HAPSession = HAPSession()
        self.session.enable(output_key, input_key)

    def connection_made(self, transport) -> None:
        """Device connection was made."""
        sock = transport.get_extra_info("socket")
        dstaddr, dstport = sock.getpeername()
        _LOGGER.debug("Connected to %s:%d", dstaddr, dstport)

        self.transport = transport

    def data_received(self, data: bytes) -> None:
        """Message was received from device."""
        assert self.transport is not None

        decrypt = self.session.decrypt(data)
        log_binary(_LOGGER, "Received data", Data=data)
        self.buffer += decrypt
        self.handle_received()

    @abstractmethod
    def handle_received(self) -> None:
        """Handle received data that was put in buffer."""

    def send(self, data: bytes) -> None:
        """Send message to device."""
        assert self.transport is not None

        encrypted = self.session.encrypt(data)
        log_binary(_LOGGER, "Sending data", Encrypted=encrypted)
        self.transport.write(encrypted)

    def connection_lost(self, exc) -> None:
        """Device connection was dropped."""
        _LOGGER.debug("Connection was lost to remote")


async def setup_channel(
    factory: Callable[[bytes, bytes], AbstractHAPChannel],
    verifier: PairVerifyProcedure,
    address: str,
    port: int,
    salt: str,
    output_info: str,
    input_info: str,
) -> Tuple[asyncio.BaseTransport, AbstractHAPChannel]:
    """Set up a new HAP channel and enable encryption."""
    out_key, in_key = verifier.encryption_keys(salt, output_info, input_info)

    loop = asyncio.get_event_loop()
    transport, protocol = await loop.create_connection(
        lambda: factory(out_key, in_key),
        address,
        port,
    )
    return transport, cast(AbstractHAPChannel, protocol)

Add abstract class for HAP channels
auth: Add abstract class for HAP channels

Relates to #1255
