#include "easyxs/easyxs.h"

#include <stdbool.h>
#include <assert.h>

#include <mbedtls/net_sockets.h>
#include <mbedtls/debug.h>
#include <mbedtls/ssl.h>
#include <mbedtls/entropy.h>
#include <mbedtls/ctr_drbg.h>
#include <mbedtls/error.h>
#include <mbedtls/version.h>
#include <mbedtls/x509.h>

#ifdef NET_CONTEXT_FD_IS_PUBLIC
#define NET_CONTEXT_FD_MEMBER fd
#else
#include <mbedtls/private_access.h>
#define NET_CONTEXT_FD_MEMBER MBEDTLS_PRIVATE(fd)
#endif

#ifdef X509_CRT_RAW_IS_PUBLIC
#define X509_CRT_RAW_MEMBER raw
#else
#include <mbedtls/private_access.h>
#define X509_CRT_RAW_MEMBER MBEDTLS_PRIVATE(raw)
#endif

#ifdef X509_ASN1_P_IS_PUBLIC
#define X509_ASN1_P_MEMBER p
#else
#include <mbedtls/private_access.h>
#define X509_ASN1_P_MEMBER MBEDTLS_PRIVATE(p)
#endif

#ifdef X509_ASN1_LEN_IS_PUBLIC
#define X509_ASN1_LEN_MEMBER len
#else
#include <mbedtls/private_access.h>
#define X509_ASN1_LEN_MEMBER MBEDTLS_PRIVATE(len)
#endif

#define CERT_PEM_STRING 1
#define CERT_PEM_PATH 2

#define _MBEDTLS_PREFIX_LEN strlen("MBEDTLS_")

#define _XS_CONSTANT(name, value) \
    newCONSTSUB(gv_stashpv("$Package", FALSE), name, value)

