Returns an ``ssl.SSLSocket`` wrapping the given socket.

    ``ssl_options`` may be either an `ssl.SSLContext` object or a
    dictionary (as accepted by `ssl_options_to_context`).  Additional
    keyword arguments are passed to ``wrap_socket`` (either the
    `~ssl.SSLContext` method or the `ssl` module function as
    appropriate).

def ssl_wrap_socket(
    socket: socket.socket,
    ssl_options: Union[Dict[str, Any], ssl.SSLContext],
    server_hostname: str = None,
    **kwargs: Any
) -> ssl.SSLSocket:
    """Returns an ``ssl.SSLSocket`` wrapping the given socket.

    ``ssl_options`` may be either an `ssl.SSLContext` object or a
    dictionary (as accepted by `ssl_options_to_context`).  Additional
    keyword arguments are passed to ``wrap_socket`` (either the
    `~ssl.SSLContext` method or the `ssl` module function as
    appropriate).
    """
    context = ssl_options_to_context(ssl_options)
    if ssl.HAS_SNI:
        # In python 3.4, wrap_socket only accepts the server_hostname
        # argument if HAS_SNI is true.
        # TODO: add a unittest (python added server-side SNI support in 3.4)
        # In the meantime it can be manually tested with
        # python3 -m tornado.httpclient https://sni.velox.ch
        return context.wrap_socket(socket, server_hostname=server_hostname, **kwargs)
    else:
        return context.wrap_socket(socket, **kwargs)