<commit_before># coding: utf-8
from __future__ import unicode_literals, division, absolute_import, print_function

import sys
import re
import socket as socket_
import select
import numbers

from asn1crypto import x509

from ._libssl import libssl, LibsslConst
from ._libcrypto import libcrypto, handle_openssl_error, peek_openssl_error
from .._errors import pretty_message
from .._ffi import null, bytes_from_buffer, buffer_from_bytes, is_null, buffer_pointer
from .._types import type_name, str_cls, byte_cls, int_types
from ..errors import TLSError
from .._tls import (
    extract_chain,
    get_dh_params_length,
    parse_session_info,
    raise_client_auth,
    raise_dh_params,
    raise_disconnection,
    raise_expired_not_yet_valid,
    raise_handshake,
    raise_hostname,
    raise_no_issuer,
    raise_protocol_error,
    raise_self_signed,
    raise_verification,
    raise_weak_signature,
)
from .asymmetric import load_certificate, Certificate
from ..keys import parse_certificate

if sys.version_info < (3,):
    range = xrange  # noqa


__all__ = [
    'TLSSession',
    'TLSSocket',
]


_line_regex = re.compile(b'(\r\n|\r|\n)')
_PROTOCOL_MAP = {
    'SSLv2': LibsslConst.SSL_OP_NO_SSLv2,
    'SSLv3': LibsslConst.SSL_OP_NO_SSLv3,
    'TLSv1': LibsslConst.SSL_OP_NO_TLSv1,
    'TLSv1.1': LibsslConst.SSL_OP_NO_TLSv1_1,
    'TLSv1.2': LibsslConst.SSL_OP_NO_TLSv1_2,
}


