# Portions of this file are derived from CPython's asyncio sources
# (notably asyncio.base_events and asyncio.selector_events).
# Copyright (c) Python Software Foundation.
# Licensed under the Python Software Foundation License Version 2.
# See LICENSES/PSF-2.0.txt and THIRD_PARTY_NOTICES for details.

import collections
import errno
import itertools
import os
import socket
import ssl
import asyncio
import sys
import weakref
from logging import getLogger

from . import constants
from .ssl_protocol import SSLProtocol
from .transport import Transport, SelectorSocketTransport, aiofn_is_buffered_protocol
from asyncio.trsock import TransportSocket

from .utils import aiofn_validate_and_maybe_copy_buffer

_HAS_IPv6 = hasattr(socket, 'AF_INET6')
_logger = getLogger('fastnet')


def _should_fallback_to_asyncio(loop: asyncio.AbstractEventLoop) -> bool:
    if os.name != "nt":
        return False

    proactor_event_loop = getattr(asyncio, "ProactorEventLoop", None)
    if proactor_event_loop is None:
        return False

    return isinstance(loop, proactor_event_loop)


def _is_asyncio_loop(loop: asyncio.AbstractEventLoop) -> bool:
    return type(loop).__module__.startswith("asyncio.")


class _WrappedTransport(Transport):
    __slots__ = ('_transport',)

    def __init__(self, transport: asyncio.Transport):
        super().__init__()
        self._transport = transport

    def get_extra_info(self, name, default=None):
        return self._transport.get_extra_info(name, default)

    def is_closing(self):
        return self._transport.is_closing()

    def close(self):
        return self._transport.close()

    def set_protocol(self, protocol):
        if aiofn_is_buffered_protocol(protocol):
            wrapped_protocol = _WrappedBufferedProtocol(protocol)
        else:
            wrapped_protocol = _WrappedProtocol(protocol)
        self._transport.set_protocol(wrapped_protocol)

    def get_protocol(self):
        wrapped_protocol: _WrappedProtocolBase = self._transport.get_protocol()
        assert isinstance(wrapped_protocol, _WrappedProtocolBase), \
            "must be our protocol wrapper"
        return wrapped_protocol._protocol

    def is_reading(self):
        return self._transport.is_reading()

    def pause_reading(self):
        return self._transport.pause_reading()

    def resume_reading(self):
        return self._transport.resume_reading()

    def set_write_buffer_limits(self, high=None, low=None):
        return self._transport.set_write_buffer_limits(high, low)

    def get_write_buffer_size(self):
        return self._transport.get_write_buffer_size()

    def get_write_buffer_limits(self):
        return self._transport.get_write_buffer_limits()

    def write(self, data):
        return self._transport.write(aiofn_validate_and_maybe_copy_buffer(data))

    def writelines(self, list_of_data):
        lst = [aiofn_validate_and_maybe_copy_buffer(data)
               for data in list_of_data if data]
        self._transport.writelines(lst)

    def write_eof(self):
        return self._transport.write_eof()

    def can_write_eof(self):
        return self._transport.can_write_eof()

    def abort(self):
        return self._transport.abort()


class _WrappedProtocolBase(asyncio.BaseProtocol):
    __slots__ = ('_protocol', '_wrapped_transport')

    def __init__(self, protocol):
        self._protocol = protocol
        self._wrapped_transport = None

    def connection_made(self, transport):
        self._wrapped_transport = _WrappedTransport(transport)
        return self._protocol.connection_made(self._wrapped_transport)

    def connection_lost(self, exc):
        return self._protocol.connection_lost(exc)

    def pause_writing(self):
        return self._protocol.pause_writing()

    def resume_writing(self):
        return self._protocol.resume_writing()


class _WrappedProtocol(_WrappedProtocolBase, asyncio.Protocol):
    def data_received(self, data):
        return self._protocol.data_received(data)