#define _MBEDTLS_XS_CONSTANT(name) \
    _XS_CONSTANT(&#name[_MBEDTLS_PREFIX_LEN], newSViv(name))

#define _NET_MBEDTLS_XS_CONSTANT(name) \
    _XS_CONSTANT(#name, newSViv(name))

#define PERL_NAMESPACE "Net::mbedTLS"

#define SNI_CB_CLASS PERL_NAMESPACE "::Server::SNICallbackCtx"

// ----------------------------------------------------------------------
#define _XS_CONNECTION_PARTS \
    pid_t pid;                          \
                                        \
    mbedtls_net_context net_context;    \
                                        \
    mbedtls_ssl_config conf;            \
    mbedtls_ssl_context ssl;            \
                                        \
    bool notify_closed;                 \
                                        \
    SV* perl_mbedtls;                   \
    SV* perl_filehandle;                \
    SV* perl_debug_cb;                  \
                                        \
    int error;

// ----------------------------------------------------------------------

typedef struct {
#ifdef MULTIPLICITY
    tTHX aTHX;
#endif
    _XS_CONNECTION_PARTS
} xs_connection;

typedef xs_connection xs_client;

typedef struct {
#ifdef MULTIPLICITY
    tTHX aTHX;
#endif
    _XS_CONNECTION_PARTS

    SV* sni_cb;

    mbedtls_pk_context  pkey;
    mbedtls_x509_crt    crt;
} xs_server;

typedef struct {
    pid_t pid;

    mbedtls_x509_crt cacert;

    mbedtls_entropy_context entropy;
    mbedtls_ctr_drbg_context ctr_drbg;

    SV* trust_store_path_sv;
    bool trust_store_loaded;
} xs_mbedtls;

#define _warn_if_global_destruct(self_obj, mystruct) \
    if (PL_dirty && (mystruct->pid == getpid())) \
        warn("%s survived until global destruction!", SvPV_nolen(self_obj));

#define _ERROR_FACTORY_CLASS PERL_NAMESPACE "::X"

#define TRUST_STORE_MODULE "Mozilla::CA"
#define TRUST_STORE_PATH_FUNCTION (TRUST_STORE_MODULE "::SSL_ca_file")

// ----------------------------------------------------------------------

// Global state is regrettable, but no less so than mbedTLS’s own:
static IV mbedtls_debug_threshold = 0;

// ----------------------------------------------------------------------

static inline SV* _get_crt_verify_info_sv (pTHX_ U32 flags, const char* prefix) {
    char buf[1024];

    const char* myprefix = prefix ? prefix : "";

    int len = mbedtls_x509_crt_verify_info(buf, sizeof(buf), myprefix, flags);

    return newSVpvn(buf, len);
}

static inline void _mbedtls_err_croak( pTHX_ const char* action, int errnum, mbedtls_ssl_context* ssl ) {
    dSP;

    char errstr[200];
    mbedtls_strerror(errnum, errstr, sizeof(errstr));

    ENTER;
    SAVETMPS;

    PUSHMARK(SP);

    bool is_cert_verify_err = (errnum == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED);

    EXTEND(SP, 5);

    mPUSHs( newSVpvs(_ERROR_FACTORY_CLASS) );
    mPUSHs( is_cert_verify_err ? newSVpvs("mbedTLS::x509VerificationFailed") : newSVpvs("mbedTLS") );
    mPUSHs( newSVpv(action, 0) );
    mPUSHi(errnum);
    mPUSHs( newSVpv(errstr, 0) );

    if (is_cert_verify_err) {
        U32 flags = mbedtls_ssl_get_verify_result(ssl);
        mPUSHu(flags);
        mPUSHs( _get_crt_verify_info_sv( aTHX_ flags, NULL ) );
    }

    PUTBACK;

    int retcount = call_method("create", G_SCALAR);

    SPAGAIN;

    SV* err = retcount ? SvREFCNT_inc(POPs) : NULL;

    FREETMPS;
    LEAVE;

    if (err) croak_sv(err);

    croak("Huh?? %s->%s() didn’t give anything?", _ERROR_FACTORY_CLASS, "create");
}

/*
void
fg_Perl_free_tmps(pTHX)
{
    // XXX should tmps_floor live in cxstack?
    const SSize_t myfloor = PL_tmps_floor;
    while (PL_tmps_ix > myfloor) {      // clean up after last statement
        SV* const sv = PL_tmps_stack[PL_tmps_ix--];
#ifdef PERL_POISON
        PoisonWith(PL_tmps_stack + PL_tmps_ix + 1, 1, SV *, 0xAB);
#endif
        if (LIKELY(sv)) {
            SvTEMP_off(sv);
            SvREFCNT_dec_NN(sv);		// note, can modify tmps_ix!!!
        }
    }
}

void _dump_tmps(pTHX) {
    fprintf(stderr, "=====> DUMP TMPS:\n");
    const SSize_t myfloor = PL_tmps_floor;
    SSize_t ix = PL_tmps_ix;
    while (ix > myfloor) {
        fprintf(stderr, "ix=%zd: %p\n", ix, PL_tmps_stack[ix]);

        SV* const sv = PL_tmps_stack[ix--];
        if (LIKELY(sv)) {
            sv_dump(sv);
        }
    }
    fprintf(stderr, "\t=====> DONE DUMP TMPS\n");
}
*/

static const char* DEBUG_LEVEL_NAME[] = {
    NULL,
    "error",
    "warn",
    "info",
    "debug",
};

static void _my_debug (void *ctx, int level, const char *file, int line, const char *str) {
    const char *slash = strrchr(file, '/');
    const char *pos = slash ? (slash + 1) : file;
    fprintf(stderr, "%s (%s:%d): %s", DEBUG_LEVEL_NAME[level], pos, line, str);
}

SV* _set_up_connection_object(pTHX_ xs_mbedtls* myconfig, size_t struct_size, const char* classname, int endpoint_type, SV* mbedtls_obj, SV* filehandle, int fd) {
    SV* referent = newSV(struct_size);
    sv_2mortal(referent);

    xs_connection* myconn = (xs_connection*) SvPVX(referent);

    *myconn = (xs_connection) {
        .net_context = {
            .NET_CONTEXT_FD_MEMBER = fd,
        },

        .pid = getpid(),
        .error = 0,

#ifdef MULTIPLICITY
        .aTHX = aTHX,
#endif
    };

    mbedtls_ssl_config_init( &myconn->conf );

    int result = mbedtls_ssl_config_defaults(
        &myconn->conf,
        endpoint_type,
        MBEDTLS_SSL_TRANSPORT_STREAM,
        MBEDTLS_SSL_PRESET_DEFAULT
    );

    if (result) {
        mbedtls_ssl_config_free( &myconn->conf );

        _mbedtls_err_croak(aTHX_ "set up config", result, NULL);
    }

    if (mbedtls_debug_threshold) {
printf("======= setting debug\n");
        mbedtls_ssl_conf_dbg(&myconn->conf, _my_debug, NULL);
    }

    mbedtls_ssl_conf_rng( &myconn->conf, mbedtls_ctr_drbg_random, &myconfig->ctr_drbg );

    mbedtls_ssl_init( &myconn->ssl );

    mbedtls_ssl_set_bio(
        &myconn->ssl,
        &myconn->net_context,
        mbedtls_net_send,
        mbedtls_net_recv,
        NULL
    );

    result = mbedtls_ssl_setup( &myconn->ssl, &myconn->conf );

    if (result) {
        mbedtls_ssl_config_free( &myconn->conf );
        mbedtls_ssl_free( &myconn->ssl );

        _mbedtls_err_croak(aTHX_ "set up TLS", result, NULL);
    }

    // Beyond here cleanup is identical to normal DESTROY:
    SV* ret = newRV_inc(referent);
    sv_bless(ret, gv_stashpv(classname, FALSE));

    myconn->perl_mbedtls = SvREFCNT_inc(mbedtls_obj);
    myconn->perl_filehandle = SvREFCNT_inc(filehandle);

    return ret;
}

static inline void _verify_io_retval(pTHX_ int retval, xs_connection* myconn, const char* msg) {
    if (retval < 0) {
        myconn->error = retval;

        switch (retval) {
            case MBEDTLS_ERR_SSL_WANT_READ:
            case MBEDTLS_ERR_SSL_WANT_WRITE:
            case MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS:
            case MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS:
            case MBEDTLS_ERR_SSL_CLIENT_RECONNECT:
                break;

            default: {
                dTHX;
                _mbedtls_err_croak(aTHX_ msg, retval, &myconn->ssl);
            }
        }
    }
}

// Returns a MORTAL SV to the default trust store path.
static inline SV* _get_default_trust_store_path_sv(pTHX) {
    dSP;

    load_module(
        PERL_LOADMOD_NOIMPORT,
        newSVpvs(TRUST_STORE_MODULE),
        NULL
    );

    ENTER;
    SAVETMPS;

    int got = call_pv(TRUST_STORE_PATH_FUNCTION, G_SCALAR);

    if (!got) croak("%s() returned nothing?!?", TRUST_STORE_PATH_FUNCTION);

    SPAGAIN;

    SV* ret = SvREFCNT_inc(POPs);

    FREETMPS;
    LEAVE;

    return sv_2mortal(ret);
}

static inline void _load_trust_store_if_needed(pTHX_ xs_mbedtls* myconfig) {
    if (!myconfig->trust_store_loaded) {
        assert(myconfig->trust_store_path_sv);

        if (!SvOK(myconfig->trust_store_path_sv)) {
            sv_setsv(myconfig->trust_store_path_sv, _get_default_trust_store_path_sv(aTHX));
        }

        mbedtls_x509_crt_init( &myconfig->cacert );

        char *path = SvPVbyte_nolen(myconfig->trust_store_path_sv);

        int ret = mbedtls_x509_crt_parse_file(&myconfig->cacert, path);

        if (ret) {
            mbedtls_x509_crt_free( &myconfig->cacert );

            char *msg = form("Read trust store (%s)", path);
            _mbedtls_err_croak(aTHX_ msg, ret, NULL);
        }

        myconfig->trust_store_loaded = true;
    }
}

// ----------------------------------------------------------------------

// Returns:
//  0 if no error
//  1 if error was for key
//  2 if error was for cert chain
//
static unsigned _parse_key_and_cert_chain(pTHX_ xs_mbedtls* myconfig, SV** given, mbedtls_pk_context* pkey, mbedtls_x509_crt* crt, int *result) {

    SV* cur = given[0];
    assert(cur);

    mbedtls_pk_init(pkey);

    STRLEN pv_length;
    const char* pv = SvPVbyte(cur, pv_length);

    *result = mbedtls_pk_parse_key(
        pkey,
        (const unsigned char*) pv,
        1 + pv_length,
        NULL, 0 // passphrase
#ifdef PK_PARSE_KEY_5_ARGS
#elif defined PK_PARSE_KEY_7_ARGS
        ,mbedtls_ctr_drbg_random
        ,&myconfig->ctr_drbg
#else
"Unrecognized mbedtls_pk_parse_key() signature" && 0)
#endif
    );

    if (*result) {
        mbedtls_pk_free(pkey);

        return 1;
    }

    U8 g = given[1] ? 1 : 0;

    mbedtls_x509_crt_init(crt);

    while ( (cur = given[g++]) ) {
        pv = SvPVbyte(cur, pv_length);

        *result = mbedtls_x509_crt_parse(
            crt,
            (const unsigned char*) pv,
            1 + pv_length
        );

        if (*result) {
            mbedtls_x509_crt_free(crt);
            mbedtls_pk_free(pkey);

            return 2;
        }
    }

    // Success!
    return 0;
}

// This “eats” servername:
SV* _create_sni_cb_ctx(pTHX_ SV* server_sv_referent, SV* servername) {
    SV* server_sv = newRV_inc(server_sv_referent);

    dSP;

    ENTER;
    SAVETMPS;

    PUSHMARK(SP);
    EXTEND(SP, 3);
    mPUSHs( newSVpvs( SNI_CB_CLASS ) );
    mPUSHs(server_sv);
    mPUSHs(servername);
    PUTBACK;

    int count = call_method( "_new", G_SCALAR );
    PERL_UNUSED_ARG(count);

    assert(count == 1);

    SPAGAIN;

    SV* cb_ctx = SvREFCNT_inc(POPs);

    FREETMPS;
    LEAVE;

    return cb_ctx;
}

static int net_mbedtls_sni_callback(void *ctx, mbedtls_ssl_context *ssl, const unsigned char* sni, size_t snilen) {
    SV* server_sv_referent = ctx;

    xs_server* myconn = (xs_server*) SvPVX( server_sv_referent );

#ifdef MULTIPLICITY
    pTHX = myconn->aTHX;
#endif

    SV* sni_sv = newSVpvn((const char *) sni, snilen);

    SV* callback_ctx_obj = _create_sni_cb_ctx(aTHX_ server_sv_referent, sni_sv);

    SV* cb = myconn->sni_cb;

    dSP;

    ENTER;
    SAVETMPS;

    PUSHMARK(SP);
    EXTEND(SP, 1);
    mPUSHs(callback_ctx_obj);
    PUTBACK;

    int count = call_sv(cb, G_SCALAR | G_EVAL);

    SPAGAIN;

    bool failed = true;

    if (SvTRUE(ERRSV)) {
        PERL_UNUSED_VAR(POPs);   // cf. perldoc perlcall
        warn("SNI callback failed: %" SVf, ERRSV);
        goto end_sni_callback;
    }

    if (count != 1) {
        failed = false;
    }
    else {
        SV* ret_sv = POPs;

        failed = SvOK(ret_sv) && (SvIV(ret_sv) == -1);
    }

  end_sni_callback:
    FREETMPS;
    LEAVE;

    return failed;
}

// ----------------------------------------------------------------------

MODULE = Net::mbedTLS        PACKAGE = Net::mbedTLS

PROTOTYPES: DISABLE

BOOT:
    _MBEDTLS_XS_CONSTANT(MBEDTLS_ERR_SSL_WANT_READ);
    _MBEDTLS_XS_CONSTANT(MBEDTLS_ERR_SSL_WANT_WRITE);
    _MBEDTLS_XS_CONSTANT(MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS);
    _MBEDTLS_XS_CONSTANT(MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS);
    _MBEDTLS_XS_CONSTANT(MBEDTLS_ERR_SSL_CLIENT_RECONNECT);

    _MBEDTLS_XS_CONSTANT(MBEDTLS_SSL_VERIFY_NONE);
    _MBEDTLS_XS_CONSTANT(MBEDTLS_SSL_VERIFY_OPTIONAL);
    _MBEDTLS_XS_CONSTANT(MBEDTLS_SSL_VERIFY_REQUIRED);

    _MBEDTLS_XS_CONSTANT(MBEDTLS_X509_BADCERT_EXPIRED);
    _MBEDTLS_XS_CONSTANT(MBEDTLS_X509_BADCERT_REVOKED);
    _MBEDTLS_XS_CONSTANT(MBEDTLS_X509_BADCERT_CN_MISMATCH);
    _MBEDTLS_XS_CONSTANT(MBEDTLS_X509_BADCERT_NOT_TRUSTED);
    _MBEDTLS_XS_CONSTANT(MBEDTLS_X509_BADCERT_MISSING);
    _MBEDTLS_XS_CONSTANT(MBEDTLS_X509_BADCERT_SKIP_VERIFY);
    _MBEDTLS_XS_CONSTANT(MBEDTLS_X509_BADCERT_OTHER);
    _MBEDTLS_XS_CONSTANT(MBEDTLS_X509_BADCERT_FUTURE);
    _MBEDTLS_XS_CONSTANT(MBEDTLS_X509_BADCERT_KEY_USAGE);
    _MBEDTLS_XS_CONSTANT(MBEDTLS_X509_BADCERT_EXT_KEY_USAGE);
    _MBEDTLS_XS_CONSTANT(MBEDTLS_X509_BADCERT_NS_CERT_TYPE);
    _MBEDTLS_XS_CONSTANT(MBEDTLS_X509_BADCERT_BAD_MD);
    _MBEDTLS_XS_CONSTANT(MBEDTLS_X509_BADCERT_BAD_PK);
    _MBEDTLS_XS_CONSTANT(MBEDTLS_X509_BADCERT_BAD_KEY);

    _NET_MBEDTLS_XS_CONSTANT(CERT_PEM_STRING);
    _NET_MBEDTLS_XS_CONSTANT(CERT_PEM_PATH);

UV
mbedtls_version_get_number()
    CODE:
        RETVAL = (UV) mbedtls_version_get_number();

    OUTPUT:
        RETVAL

char*
mbedtls_version_get_string()
    CODE:
        // Per docs, this should be at least 9 bytes:
        char versionstr[20] = { 0 };
        mbedtls_version_get_string(versionstr);

        RETVAL = versionstr;

    OUTPUT:
        RETVAL

void
set_debug_threshold(IV threshold)
    CODE:
        mbedtls_debug_threshold = threshold;
        mbedtls_debug_set_threshold(threshold);

SV*
verify_info (UV flags, SV* line_prefix=NULL)
    CODE:
        const char* prefix = line_prefix ? SvPVbyte_nolen(line_prefix) : NULL;

        RETVAL = _get_crt_verify_info_sv(aTHX_ flags, prefix);

    OUTPUT:
        RETVAL

SV*
_new(SV* classname, SV* trust_store_path_sv = NULL)
    CODE:
        int ret;

        SV* referent = newSV(sizeof(xs_mbedtls));
        SvPOK_on(referent);
        sv_2mortal(referent);

        xs_mbedtls* myconfig = (xs_mbedtls*) SvPVX(referent);

        *myconfig = (xs_mbedtls) {
            .pid = getpid(),
            .trust_store_path_sv = trust_store_path_sv ? newSVsv(trust_store_path_sv) : NULL,
        };

        mbedtls_ctr_drbg_init( &myconfig->ctr_drbg );
        mbedtls_entropy_init( &myconfig->entropy );

        // At this point myconfig is all set up. Any further failures
        // require cleanup identical to the normal object DESTROY, so
        // we might as well reuse that logic.
        RETVAL = newRV_inc(referent);
        sv_bless(RETVAL, gv_stashpv(SvPVbyte_nolen(classname), FALSE));

        ret = mbedtls_ctr_drbg_seed(
            &myconfig->ctr_drbg,
            mbedtls_entropy_func,
            &myconfig->entropy,
            NULL, 0
        );

        if (ret) {
            _mbedtls_err_croak(aTHX_ "Failed to seed random-number generator", ret, NULL);
        }

    OUTPUT:
        RETVAL

void
DESTROY(SV* self_obj)
    CODE:
        //fprintf(stderr, "DESTROY Net::mbedTLS at phase %s\n", PL_phase_names[PL_phase]);
        xs_mbedtls* myconfig = (xs_mbedtls*) SvPVX( SvRV(self_obj) );

        _warn_if_global_destruct(self_obj, myconfig);

        if (myconfig->trust_store_path_sv) {
            SvREFCNT_dec(myconfig->trust_store_path_sv);
        }

        if (myconfig->trust_store_loaded) {
            mbedtls_x509_crt_free( &myconfig->cacert );
        }

        mbedtls_ctr_drbg_free( &myconfig->ctr_drbg );
        mbedtls_entropy_free( &myconfig->entropy );
        //fprintf(stderr, "DESTROY Net::mbedTLS done\n");


# ----------------------------------------------------------------------

MODULE = Net::mbedTLS   PACKAGE = Net::mbedTLS::Connection

bool
shake_hands(SV* peer_obj)
    CODE:
        xs_connection* myconn = (xs_connection*) SvPVX( SvRV(peer_obj) );

        int result = mbedtls_ssl_handshake( &myconn->ssl );

        _verify_io_retval(aTHX_ result, myconn, "handshake");

        RETVAL = !result;

    OUTPUT:
        RETVAL

# Named differently in client vs. server end classes because the
# actual work is different, but the call to mbedTLS is identical:
#
bool
_renegotiate(SV* peer_obj)
    CODE:
        xs_connection* myconn = (xs_connection*) SvPVX( SvRV(peer_obj) );

        int result = mbedtls_ssl_renegotiate( &myconn->ssl );

        _verify_io_retval(aTHX_ result, myconn, "renegotiate");

        RETVAL = !result;

    OUTPUT:
        RETVAL

SV*
write(SV* peer_obj, SV* bytes_sv)
    CODE:
        SvGETMAGIC(bytes_sv);

        xs_connection* myconn = (xs_connection*) SvPVX( SvRV(peer_obj) );

        STRLEN outputlen;
        const char* output = SvPVbyte(bytes_sv, outputlen);

        int result = mbedtls_ssl_write(
            &myconn->ssl,
            (unsigned char*) output,
            outputlen
        );

        _verify_io_retval(aTHX_ result, myconn, "write");

        RETVAL = (result < 0) ? &PL_sv_undef : newSViv(result);

    OUTPUT:
        RETVAL

SV*
read(SV* peer_obj, SV* output_sv)
    CODE:
        SvGETMAGIC(output_sv);

        if (!SvOK(output_sv)) croak("Undef is nonsense!");
        if (SvROK(output_sv)) croak("read() needs a plain scalar, not %s!", SvPVbyte_nolen(output_sv));

        xs_connection* myconn = (xs_connection*) SvPVX( SvRV(peer_obj) );

        STRLEN outputlen;
        const char* output = SvPVbyte(output_sv, outputlen);
        if (!outputlen) croak("Empty string is nonsense!");

        int result = mbedtls_ssl_read(
            &myconn->ssl,
            (unsigned char*) output,
            outputlen
        );

        if (result == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) {
            myconn->notify_closed = true;

            result = 0;
        }
        else {
            _verify_io_retval(aTHX_ result, myconn, "read");
        }

        SvUTF8_off(output_sv);

        SvSETMAGIC(output_sv);

        RETVAL = (result < 0) ? &PL_sv_undef : newSViv(result);

    OUTPUT:
        RETVAL

SV*
fh (SV* self_sv)
    CODE:
        xs_connection* myconn = (xs_connection*) SvPVX( SvRV(self_sv) );

        RETVAL = SvREFCNT_inc(myconn->perl_filehandle);

    OUTPUT:
        RETVAL

bool
closed(SV* peer_obj)
    CODE:
        xs_connection* myconn = (xs_connection*) SvPVX( SvRV(peer_obj) );

        RETVAL = myconn->notify_closed;

    OUTPUT:
        RETVAL

SV*
ciphersuite (SV* peer_obj)
    CODE:
        xs_connection* myconn = (xs_connection*) SvPVX( SvRV(peer_obj) );

        const char* name = mbedtls_ssl_get_ciphersuite(&myconn->ssl);

        RETVAL = name ? newSVpv(name, 0) : &PL_sv_undef;

    OUTPUT:
        RETVAL

int
max_out_record_payload  (SV* peer_obj)
    CODE:
        xs_connection* myconn = (xs_connection*) SvPVX( SvRV(peer_obj) );

        RETVAL = mbedtls_ssl_get_max_out_record_payload(&myconn->ssl);

    OUTPUT:
        RETVAL

SV*
tls_version_name (SV* peer_obj)
    CODE:
        xs_connection* myconn = (xs_connection*) SvPVX( SvRV(peer_obj) );

        const char *name = mbedtls_ssl_get_version(&myconn->ssl);

        RETVAL = name ? newSVpv(name, 0) : &PL_sv_undef;

    OUTPUT:
        RETVAL

void
peer_certificates (SV* peer_obj)
    PPCODE:
        if (GIMME_V != G_ARRAY) croak("List context only!");

        xs_connection* myconn = (xs_connection*) SvPVX( SvRV(peer_obj) );

        const mbedtls_x509_crt* crt = mbedtls_ssl_get_peer_cert(&myconn->ssl);

        int count = 0;

        while (crt) {
            SV* crt_sv = newSVpv(
                (const char*) crt->X509_CRT_RAW_MEMBER.X509_ASN1_P_MEMBER,
                crt->X509_CRT_RAW_MEMBER.X509_ASN1_LEN_MEMBER
            );

            XPUSHs(sv_2mortal(crt_sv));
            count++;

            crt = crt->next;
        }

U32
verification_result (SV* peer_obj)
    CODE:
        xs_connection* myconn = (xs_connection*) SvPVX( SvRV(peer_obj) );

        RETVAL = mbedtls_ssl_get_verify_result(&myconn->ssl);

    OUTPUT:
        RETVAL

bool
close_notify (SV* peer_obj)
    CODE:
        xs_connection* myconn = (xs_connection*) SvPVX( SvRV(peer_obj) );

        int result = mbedtls_ssl_close_notify(&myconn->ssl);
        _verify_io_retval(aTHX_ result, myconn, "close_notify");

        RETVAL = !result;

    OUTPUT:
        RETVAL

IV
error (SV* peer_obj)
    CODE:
        xs_connection* myconn = (xs_connection*) SvPVX( SvRV(peer_obj) );

        RETVAL = myconn->error;

    OUTPUT:
        RETVAL

void
DESTROY(SV* peer_obj)
    CODE:
        //fprintf(stderr, "DESTROY %s at phase %s\n", SvPVbyte_nolen(peer_obj), PL_phase_names[PL_phase]);
        xs_connection* myconn = (xs_connection*) SvPVX( SvRV(peer_obj) );

        _warn_if_global_destruct(peer_obj, myconn);

        mbedtls_ssl_config_free( &myconn->conf );
        mbedtls_ssl_free( &myconn->ssl );

        SvREFCNT_dec(myconn->perl_mbedtls);
        SvREFCNT_dec(myconn->perl_filehandle);

# ----------------------------------------------------------------------

MODULE = Net::mbedTLS   PACKAGE = Net::mbedTLS::Client

SV*
_new(const char* classname, SV* mbedtls_obj, SV* filehandle, int fd, SV* servername_sv, SV* authmode_sv)
    CODE:
        const char* servername = SvOK(servername_sv) ? SvPVbyte_nolen(servername_sv) : "";

        xs_mbedtls* myconfig = (xs_mbedtls*) SvPVX( SvRV(mbedtls_obj) );

        bool need_trust_store = !SvOK(authmode_sv) || (SvIV(authmode_sv) != MBEDTLS_SSL_VERIFY_NONE);

        if (need_trust_store) {
            _load_trust_store_if_needed(aTHX_ myconfig);
        }

        RETVAL = _set_up_connection_object(aTHX_ myconfig, sizeof(xs_client), classname, MBEDTLS_SSL_IS_CLIENT, mbedtls_obj, filehandle, fd);

        SV* referent = SvRV(RETVAL);

        xs_client* myconn = (xs_client*) SvPVX(referent);

        int result = mbedtls_ssl_set_hostname(&myconn->ssl, servername);

        if (result) {
            _mbedtls_err_croak(aTHX_ "set SNI string", result, NULL);
        }

        if (need_trust_store) {
            mbedtls_ssl_conf_ca_chain( &myconn->conf, &myconfig->cacert, NULL );
        }

        if (SvOK(authmode_sv)) {
            mbedtls_ssl_conf_authmode( &myconn->conf, SvIV(authmode_sv) );
        }

    OUTPUT:
        RETVAL

# ----------------------------------------------------------------------

MODULE = Net::mbedTLS   PACKAGE = Net::mbedTLS::Server

SV*
_new(const char* classname, SV* mbedtls_obj, SV* filehandle, int fd, SV* own_cert_sv, SV* sni_cb)
    CODE:
        xs_mbedtls* myconfig = (xs_mbedtls*) SvPVX( SvRV(mbedtls_obj) );

        // We don’t currently support client certificates.
        // _load_trust_store_if_needed(aTHX_ myconfig);

        RETVAL = _set_up_connection_object(aTHX_ myconfig, sizeof(xs_server), classname, MBEDTLS_SSL_IS_SERVER, mbedtls_obj, filehandle, fd);

        SV* referent = SvRV(RETVAL);

        xs_server* myconn = (xs_server*) SvPVX(referent);

        AV* own_cert_av = (AV*) SvRV(own_cert_sv);
        unsigned own_cert_len = 1 + av_len(own_cert_av);
        SV* own_cert[ 1 + own_cert_len ];
        own_cert[own_cert_len] = NULL;
        Copy(AvARRAY(own_cert_av), own_cert, own_cert_len, SV*);

        mbedtls_x509_crt_init(&myconn->crt);
        mbedtls_pk_init(&myconn->pkey);

        int result;

        int parse_err = _parse_key_and_cert_chain(aTHX_
            myconfig,
            own_cert,
            &myconn->pkey,
            &myconn->crt,
            &result
        );

        if (parse_err) {
            _mbedtls_err_croak(aTHX_
                (parse_err == 1) ? "parse key" : "parse certificate chain",
                result,
                NULL
            );
        }

        result = mbedtls_ssl_conf_own_cert (&myconn->conf, &myconn->crt, &myconn->pkey);
        if (result) {
            _mbedtls_err_croak(aTHX_ "assign key & certificate", result, NULL);
        }

        if (SvOK(sni_cb)) {
            myconn->sni_cb = SvREFCNT_inc(sni_cb);

            mbedtls_ssl_conf_sni(
                &myconn->conf,
                net_mbedtls_sni_callback,
                referent
            );
        }

    OUTPUT:
        RETVAL

void
_set_hs_own_cert (SV* self_sv, SV* key_sv, ...)
    CODE:
        unsigned certs_len = items - 2;

        xs_server* myconn = (xs_server*) SvPVX( SvRV(self_sv) );

        SV* mbedtls_obj = myconn->perl_mbedtls;
        xs_mbedtls* myconfig = (xs_mbedtls*) SvPVX(mbedtls_obj);

        SV* key_and_chain[2 + certs_len];
        key_and_chain[0] = key_sv;
        key_and_chain[1 + certs_len] = NULL;
        Copy(&ST(2), (key_and_chain + 1), certs_len, SV*);

        // These will be re-allocated below:
        mbedtls_pk_free(&myconn->pkey);
        mbedtls_x509_crt_free(&myconn->crt);

        int result;

        unsigned errtype = _parse_key_and_cert_chain(
            aTHX_
            myconfig,
            key_and_chain,
            &myconn->pkey,
            &myconn->crt,
            &result
        );

        if (errtype) {
            _mbedtls_err_croak( aTHX_
                (errtype == 1) ? "parse key" : "parse certificate chain",
                result,
                NULL
            );
        }

        result = mbedtls_ssl_set_hs_own_cert(&myconn->ssl, &myconn->crt, &myconn->pkey);
        if (result) {
            mbedtls_x509_crt_free(&myconn->crt);
            mbedtls_pk_free(&myconn->pkey);

            _mbedtls_err_croak( aTHX_
                "assign key & certificate",
                result,
                NULL
            );
        }


void
_DESTROY (SV* self_sv)
    CODE:
        SV* referent = SvRV(self_sv);

        xs_server* myconn = (xs_server*) SvPVX(referent);

        mbedtls_x509_crt_free(&myconn->crt);
        mbedtls_pk_free(&myconn->pkey);