class TLSSession(object):
    """
    A TLS session object that multiple TLSSocket objects can share for the
    sake of session reuse
    """

    _protocols = None
    _ciphers = None
    _manual_validation = None
    _extra_trust_roots = None
    _ssl_ctx = None
    _ssl_session = None

    def __init__(self, protocol=None, manual_validation=False, extra_trust_roots=None):
        """
        :param protocol:
            A unicode string or set of unicode strings representing allowable
            protocols to negotiate with the server:

             - "TLSv1.2"
             - "TLSv1.1"
             - "TLSv1"
             - "SSLv3"

            Default is: {"TLSv1", "TLSv1.1", "TLSv1.2"}

        :param manual_validation:
            If certificate and certificate path validation should be skipped
            and left to the developer to implement

        :param extra_trust_roots:
            A list containing one or more certificates to be treated as trust
            roots, in one of the following formats:
             - A byte string of the DER encoded certificate
             - A unicode string of the certificate filename
             - An asn1crypto.x509.Certificate object
             - An oscrypto.asymmetric.Certificate object

        :raises:
            ValueError - when any of the parameters contain an invalid value
            TypeError - when any of the parameters are of the wrong type
            OSError - when an error is returned by the OS crypto library
        """

        if not isinstance(manual_validation, bool):
            raise TypeError(pretty_message(
                '''
                manual_validation must be a boolean, not %s
                ''',
                type_name(manual_validation)
            ))

        self._manual_validation = manual_validation

        if protocol is None:
            protocol = set(['TLSv1', 'TLSv1.1', 'TLSv1.2'])

        if isinstance(protocol, str_cls):
            protocol = set([protocol])
        elif not isinstance(protocol, set):
            raise TypeError(pretty_message(
                '''
                protocol must be a unicode string or set of unicode strings,
                not %s
                ''',
                type_name(protocol)
            ))

        valid_protocols = set(['SSLv3', 'TLSv1', 'TLSv1.1', 'TLSv1.2'])
        unsupported_protocols = protocol - valid_protocols
        if unsupported_protocols:
            raise ValueError(pretty_message(
                '''
                protocol must contain only the unicode strings "SSLv3", "TLSv1",
                "TLSv1.1", "TLSv1.2", not %s
                ''',
                repr(unsupported_protocols)
            ))

        self._protocols = protocol

        self._extra_trust_roots = []
        if extra_trust_roots:
            for extra_trust_root in extra_trust_roots:
                if isinstance(extra_trust_root, Certificate):
                    extra_trust_root = extra_trust_root.asn1
                elif isinstance(extra_trust_root, byte_cls):
                    extra_trust_root = parse_certificate(extra_trust_root)
                elif isinstance(extra_trust_root, str_cls):
                    with open(extra_trust_root, 'rb') as f:
                        extra_trust_root = parse_certificate(f.read())
                elif not isinstance(extra_trust_root, x509.Certificate):
                    raise TypeError(pretty_message(
                        '''
                        extra_trust_roots must be a list of byte strings, unicode
                        strings, asn1crypto.x509.Certificate objects or
                        oscrypto.asymmetric.Certificate objects, not %s
                        ''',
                        type_name(extra_trust_root)
                    ))
                self._extra_trust_roots.append(extra_trust_root)

        ssl_ctx = None
        try:
            ssl_ctx = libssl.SSL_CTX_new(libssl.SSLv23_method())
            if is_null(ssl_ctx):
                handle_openssl_error(0)
            self._ssl_ctx = ssl_ctx

            libssl.SSL_CTX_set_timeout(ssl_ctx, 600)

            # Allow caching SSL sessions
            libssl.SSL_CTX_ctrl(
                ssl_ctx,
                LibsslConst.SSL_CTRL_SET_SESS_CACHE_MODE,
                LibsslConst.SSL_SESS_CACHE_CLIENT,
                null()
            )

            result = libssl.SSL_CTX_set_default_verify_paths(ssl_ctx)
            handle_openssl_error(result)

            verify_mode = LibsslConst.SSL_VERIFY_NONE if manual_validation else LibsslConst.SSL_VERIFY_PEER
            libssl.SSL_CTX_set_verify(ssl_ctx, verify_mode, null())

            # Modern cipher suite list from https://wiki.mozilla.org/Security/Server_Side_TLS late August 2015
            result = libssl.SSL_CTX_set_cipher_list(
                ssl_ctx,
                (
                    b'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:'
                    b'ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:'
                    b'DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:'
                    b'kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:'
                    b'ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:'
                    b'ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:'
                    b'DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:'
                    b'DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:'
                    b'AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:'
                    b'AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:'
                    b'!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:'
                    b'!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA'
                )
            )
            handle_openssl_error(result)

            disabled_protocols = set(['SSLv2'])
            disabled_protocols |= (valid_protocols - self._protocols)
            for disabled_protocol in disabled_protocols:
                libssl.SSL_CTX_ctrl(
                    ssl_ctx,
                    LibsslConst.SSL_CTRL_OPTIONS,
                    _PROTOCOL_MAP[disabled_protocol],
                    null()
                )

            if self._extra_trust_roots:
                x509_store = libssl.SSL_CTX_get_cert_store(ssl_ctx)
                for cert in self._extra_trust_roots:
                    oscrypto_cert = load_certificate(cert)
                    result = libssl.X509_STORE_add_cert(
                        x509_store,
                        oscrypto_cert.x509
                    )
                    handle_openssl_error(result)

        except (Exception):
            if ssl_ctx:
                libssl.SSL_CTX_free(ssl_ctx)
            self._ssl_ctx = None
            raise

    def __del__(self):
        if self._ssl_ctx:
            libssl.SSL_CTX_free(self._ssl_ctx)
            self._ssl_ctx = None

        if self._ssl_session:
            libssl.SSL_SESSION_free(self._ssl_session)
            self._ssl_session = None


