# Path: puresnmp/exc.py
# class Timeout(socket.timeout):
#     """
#     Wrapper for network timeouts.
# 
#     This wraps both "socket.timeout" and "asyncio.TimeoutError"
#     """
# 
#     def __init__(self, message: str) -> None:
#         super().__init__()
#         self.message = message
# 
# Path: puresnmp/transport.py
# class Transport:
#     """
#     A simple UDP transport.
# 
#     Calling ``send`` will attempt to send a packet as many times as specified
#     in *retries* (default=3). If it fails after those attemps, a
#     py:exc:`puresnmp.Timeout` exception is raised.
# 
#     :param timeout: How long to wait on the socket before retrying
#     :param retries: The number of retries attempted if a low-level
#         socket-timeout occurs. If set to ``None`` it will use
#         :py:data:`puresnmp.transport.RETRIES` as default.
#     :param buffer_size: How much data to read from the socket. If this is too
#         small, it may result in incomplete (corrupt) packages. This should be
#         kept as low as possible (see ``man(2) recv``). If set to ``None`` it
#         will use :py:data:`puresnmp.transport.BUFFER_SIZE` as default.
#     """
# 
#     def __init__(self, timeout=2, retries=None, buffer_size=None):
#         # type: (int, Optional[int], Optional[int]) -> None
#         self.timeout = timeout
#         self.retries = retries or RETRIES
#         self.buffer_size = buffer_size or BUFFER_SIZE
# 
#     def send(self, ip, port, packet, timeout=2):  # pragma: no cover
#         # type: ( str, int, bytes, int ) -> bytes
#         """
#         Opens a TCP connection to *ip:port*, sends a packet with *bytes* and
#         returns the raw bytes as returned from the remote host.
#         """
#         checked_ip = ip_address(ip)
#         if checked_ip.version == 4:
#             address_family = socket.AF_INET
#         else:
#             address_family = socket.AF_INET6
# 
#         sock = socket.socket(address_family, socket.SOCK_DGRAM)
#         sock.settimeout(timeout or self.timeout)
# 
#         for num_retry in range(self.retries):
#             try:
#                 if LOG.isEnabledFor(logging.DEBUG):
#                     hexdump = visible_octets(packet)
#                     LOG.debug(
#                         "Sending packet to %s:%s (attempt %d/%d)\n%s",
#                         ip,
#                         port,
#                         (num_retry + 1),
#                         self.retries,
#                         hexdump,
#                     )
#                 sock.sendto(packet, (ip, port))
#                 response = sock.recv(self.buffer_size)
#                 break
#             except socket.timeout:
#                 LOG.debug(
#                     "Timeout during attempt #%d", (num_retry + 1)
#                 )  # TODO add detail
#                 continue
#         else:
#             sock.close()
#             raise Timeout("Max of %d retries reached" % self.retries)
#         sock.close()
# 
#         if LOG.isEnabledFor(logging.DEBUG):
#             hexdump = visible_octets(response)
#             LOG.debug("Received packet:\n%s", hexdump)
# 
#         return response
# 
#     def listen(self, bind_address="0.0.0.0", port=162):  # pragma: no cover
#         # type: (str, int) -> Generator[SocketResponse, None, None]
#         """
#         Sets up a listening UDP socket and returns a generator over recevied
#         packets::
# 
#             >>> transport = Transport()
#             >>> for seq, packet in enumerate(transport.listen()):
#             ...     print(seq, repr(packet))
#             0, b'...'
#             1, b'...'
#             2, b'...'
# 
#         .. note::
# 
#             This defaults to the standard SNMP Trap port 162. This is a
#             privileged port so processes using this port must run as root!
#         """
#         with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
#             sock.bind((bind_address, port))
#             while True:
#                 request, addr = sock.recvfrom(self.buffer_size)
#                 if LOG.isEnabledFor(logging.DEBUG):
#                     hexdump = visible_octets(request)
#                     LOG.debug("Received packet:\n%s", hexdump)
# 
#                 yield SocketResponse(request, SocketInfo(addr[0], addr[1]))
# 
#     def get_request_id(self):  # pragma: no cover
#         # type: () -> int
#         # pylint: disable=no-self-use
#         """
#         Generates a SNMP request ID. This value should be unique for each
#         request.
#         """
#         # TODO check if this is good enough. My gut tells me "no"! Depends if
#         # it has to be unique across all clients, or just one client. If it's
#         # just one client it *may* be enough.
# 
#         return int(time())