class _WrappedBufferedProtocol(_WrappedProtocolBase, asyncio.BufferedProtocol):
    def get_buffer(self, sizehint):
        return self._protocol.get_buffer(sizehint)

    def buffer_updated(self, nbytes):
        return self._protocol.buffer_updated(nbytes)


async def create_connection(
        loop: asyncio.AbstractEventLoop,
        protocol_factory, host=None, port=None,
        *, ssl=None, family=0,
        proto=0, flags=0, sock=None,
        local_addr=None, server_hostname=None,
        ssl_handshake_timeout=None,
        ssl_shutdown_timeout=None,
        happy_eyeballs_delay=None,
        interleave=None,
        all_errors=False):
    """Connect to a TCP server.

    Create a streaming transport connection to a given internet host and
    port: socket family AF_INET or socket.AF_INET6 depending on host (or
    family if specified), socket type SOCK_STREAM. protocol_factory must be
    a callable returning a protocol instance.

    This method is a coroutine which will try to establish the connection
    in the background.  When successful, the coroutine returns a
    (transport, protocol) pair.
    """
    if _should_fallback_to_asyncio(loop):
        kwargs = {
            'host': host,
            'port': port,
            'ssl': ssl,
            'family': family,
            'proto': proto,
            'flags': flags,
            'sock': sock,
            'local_addr': local_addr,
            'server_hostname': server_hostname,
            'ssl_handshake_timeout': ssl_handshake_timeout
        }
        if sys.version_info >= (3, 11):
            kwargs['ssl_shutdown_timeout'] = ssl_shutdown_timeout
        if sys.version_info >= (3, 12) and _is_asyncio_loop(loop):
            kwargs['all_errors'] = all_errors
        if _is_asyncio_loop(loop):
            kwargs['interleave'] = interleave
            kwargs['happy_eyeballs_delay'] = happy_eyeballs_delay

        def wrapped_protocol_factory():
            user_protocol = protocol_factory()
            if aiofn_is_buffered_protocol(user_protocol):
                return _WrappedBufferedProtocol(user_protocol)
            else:
                return _WrappedProtocol(user_protocol)

        transport, wrapped_protocol = await loop.create_connection(wrapped_protocol_factory, **kwargs)
        wrapped_transport = wrapped_protocol._wrapped_transport
        protocol = wrapped_protocol._protocol
        wrapped_protocol._wrapped_transport = None

        return wrapped_transport, protocol

    if server_hostname is not None and not ssl:
        raise ValueError('server_hostname is only meaningful with ssl')

    if server_hostname is None and ssl:
        # Use host as default for server_hostname.  It is an error
        # if host is empty or not set, e.g. when an
        # already-connected socket was passed or when only a port
        # is given.  To avoid this error, you can pass
        # server_hostname='' -- this will bypass the hostname
        # check.  (This also means that if host is a numeric
        # IP/IPv6 address, we will attempt to verify that exact
        # address; this will probably fail, but it is possible to
        # create a certificate for a specific IP address, so we
        # don't judge it here.)
        if not host:
            raise ValueError('You must set server_hostname '
                             'when using ssl without a host')
        server_hostname = host

    if ssl_handshake_timeout is not None and not ssl:
        raise ValueError(
            'ssl_handshake_timeout is only meaningful with ssl')

    if ssl_shutdown_timeout is not None and not ssl:
        raise ValueError(
            'ssl_shutdown_timeout is only meaningful with ssl')

    if sock is not None:
        _check_ssl_socket(sock)

    if happy_eyeballs_delay is not None and interleave is None:
        # If using happy eyeballs, default to interleave addresses by family
        interleave = 1

    if host is not None or port is not None:
        if sock is not None:
            raise ValueError(
                'host/port and sock can not be specified at the same time')

        infos = await _ensure_resolved(
            (host, port), family=family,
            type=socket.SOCK_STREAM, proto=proto, flags=flags, loop=loop)
        if not infos:
            raise OSError('getaddrinfo() returned empty list')

        if local_addr is not None:
            laddr_infos = await _ensure_resolved(
                local_addr, family=family,
                type=socket.SOCK_STREAM, proto=proto,
                flags=flags, loop=loop)
            if not laddr_infos:
                raise OSError('getaddrinfo() returned empty list')
        else:
            laddr_infos = None

        if interleave:
            infos = _interleave_addrinfos(infos, interleave)

        exceptions = []
        if happy_eyeballs_delay is None:
            # not using happy eyeballs
            for addrinfo in infos:
                try:
                    sock = await _connect_sock(
                        loop, exceptions, addrinfo, laddr_infos)
                    break
                except OSError:
                    continue
        else:  # using happy eyeballs
            # TODO: wtf? fix asyncio.staggered private import
            sock = (await asyncio.staggered.staggered_race(
                (
                    # can't use functools.partial as it keeps a reference
                    # to exceptions
                    lambda addrinfo=addrinfo: _connect_sock(
                        loop, exceptions, addrinfo, laddr_infos
                    )
                    for addrinfo in infos
                ),
                happy_eyeballs_delay,
                loop=loop,
            ))[0]  # can't use sock, _, _ as it keeks a reference to exceptions

        if sock is None:
            exceptions = [exc for sub in exceptions for exc in sub]
            try:
                if all_errors:
                    raise ExceptionGroup("create_connection failed", exceptions)
                if len(exceptions) == 1:
                    raise exceptions[0]
                elif exceptions:
                    # If they all have the same str(), raise one.
                    model = str(exceptions[0])
                    if all(str(exc) == model for exc in exceptions):
                        raise exceptions[0]
                    # Raise a combined exception so the user can see all
                    # the various error messages.
                    raise OSError('Multiple exceptions: {}'.format(
                        ', '.join(str(exc) for exc in exceptions)))
                else:
                    # No exceptions were collected, raise a timeout error
                    raise TimeoutError('create_connection failed')
            finally:
                exceptions = None

    else:
        if sock is None:
            raise ValueError(
                'host and port was not specified and no sock specified')
        if sock.type != socket.SOCK_STREAM:
            # We allow AF_INET, AF_INET6, AF_UNIX as long as they
            # are SOCK_STREAM.
            # We support passing AF_UNIX sockets even though we have
            # a dedicated API for that: create_unix_connection.
            # Disallowing AF_UNIX in this method, breaks backwards
            # compatibility.
            raise ValueError(
                f'A Stream Socket was expected, got {sock!r}')

    transport, protocol = await _create_connection_transport(
        loop,
        sock, protocol_factory, ssl, server_hostname,
        ssl_handshake_timeout=ssl_handshake_timeout,
        ssl_shutdown_timeout=ssl_shutdown_timeout)
    if loop.get_debug():
        # Get the socket from the transport because SSL transport closes
        # the old socket and creates a new SSL socket
        sock = transport.get_extra_info('socket')
        _logger.debug("%r connected to %s:%r: (%r, %r)",
                      sock, host, port, transport, protocol)
    return transport, protocol