class TLSSocket(object):
    """
    A wrapper around a socket.socket that adds TLS
    """

    _socket = None
    _session = None

    _ssl = None
    _rbio = None
    _wbio = None
    _bio_write_buffer = None
    _read_buffer = None

    _decrypted_bytes = None

    _hostname = None

    _certificate = None
    _intermediates = None

    _protocol = None
    _cipher_suite = None
    _compression = None
    _session_id = None
    _session_ticket = None

    _local_closed = False

    @classmethod
    def wrap(cls, socket, hostname, session=None):
        """
        Takes an existing socket and adds TLS

        :param socket:
            A socket.socket object to wrap with TLS

        :param hostname:
            A unicode string of the hostname or IP the socket is connected to

        :param session:
            An existing TLSSession object to allow for session reuse, specific
            protocol or manual certificate validation

        :raises:
            ValueError - when any of the parameters contain an invalid value
            TypeError - when any of the parameters are of the wrong type
            OSError - when an error is returned by the OS crypto library
        """

        if not isinstance(socket, socket_.socket):
            raise TypeError(pretty_message(
                '''
                socket must be an instance of socket.socket, not %s
                ''',
                type_name(socket)
            ))

        if not isinstance(hostname, str_cls):
            raise TypeError(pretty_message(
                '''
                hostname must be a unicode string, not %s
                ''',
                type_name(hostname)
            ))

        if session is not None and not isinstance(session, TLSSession):
            raise TypeError(pretty_message(
                '''
                session must be an instance of oscrypto.tls.TLSSession, not %s
                ''',
                type_name(session)
            ))

        new_socket = cls(None, None, session=session)
        new_socket._socket = socket
        new_socket._hostname = hostname
        new_socket._handshake()

        return new_socket

    def __init__(self, address, port, timeout=10, session=None):
        """
        :param address:
            A unicode string of the domain name or IP address to conenct to

        :param port:
            An integer of the port number to connect to

        :param timeout:
            An integer timeout to use for the socket

        :param session:
            An oscrypto.tls.TLSSession object to allow for session reuse and
            controlling the protocols and validation performed
        """

        self._decrypted_bytes = b''

        if address is None and port is None:
            self._socket = None

        else:
            if not isinstance(address, str_cls):
                raise TypeError(pretty_message(
                    '''
                    address must be a unicode string, not %s
                    ''',
                    type_name(address)
                ))

            if not isinstance(port, int_types):
                raise TypeError(pretty_message(
                    '''
                    port must be an integer, not %s
                    ''',
                    type_name(port)
                ))

            if timeout is not None and not isinstance(timeout, numbers.Number):
                raise TypeError(pretty_message(
                    '''
                    timeout must be a number, not %s
                    ''',
                    type_name(timeout)
                ))

            self._socket = socket_.create_connection((address, port), timeout)
            self._socket.settimeout(timeout)

        if session is None:
            session = TLSSession()

        elif not isinstance(session, TLSSession):
            raise TypeError(pretty_message(
                '''
                session must be an instance of oscrypto.tls.TLSSession, not %s
                ''',
                type_name(session)
            ))

        self._session = session

        if self._socket:
            self._hostname = address
            self._handshake()

    def _handshake(self):
        """
        Perform an initial TLS handshake
        """

        ssl = None
        rbio = None
        wbio = None

        try:
            ssl = libssl.SSL_new(self._session._ssl_ctx)
            if is_null(ssl):
                handle_openssl_error(0)

            mem_bio = libssl.BIO_s_mem()

            rbio = libssl.BIO_new(mem_bio)
            if is_null(rbio):
                handle_openssl_error(0)

            wbio = libssl.BIO_new(mem_bio)
            if is_null(wbio):
                handle_openssl_error(0)

            libssl.SSL_set_bio(ssl, rbio, wbio)

            utf8_domain = self._hostname.encode('utf-8')
            libssl.SSL_ctrl(
                ssl,
                LibsslConst.SSL_CTRL_SET_TLSEXT_HOSTNAME,
                LibsslConst.TLSEXT_NAMETYPE_host_name,
                utf8_domain
            )

            libssl.SSL_set_connect_state(ssl)

            if self._session._ssl_session:
                libssl.SSL_set_session(ssl, self._session._ssl_session)

            self._bio_write_buffer = buffer_from_bytes(8192)
            self._read_buffer = buffer_from_bytes(8192)

            handshake_server_bytes = b''
            handshake_client_bytes = b''

            self._ssl = ssl
            self._rbio = rbio
            self._wbio = wbio

            while True:
                result = libssl.SSL_do_handshake(self._ssl)
                handshake_client_bytes += self._raw_write(self._wbio)

                if result == 1:
                    break

                error = libssl.SSL_get_error(ssl, result)
                if error == LibsslConst.SSL_ERROR_WANT_READ:
                    chunk = self._raw_read(self._rbio)
                    if chunk == b'':
                        if handshake_server_bytes == b'':
                            raise_disconnection()
                        raise_protocol_error(handshake_server_bytes)
                    handshake_server_bytes += chunk

                elif error == LibsslConst.SSL_ERROR_WANT_WRITE:
                    handshake_client_bytes += self._raw_write(self._wbio)

                else:
                    info = peek_openssl_error()

                    dh_key_info = (<commit_msg>Properly detect specific client auth failures with OpenSSL version of tls.TLSSocket()<commit_after># coding: utf-8
from __future__ import unicode_literals, division, absolute_import, print_function

import sys
import re
import socket as socket_
import select
import numbers

from asn1crypto import x509

from ._libssl import libssl, LibsslConst
from ._libcrypto import libcrypto, handle_openssl_error, peek_openssl_error
from .._errors import pretty_message
from .._ffi import null, bytes_from_buffer, buffer_from_bytes, is_null, buffer_pointer
from .._types import type_name, str_cls, byte_cls, int_types
from ..errors import TLSError
from .._tls import (
    detect_client_auth_request,
    extract_chain,
    get_dh_params_length,
    parse_session_info,
    raise_client_auth,
    raise_dh_params,
    raise_disconnection,
    raise_expired_not_yet_valid,
    raise_handshake,
    raise_hostname,
    raise_no_issuer,
    raise_protocol_error,
    raise_self_signed,
    raise_verification,
    raise_weak_signature,
)
from .asymmetric import load_certificate, Certificate
from ..keys import parse_certificate

if sys.version_info < (3,):
    range = xrange  # noqa


__all__ = [
    'TLSSession',
    'TLSSocket',
]


_line_regex = re.compile(b'(\r\n|\r|\n)')
_PROTOCOL_MAP = {
    'SSLv2': LibsslConst.SSL_OP_NO_SSLv2,
    'SSLv3': LibsslConst.SSL_OP_NO_SSLv3,
    'TLSv1': LibsslConst.SSL_OP_NO_TLSv1,
    'TLSv1.1': LibsslConst.SSL_OP_NO_TLSv1_1,
    'TLSv1.2': LibsslConst.SSL_OP_NO_TLSv1_2,
}


class TLSSession(object):
    """
    A TLS session object that multiple TLSSocket objects can share for the
    sake of session reuse
    """

    _protocols = None
    _ciphers = None
    _manual_validation = None
    _extra_trust_roots = None
    _ssl_ctx = None
    _ssl_session = None

    def __init__(self, protocol=None, manual_validation=False, extra_trust_roots=None):
        """
        :param protocol:
            A unicode string or set of unicode strings representing allowable
            protocols to negotiate with the server:

             - "TLSv1.2"
             - "TLSv1.1"
             - "TLSv1"
             - "SSLv3"

            Default is: {"TLSv1", "TLSv1.1", "TLSv1.2"}

        :param manual_validation:
            If certificate and certificate path validation should be skipped
            and left to the developer to implement

        :param extra_trust_roots:
            A list containing one or more certificates to be treated as trust
            roots, in one of the following formats:
             - A byte string of the DER encoded certificate
             - A unicode string of the certificate filename
             - An asn1crypto.x509.Certificate object
             - An oscrypto.asymmetric.Certificate object

        :raises:
            ValueError - when any of the parameters contain an invalid value
            TypeError - when any of the parameters are of the wrong type
            OSError - when an error is returned by the OS crypto library
        """

        if not isinstance(manual_validation, bool):
            raise TypeError(pretty_message(
                '''
                manual_validation must be a boolean, not %s
                ''',
                type_name(manual_validation)
            ))

        self._manual_validation = manual_validation

        if protocol is None:
            protocol = set(['TLSv1', 'TLSv1.1', 'TLSv1.2'])

        if isinstance(protocol, str_cls):
            protocol = set([protocol])
        elif not isinstance(protocol, set):
            raise TypeError(pretty_message(
                '''
                protocol must be a unicode string or set of unicode strings,
                not %s
                ''',
                type_name(protocol)
            ))

        valid_protocols = set(['SSLv3', 'TLSv1', 'TLSv1.1', 'TLSv1.2'])
        unsupported_protocols = protocol - valid_protocols
        if unsupported_protocols:
            raise ValueError(pretty_message(
                '''
                protocol must contain only the unicode strings "SSLv3", "TLSv1",
                "TLSv1.1", "TLSv1.2", not %s
                ''',
                repr(unsupported_protocols)
            ))

        self._protocols = protocol

        self._extra_trust_roots = []
        if extra_trust_roots:
            for extra_trust_root in extra_trust_roots:
                if isinstance(extra_trust_root, Certificate):
                    extra_trust_root = extra_trust_root.asn1
                elif isinstance(extra_trust_root, byte_cls):
                    extra_trust_root = parse_certificate(extra_trust_root)
                elif isinstance(extra_trust_root, str_cls):
                    with open(extra_trust_root, 'rb') as f:
                        extra_trust_root = parse_certificate(f.read())
                elif not isinstance(extra_trust_root, x509.Certificate):
                    raise TypeError(pretty_message(
                        '''
                        extra_trust_roots must be a list of byte strings, unicode
                        strings, asn1crypto.x509.Certificate objects or
                        oscrypto.asymmetric.Certificate objects, not %s
                        ''',
                        type_name(extra_trust_root)
                    ))
                self._extra_trust_roots.append(extra_trust_root)

        ssl_ctx = None
        try:
            ssl_ctx = libssl.SSL_CTX_new(libssl.SSLv23_method())
            if is_null(ssl_ctx):
                handle_openssl_error(0)
            self._ssl_ctx = ssl_ctx

            libssl.SSL_CTX_set_timeout(ssl_ctx, 600)

            # Allow caching SSL sessions
            libssl.SSL_CTX_ctrl(
                ssl_ctx,
                LibsslConst.SSL_CTRL_SET_SESS_CACHE_MODE,
                LibsslConst.SSL_SESS_CACHE_CLIENT,
                null()
            )

            result = libssl.SSL_CTX_set_default_verify_paths(ssl_ctx)
            handle_openssl_error(result)

            verify_mode = LibsslConst.SSL_VERIFY_NONE if manual_validation else LibsslConst.SSL_VERIFY_PEER
            libssl.SSL_CTX_set_verify(ssl_ctx, verify_mode, null())

            # Modern cipher suite list from https://wiki.mozilla.org/Security/Server_Side_TLS late August 2015
            result = libssl.SSL_CTX_set_cipher_list(
                ssl_ctx,
                (
                    b'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:'
                    b'ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:'
                    b'DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:'
                    b'kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:'
                    b'ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:'
                    b'ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:'
                    b'DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:'
                    b'DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:'
                    b'AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:'
                    b'AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:'
                    b'!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:'
                    b'!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA'
                )
            )
            handle_openssl_error(result)

            disabled_protocols = set(['SSLv2'])
            disabled_protocols |= (valid_protocols - self._protocols)
            for disabled_protocol in disabled_protocols:
                libssl.SSL_CTX_ctrl(
                    ssl_ctx,
                    LibsslConst.SSL_CTRL_OPTIONS,
                    _PROTOCOL_MAP[disabled_protocol],
                    null()
                )

            if self._extra_trust_roots:
                x509_store = libssl.SSL_CTX_get_cert_store(ssl_ctx)
                for cert in self._extra_trust_roots:
                    oscrypto_cert = load_certificate(cert)
                    result = libssl.X509_STORE_add_cert(
                        x509_store,
                        oscrypto_cert.x509
                    )
                    handle_openssl_error(result)

        except (Exception):
            if ssl_ctx:
                libssl.SSL_CTX_free(ssl_ctx)
            self._ssl_ctx = None
            raise

    def __del__(self):
        if self._ssl_ctx:
            libssl.SSL_CTX_free(self._ssl_ctx)
            self._ssl_ctx = None

        if self._ssl_session:
            libssl.SSL_SESSION_free(self._ssl_session)
            self._ssl_session = None


class TLSSocket(object):
    """
    A wrapper around a socket.socket that adds TLS
    """

    _socket = None
    _session = None

    _ssl = None
    _rbio = None
    _wbio = None
    _bio_write_buffer = None
    _read_buffer = None

    _decrypted_bytes = None

    _hostname = None

    _certificate = None
    _intermediates = None

    _protocol = None
    _cipher_suite = None
    _compression = None
    _session_id = None
    _session_ticket = None

    _local_closed = False

    @classmethod
    def wrap(cls, socket, hostname, session=None):
        """
        Takes an existing socket and adds TLS

        :param socket:
            A socket.socket object to wrap with TLS

        :param hostname:
            A unicode string of the hostname or IP the socket is connected to

        :param session:
            An existing TLSSession object to allow for session reuse, specific
            protocol or manual certificate validation

        :raises:
            ValueError - when any of the parameters contain an invalid value
            TypeError - when any of the parameters are of the wrong type
            OSError - when an error is returned by the OS crypto library
        """

        if not isinstance(socket, socket_.socket):
            raise TypeError(pretty_message(
                '''
                socket must be an instance of socket.socket, not %s
                ''',
                type_name(socket)
            ))

        if not isinstance(hostname, str_cls):
            raise TypeError(pretty_message(
                '''
                hostname must be a unicode string, not %s
                ''',
                type_name(hostname)
            ))

        if session is not None and not isinstance(session, TLSSession):
            raise TypeError(pretty_message(
                '''
                session must be an instance of oscrypto.tls.TLSSession, not %s
                ''',
                type_name(session)
            ))

        new_socket = cls(None, None, session=session)
        new_socket._socket = socket
        new_socket._hostname = hostname
        new_socket._handshake()

        return new_socket

    def __init__(self, address, port, timeout=10, session=None):
        """
        :param address:
            A unicode string of the domain name or IP address to conenct to

        :param port:
            An integer of the port number to connect to

        :param timeout:
            An integer timeout to use for the socket

        :param session:
            An oscrypto.tls.TLSSession object to allow for session reuse and
            controlling the protocols and validation performed
        """

        self._decrypted_bytes = b''

        if address is None and port is None:
            self._socket = None

        else:
            if not isinstance(address, str_cls):
                raise TypeError(pretty_message(
                    '''
                    address must be a unicode string, not %s
                    ''',
                    type_name(address)
                ))

            if not isinstance(port, int_types):
                raise TypeError(pretty_message(
                    '''
                    port must be an integer, not %s
                    ''',
                    type_name(port)
                ))

            if timeout is not None and not isinstance(timeout, numbers.Number):
                raise TypeError(pretty_message(
                    '''
                    timeout must be a number, not %s
                    ''',
                    type_name(timeout)
                ))

            self._socket = socket_.create_connection((address, port), timeout)
            self._socket.settimeout(timeout)

        if session is None:
            session = TLSSession()

        elif not isinstance(session, TLSSession):
            raise TypeError(pretty_message(
                '''
                session must be an instance of oscrypto.tls.TLSSession, not %s
                ''',
                type_name(session)
            ))

        self._session = session

        if self._socket:
            self._hostname = address
            self._handshake()

    def _handshake(self):
        """
        Perform an initial TLS handshake
        """

        ssl = None
        rbio = None
        wbio = None

        try:
            ssl = libssl.SSL_new(self._session._ssl_ctx)
            if is_null(ssl):
                handle_openssl_error(0)

            mem_bio = libssl.BIO_s_mem()

            rbio = libssl.BIO_new(mem_bio)
            if is_null(rbio):
                handle_openssl_error(0)

            wbio = libssl.BIO_new(mem_bio)
            if is_null(wbio):
                handle_openssl_error(0)

            libssl.SSL_set_bio(ssl, rbio, wbio)

            utf8_domain = self._hostname.encode('utf-8')
            libssl.SSL_ctrl(
                ssl,
                LibsslConst.SSL_CTRL_SET_TLSEXT_HOSTNAME,
                LibsslConst.TLSEXT_NAMETYPE_host_name,
                utf8_domain
            )

            libssl.SSL_set_connect_state(ssl)

            if self._session._ssl_session:
                libssl.SSL_set_session(ssl, self._session._ssl_session)

            self._bio_write_buffer = buffer_from_bytes(8192)
            self._read_buffer = buffer_from_bytes(8192)

            handshake_server_bytes = b''
            handshake_client_bytes = b''

            self._ssl = ssl
            self._rbio = rbio
            self._wbio = wbio

            while True:
                result = libssl.SSL_do_handshake(self._ssl)
                handshake_client_bytes += self._raw_write(self._wbio)

                if result == 1:
                    break

                error = libssl.SSL_get_error(ssl, result)
                if error == LibsslConst.SSL_ERROR_WANT_READ:
                    chunk = self._raw_read(self._rbio)
                    if chunk == b'':
                        if handshake_server_bytes == b'':
                            raise_disconnection()
                        if detect_client_auth_request(handshake_server_bytes):
                            raise_client_auth()
                        raise_protocol_error(handshake_server_bytes)
                    handshake_server_bytes += chunk

                elif error == LibsslConst.SSL_ERROR_WANT_WRITE:
                    handshake_client_bytes += self._raw_write(self._wbio)

                else:
                    info = peek_openssl_error()

                    dh_key_info = (
{'fraction_non_alphanumeric': 0.07283848570011572, 'fraction_numerical': 0.015605885270292611, 'mean_word_length': 1.6550210674157304, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 3, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3709574', 'n_tokens_mistral': 9176, 'n_tokens_neox': 8219, 'n_words': 1902}