import asyncio
import logging
from asyncio.events import AbstractEventLoop
from typing import Optional, Tuple, Union
from x690.util import visible_octets  # type: ignore
from ..exc import Timeout
from ..transport import Transport as SyncTransport
        Receive the data and close the connection.
        """
        if LOG.isEnabledFor(logging.DEBUG) and isinstance(data, bytes):
            hexdump = visible_octets(data)
            LOG.debug("Received packet:\n%s", hexdump)

        self.future.set_result(data)
        if self.transport:
            self.transport.close()

    def error_received(self, exc):
        # type: (Exception) -> None
        """
        Pass the exception along if there is an error.
        """
        if LOG.isEnabledFor(logging.DEBUG):
            LOG.debug("Error received: %s", exc)

        self.future.set_exception(exc)

    async def get_data(self, timeout):
        # type: (int) -> bytes
        """
        Retrieve the response data back into the calling coroutine.
        """
        try:
            return await asyncio.wait_for(self.future, timeout, loop=self.loop)
        except asyncio.TimeoutError as exc:
            if self.transport:
                self.transport.abort()

# Path: puresnmp/exc.py
# class Timeout(socket.timeout):
#     """
#     Wrapper for network timeouts.
# 
#     This wraps both "socket.timeout" and "asyncio.TimeoutError"
#     """
# 
#     def __init__(self, message: str) -> None:
#         super().__init__()
#         self.message = message
# 
# Path: puresnmp/transport.py
# class Transport:
#     """
#     A simple UDP transport.
# 
#     Calling ``send`` will attempt to send a packet as many times as specified
#     in *retries* (default=3). If it fails after those attemps, a
#     py:exc:`puresnmp.Timeout` exception is raised.
# 
#     :param timeout: How long to wait on the socket before retrying
#     :param retries: The number of retries attempted if a low-level
#         socket-timeout occurs. If set to ``None`` it will use
#         :py:data:`puresnmp.transport.RETRIES` as default.
#     :param buffer_size: How much data to read from the socket. If this is too
#         small, it may result in incomplete (corrupt) packages. This should be
#         kept as low as possible (see ``man(2) recv``). If set to ``None`` it
#         will use :py:data:`puresnmp.transport.BUFFER_SIZE` as default.
#     """
# 
#     def __init__(self, timeout=2, retries=None, buffer_size=None):
#         # type: (int, Optional[int], Optional[int]) -> None
#         self.timeout = timeout
#         self.retries = retries or RETRIES
#         self.buffer_size = buffer_size or BUFFER_SIZE
# 
#     def send(self, ip, port, packet, timeout=2):  # pragma: no cover
#         # type: ( str, int, bytes, int ) -> bytes
#         """
#         Opens a TCP connection to *ip:port*, sends a packet with *bytes* and
#         returns the raw bytes as returned from the remote host.
#         """
#         checked_ip = ip_address(ip)
#         if checked_ip.version == 4:
#             address_family = socket.AF_INET
#         else:
#             address_family = socket.AF_INET6
# 
#         sock = socket.socket(address_family, socket.SOCK_DGRAM)
#         sock.settimeout(timeout or self.timeout)
# 
#         for num_retry in range(self.retries):
#             try:
#                 if LOG.isEnabledFor(logging.DEBUG):
#                     hexdump = visible_octets(packet)
#                     LOG.debug(
#                         "Sending packet to %s:%s (attempt %d/%d)\n%s",
#                         ip,
#                         port,
#                         (num_retry + 1),
#                         self.retries,
#                         hexdump,
#                     )
#                 sock.sendto(packet, (ip, port))
#                 response = sock.recv(self.buffer_size)
#                 break
#             except socket.timeout:
#                 LOG.debug(
#                     "Timeout during attempt #%d", (num_retry + 1)
#                 )  # TODO add detail
#                 continue
#         else:
#             sock.close()
#             raise Timeout("Max of %d retries reached" % self.retries)
#         sock.close()
# 
#         if LOG.isEnabledFor(logging.DEBUG):
#             hexdump = visible_octets(response)
#             LOG.debug("Received packet:\n%s", hexdump)
# 
#         return response
# 
#     def listen(self, bind_address="0.0.0.0", port=162):  # pragma: no cover
#         # type: (str, int) -> Generator[SocketResponse, None, None]
#         """
#         Sets up a listening UDP socket and returns a generator over recevied
#         packets::
# 
#             >>> transport = Transport()
#             >>> for seq, packet in enumerate(transport.listen()):
#             ...     print(seq, repr(packet))
#             0, b'...'
#             1, b'...'
#             2, b'...'
# 
#         .. note::
# 
#             This defaults to the standard SNMP Trap port 162. This is a
#             privileged port so processes using this port must run as root!
#         """
#         with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
#             sock.bind((bind_address, port))
#             while True:
#                 request, addr = sock.recvfrom(self.buffer_size)
#                 if LOG.isEnabledFor(logging.DEBUG):
#                     hexdump = visible_octets(request)
#                     LOG.debug("Received packet:\n%s", hexdump)
# 
#                 yield SocketResponse(request, SocketInfo(addr[0], addr[1]))
# 
#     def get_request_id(self):  # pragma: no cover
#         # type: () -> int
#         # pylint: disable=no-self-use
#         """
#         Generates a SNMP request ID. This value should be unique for each
#         request.
#         """
#         # TODO check if this is good enough. My gut tells me "no"! Depends if
#         # it has to be unique across all clients, or just one client. If it's
#         # just one client it *may* be enough.
# 
#         return int(time())


# Path: puresnmp/aio/transport.py
import asyncio
import logging
from asyncio.events import AbstractEventLoop
from typing import Optional, Tuple, Union
from x690.util import visible_octets  # type: ignore
from ..exc import Timeout
from ..transport import Transport as SyncTransport

        Receive the data and close the connection.
        """
        if LOG.isEnabledFor(logging.DEBUG) and isinstance(data, bytes):
            hexdump = visible_octets(data)
            LOG.debug("Received packet:\n%s", hexdump)

        self.future.set_result(data)
        if self.transport:
            self.transport.close()

    def error_received(self, exc):
        # type: (Exception) -> None
        """
        Pass the exception along if there is an error.
        """
        if LOG.isEnabledFor(logging.DEBUG):
            LOG.debug("Error received: %s", exc)

        self.future.set_exception(exc)

    async def get_data(self, timeout):
        # type: (int) -> bytes
        """
        Retrieve the response data back into the calling coroutine.
        """
        try:
            return await asyncio.wait_for(self.future, timeout, loop=self.loop)
        except asyncio.TimeoutError as exc:
            if self.transport:
                self.transport.abort()

            raise Timeout(f"{timeout} second timeout exceeded") from exc