async def create_server(
        loop, protocol_factory, host=None, port=None,
        *,
        family=socket.AF_UNSPEC,
        flags=socket.AI_PASSIVE,
        sock=None,
        backlog=100,
        ssl=None,
        reuse_address=None,
        reuse_port=None,
        keep_alive=None,
        ssl_handshake_timeout=None,
        ssl_shutdown_timeout=None,
        start_serving=True):
    """Create a TCP server.

    The host parameter can be a string, in that case the TCP server is
    bound to host and port.

    The host parameter can also be a sequence of strings and in that case
    the TCP server is bound to all hosts of the sequence. If a host
    appears multiple times (possibly indirectly e.g. when hostnames
    resolve to the same IP address), the server is only bound once to that
    host.

    Return a Server object which can be used to stop the service.

    This method is a coroutine.
    """
    if _should_fallback_to_asyncio(loop):
        kwargs = {
            'host': host,
            'port': port,
            'family': family,
            'flags': flags,
            'sock': sock,
            'backlog': backlog,
            'ssl': ssl,
            'reuse_address': reuse_address,
            'reuse_port': reuse_port,
            'ssl_handshake_timeout': ssl_handshake_timeout,
            'start_serving': start_serving
        }
        if sys.version_info >= (3, 13) and _is_asyncio_loop(loop):
            kwargs['keep_alive'] = keep_alive

        if sys.version_info >= (3, 11):
            kwargs['ssl_shutdown_timeout'] = ssl_shutdown_timeout

        def wrapped_protocol_factory():
            user_protocol = protocol_factory()
            if aiofn_is_buffered_protocol(user_protocol):
                return _WrappedBufferedProtocol(user_protocol)
            else:
                return _WrappedProtocol(user_protocol)

        return await loop.create_server(wrapped_protocol_factory, **kwargs)

    if isinstance(ssl, bool):
        raise TypeError('ssl argument must be an SSLContext or None')

    if ssl_handshake_timeout is not None and ssl is None:
        raise ValueError(
            'ssl_handshake_timeout is only meaningful with ssl')

    if ssl_shutdown_timeout is not None and ssl is None:
        raise ValueError(
            'ssl_shutdown_timeout is only meaningful with ssl')

    if sock is not None:
        _check_ssl_socket(sock)

    if host is not None or port is not None:
        if sock is not None:
            raise ValueError(
                'host/port and sock can not be specified at the same time')

        if reuse_address is None:
            reuse_address = os.name == "posix" and sys.platform != "cygwin"
        sockets = []
        if host == '':
            hosts = [None]
        elif (isinstance(host, str) or
              not isinstance(host, collections.abc.Iterable)):
            hosts = [host]
        else:
            hosts = host

        fs = [_create_server_getaddrinfo(loop, host, port, family=family, flags=flags)
              for host in hosts]
        infos = await asyncio.gather(*fs)
        infos = set(itertools.chain.from_iterable(infos))

        completed = False
        try:
            for res in infos:
                af, socktype, proto, canonname, sa = res
                try:
                    sock = socket.socket(af, socktype, proto)
                except socket.error:
                    # Assume it's a bad family/type/protocol combination.
                    if loop.get_debug():
                        _logger.warning('create_server() failed to create '
                                        'socket.socket(%r, %r, %r)',
                                        af, socktype, proto, exc_info=True)
                    continue
                sockets.append(sock)
                if reuse_address:
                    sock.setsockopt(
                        socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
                # Since Linux 6.12.9, SO_REUSEPORT is not allowed
                # on other address families than AF_INET/AF_INET6.
                if reuse_port and af in (socket.AF_INET, socket.AF_INET6):
                    _set_reuseport(sock)
                if keep_alive:
                    sock.setsockopt(
                        socket.SOL_SOCKET, socket.SO_KEEPALIVE, True)
                # Disable IPv4/IPv6 dual stack support (enabled by
                # default on Linux) which makes a single socket
                # listen on both address families.
                if (_HAS_IPv6 and
                        af == socket.AF_INET6 and
                        hasattr(socket, 'IPPROTO_IPV6')):
                    sock.setsockopt(socket.IPPROTO_IPV6,
                                    socket.IPV6_V6ONLY,
                                    True)
                try:
                    sock.bind(sa)
                except OSError as err:
                    msg = ('error while attempting '
                           'to bind on address %r: %s'
                           % (sa, str(err).lower()))
                    if err.errno == errno.EADDRNOTAVAIL:
                        # Assume the family is not enabled (bpo-30945)
                        sockets.pop()
                        sock.close()
                        if loop.get_debug():
                            _logger.warning(msg)
                        continue
                    raise OSError(err.errno, msg) from None

            if not sockets:
                raise OSError('could not bind on any address out of %r'
                              % ([info[4] for info in infos],))

            completed = True
        finally:
            if not completed:
                for sock in sockets:
                    sock.close()
    else:
        if sock is None:
            raise ValueError('Neither host/port nor sock were specified')
        if sock.type != socket.SOCK_STREAM:
            raise ValueError(f'A Stream Socket was expected, got {sock!r}')
        sockets = [sock]

    for sock in sockets:
        sock.setblocking(False)

    server = Server(
        loop, sockets, protocol_factory,
        ssl, backlog, ssl_handshake_timeout,
        ssl_shutdown_timeout)
    if start_serving:
        server._start_serving()
        # Skip one loop iteration so that all 'loop.add_reader'
        # go through.
        await asyncio.sleep(0)

    if loop.get_debug():
        _logger.info("%r is serving", server)
    return server


class Server(asyncio.AbstractServer):

    def __init__(self, loop, sockets, protocol_factory, ssl_context, backlog,
                 ssl_handshake_timeout, ssl_shutdown_timeout=None):
        self._loop = loop
        self._sockets = sockets
        # Weak references so we don't break Transport's ability to
        # detect abandoned transports
        self._clients = weakref.WeakSet()
        self._waiters = []
        self._protocol_factory = protocol_factory
        self._backlog = backlog
        self._ssl_context = ssl_context
        self._ssl_handshake_timeout = ssl_handshake_timeout
        self._ssl_shutdown_timeout = ssl_shutdown_timeout
        self._serving = False
        self._serving_forever_fut = None

    def __repr__(self):
        return f'<{self.__class__.__name__} sockets={self.sockets!r}>'

    def _attach(self, transport):
        assert self._sockets is not None
        self._clients.add(transport)

    def _detach(self, transport):
        self._clients.discard(transport)
        if len(self._clients) == 0 and self._sockets is None:
            self._wakeup()

    def _wakeup(self):
        waiters = self._waiters
        self._waiters = None
        for waiter in waiters:
            if not waiter.done():
                waiter.set_result(None)

    def _start_serving(self):
        if self._serving:
            return
        self._serving = True
        for sock in self._sockets:
            sock.listen(self._backlog)
            _start_serving(
                self._loop,
                self._protocol_factory, sock, self._ssl_context,
                self, self._backlog, self._ssl_handshake_timeout,
                self._ssl_shutdown_timeout)

    def get_loop(self):
        return self._loop

    def is_serving(self):
        return self._serving

    @property
    def sockets(self):
        if self._sockets is None:
            return ()
        return tuple(asyncio.trsock.TransportSocket(s) for s in self._sockets)

    def close(self):
        sockets = self._sockets
        if sockets is None:
            return
        self._sockets = None

        for sock in sockets:
            _stop_serving(self._loop, sock)

        self._serving = False

        if (self._serving_forever_fut is not None and
                not self._serving_forever_fut.done()):
            self._serving_forever_fut.cancel()
            self._serving_forever_fut = None

        if len(self._clients) == 0:
            self._wakeup()

    def close_clients(self):
        for transport in self._clients.copy():
            transport.close()

    def abort_clients(self):
        for transport in self._clients.copy():
            transport.abort()

    async def start_serving(self):
        self._start_serving()
        # Skip one loop iteration so that all 'loop.add_reader'
        # go through.
        await asyncio.sleep(0)

    async def serve_forever(self):
        if self._serving_forever_fut is not None:
            raise RuntimeError(
                f'server {self!r} is already being awaited on serve_forever()')
        if self._sockets is None:
            raise RuntimeError(f'server {self!r} is closed')

        self._start_serving()
        self._serving_forever_fut = self._loop.create_future()

        try:
            await self._serving_forever_fut
        except asyncio.CancelledError:
            try:
                self.close()
                await self.wait_closed()
            finally:
                raise
        finally:
            self._serving_forever_fut = None

    async def wait_closed(self):
        """Wait until server is closed and all connections are dropped.

        - If the server is not closed, wait.
        - If it is closed, but there are still active connections, wait.

        Anyone waiting here will be unblocked once both conditions
        (server is closed and all connections have been dropped)
        have become true, in either order.

        Historical note: In 3.11 and before, this was broken, returning
        immediately if the server was already closed, even if there
        were still active connections. An attempted fix in 3.12.0 was
        still broken, returning immediately if the server was still
        open and there were no active connections. Hopefully in 3.12.1
        we have it right.
        """
        # Waiters are unblocked by self._wakeup(), which is called
        # from two places: self.close() and self._detach(), but only
        # when both conditions have become true. To signal that this
        # has happened, self._wakeup() sets self._waiters to None.
        if self._waiters is None:
            return
        waiter = self._loop.create_future()
        self._waiters.append(waiter)
        await waiter


def _accept_connection(
        loop, protocol_factory, sock,
        sslcontext=None, server=None, backlog=100,
        ssl_handshake_timeout=constants.SSL_HANDSHAKE_TIMEOUT,
        ssl_shutdown_timeout=constants.SSL_SHUTDOWN_TIMEOUT):
    # This method is only called once for each event loop tick where the
    # listening socket has triggered an EVENT_READ. There may be multiple
    # connections waiting for an .accept() so it is called in a loop.
    # See https://bugs.python.org/issue27906 for more details.
    for _ in range(backlog + 1):
        try:
            conn, addr = sock.accept()
            if loop.get_debug():
                _logger.debug("%r got a new connection from %r: %r",
                              server, addr, conn)
            conn.setblocking(False)
        except ConnectionAbortedError:
            # Discard connections that were aborted before accept().
            continue
        except (BlockingIOError, InterruptedError):
            # Early exit because of a signal or
            # the socket accept buffer is empty.
            return
        except OSError as exc:
            # There's nowhere to send the error, so just log it.
            if exc.errno in (errno.EMFILE, errno.ENFILE,
                             errno.ENOBUFS, errno.ENOMEM):
                # Some platforms (e.g. Linux keep reporting the FD as
                # ready, so we remove the read handler temporarily.
                # We'll try again in a while.
                loop.call_exception_handler({
                    'message': 'socket.accept() out of system resource',
                    'exception': exc,
                    'socket': TransportSocket(sock),
                })
                loop.remove_reader(sock.fileno())
                loop.call_later(constants.ACCEPT_RETRY_DELAY,
                                _start_serving,
                                loop, protocol_factory, sock, sslcontext, server,
                                backlog, ssl_handshake_timeout,
                                ssl_shutdown_timeout)
            else:
                raise  # The event loop will catch, log and ignore it.
        else:
            extra = {'peername': addr}
            accept = _accept_connection2(
                loop, protocol_factory, conn, extra, sslcontext, server,
                ssl_handshake_timeout, ssl_shutdown_timeout)
            asyncio.create_task(accept)


async def _accept_connection2(
        loop,
        protocol_factory, conn, extra,
        sslcontext=None, server=None,
        ssl_handshake_timeout=constants.SSL_HANDSHAKE_TIMEOUT,
        ssl_shutdown_timeout=constants.SSL_SHUTDOWN_TIMEOUT):
    protocol = None
    transport = None
    try:
        protocol = protocol_factory()
        waiter = loop.create_future()
        if sslcontext:
            transport = _make_ssl_transport(
                loop,
                conn, protocol, sslcontext, waiter=waiter,
                server_side=True, extra=extra, server=server,
                ssl_handshake_timeout=ssl_handshake_timeout,
                ssl_shutdown_timeout=ssl_shutdown_timeout)
        else:
            transport = _make_socket_transport(
                loop,
                conn, protocol, waiter=waiter, extra=extra,
                server=server)

        try:
            await waiter
        except BaseException:
            transport.close()
            # gh-109534: When an exception is raised by the SSLProtocol object the
            # exception set in this future can keep the protocol object alive and
            # cause a reference cycle.
            waiter = None
            raise
            # It's now up to the protocol to handle the connection.

    except (SystemExit, KeyboardInterrupt):
        raise
    except BaseException as exc:
        if loop.get_debug():
            context = {
                'message':
                    'Error on transport creation for incoming connection',
                'exception': exc,
            }
            if protocol is not None:
                context['protocol'] = protocol
            if transport is not None:
                context['transport'] = transport
            loop.call_exception_handler(context)


def _start_serving(loop, protocol_factory, sock,
                   sslcontext=None, server=None, backlog=100,
                   ssl_handshake_timeout=constants.SSL_HANDSHAKE_TIMEOUT,
                   ssl_shutdown_timeout=constants.SSL_SHUTDOWN_TIMEOUT):
    loop.add_reader(sock.fileno(), _accept_connection, loop,
                    protocol_factory, sock, sslcontext, server, backlog,
                    ssl_handshake_timeout, ssl_shutdown_timeout)



async def _ensure_resolved(address, *,
                           family=0, type=socket.SOCK_STREAM,
                           proto=0, flags=0, loop):
    host, port = address[:2]
    info = _ipaddr_info(host, port, family, type, proto, *address[2:])
    if info is not None:
        # "host" is already a resolved IP.
        return [info]
    else:
        return await loop.getaddrinfo(host, port, family=family, type=type,
                                      proto=proto, flags=flags)


def _ipaddr_info(host, port, family, type, proto, flowinfo=0, scopeid=0):
    # Try to skip getaddrinfo if "host" is already an IP. Users might have
    # handled name resolution in their own code and pass in resolved IPs.
    if not hasattr(socket, 'inet_pton'):
        return

    if proto not in {0, socket.IPPROTO_TCP, socket.IPPROTO_UDP} or \
            host is None:
        return None

    if type == socket.SOCK_STREAM:
        proto = socket.IPPROTO_TCP
    elif type == socket.SOCK_DGRAM:
        proto = socket.IPPROTO_UDP
    else:
        return None

    if port is None:
        port = 0
    elif isinstance(port, bytes) and port == b'':
        port = 0
    elif isinstance(port, str) and port == '':
        port = 0
    else:
        # If port's a service name like "http", don't skip getaddrinfo.
        try:
            port = int(port)
        except (TypeError, ValueError):
            return None

    if family == socket.AF_UNSPEC:
        afs = [socket.AF_INET]
        if _HAS_IPv6:
            afs.append(socket.AF_INET6)
    else:
        afs = [family]

    if isinstance(host, bytes):
        host = host.decode('idna')
    if '%' in host:
        # Linux's inet_pton doesn't accept an IPv6 zone index after host,
        # like '::1%lo0'.
        return None

    for af in afs:
        try:
            socket.inet_pton(af, host)
            # The host has already been resolved.
            if _HAS_IPv6 and af == socket.AF_INET6:
                return af, type, proto, '', (host, port, flowinfo, scopeid)
            else:
                return af, type, proto, '', (host, port)
        except OSError:
            pass

    # "host" is not an IP address.
    return None


async def _connect_sock(loop, exceptions, addr_info, local_addr_infos=None):
    """Create, bind and connect one socket."""
    my_exceptions = []
    exceptions.append(my_exceptions)
    family, type_, proto, _, address = addr_info
    sock = None
    try:
        try:
            sock = socket.socket(family=family, type=type_, proto=proto)
            sock.setblocking(False)
            if local_addr_infos is not None:
                for lfamily, _, _, _, laddr in local_addr_infos:
                    # skip local addresses of different family
                    if lfamily != family:
                        continue
                    try:
                        sock.bind(laddr)
                        break
                    except OSError as exc:
                        msg = (
                            f'error while attempting to bind on '
                            f'address {laddr!r}: {str(exc).lower()}'
                        )
                        exc = OSError(exc.errno, msg)
                        my_exceptions.append(exc)
                else:  # all bind attempts failed
                    if my_exceptions:
                        raise my_exceptions.pop()
                    else:
                        raise OSError(f"no matching local address with {family=} found")
            await loop.sock_connect(sock, address)
            return sock
        except OSError as exc:
            my_exceptions.append(exc)
            raise
    except:
        if sock is not None:
            try:
                sock.close()
            except OSError:
                # An error when closing a newly created socket is
                # not important, but it can overwrite more important
                # non-OSError error. So ignore it.
                pass
        raise
    finally:
        exceptions = my_exceptions = None


async def _create_connection_transport(
        loop,
        sock, protocol_factory, ssl,
        server_hostname, server_side=False,
        ssl_handshake_timeout=None,
        ssl_shutdown_timeout=None):
    sock.setblocking(False)

    protocol = protocol_factory()
    waiter = loop.create_future()
    if ssl:
        sslcontext = None if isinstance(ssl, bool) else ssl
        transport = _make_ssl_transport(
            loop,
            sock, protocol, sslcontext, waiter,
            server_side=server_side, server_hostname=server_hostname,
            ssl_handshake_timeout=ssl_handshake_timeout,
            ssl_shutdown_timeout=ssl_shutdown_timeout)
    else:
        transport = _make_socket_transport(loop, sock, protocol, waiter)

    try:
        await waiter
    except:
        transport.close()
        raise

    return transport, protocol


def _check_ssl_socket(sock):
    if isinstance(sock, ssl.SSLSocket):
        raise TypeError("Socket cannot be of type SSLSocket")


def _interleave_addrinfos(addrinfos, first_address_family_count=1):
    """Interleave list of addrinfo tuples by family."""
    # Group addresses by family
    addrinfos_by_family = collections.OrderedDict()
    for addr in addrinfos:
        family = addr[0]
        if family not in addrinfos_by_family:
            addrinfos_by_family[family] = []
        addrinfos_by_family[family].append(addr)
    addrinfos_lists = list(addrinfos_by_family.values())

    reordered = []
    if first_address_family_count > 1:
        reordered.extend(addrinfos_lists[0][:first_address_family_count - 1])
        del addrinfos_lists[0][:first_address_family_count - 1]
    reordered.extend(
        a for a in itertools.chain.from_iterable(
            itertools.zip_longest(*addrinfos_lists)
        ) if a is not None)
    return reordered


def _make_ssl_transport(
        loop, rawsock, protocol, sslcontext, waiter=None,
        *, server_side=False, server_hostname=None,
        extra=None, server=None,
        ssl_handshake_timeout=60.0,
        ssl_shutdown_timeout=30.0,
):
    ssl_protocol = SSLProtocol(
        loop, protocol, sslcontext, waiter,
        server_side, server_hostname,
        ssl_handshake_timeout=ssl_handshake_timeout,
        ssl_shutdown_timeout=ssl_shutdown_timeout
    )
    SelectorSocketTransport(loop, rawsock, ssl_protocol,
                            extra=extra, server=server)
    return ssl_protocol.get_app_transport()


def _make_socket_transport(loop, sock, protocol, waiter=None, *,
                           extra=None, server=None):
    return SelectorSocketTransport(loop, sock, protocol, waiter,
                                   extra, server)


def _set_reuseport(sock):
    if not hasattr(socket, 'SO_REUSEPORT'):
        raise ValueError('reuse_port not supported by socket module')
    else:
        try:
            sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
        except OSError:
            raise ValueError('reuse_port not supported by socket module, '
                             'SO_REUSEPORT defined but not implemented.')


async def _create_server_getaddrinfo(loop, host, port, family, flags):
    infos = await _ensure_resolved((host, port), family=family,
                                   type=socket.SOCK_STREAM,
                                   flags=flags, loop=loop)
    if not infos:
        raise OSError(f'getaddrinfo({host!r}) returned empty list')
    return infos


def _stop_serving(loop, sock):
    loop.remove_reader(sock.fileno())
    sock.close()
