Crypt-NaCl-Sodium

 view release on metacpan or  search on metacpan

Sodium.xs  view on Meta::CPAN


#define PERL_NO_GET_CONTEXT
#define NO_XSLOCKS

#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "patchlevel.h"
#include "ppport.h"

/* libsodium */
#include "sodium.h"

#define DUMP(v) do_sv_dump(0, Perl_debug_log, v, 0, 4, 0, 0);

typedef struct {
    unsigned char * bytes;
    STRLEN length;
    int locked;
} DataBytesLocker;

#if defined(AES256GCM_IS_AVAILABLE)
typedef struct {
    int locked;
    crypto_aead_aes256gcm_state * ctx;
} CryptNaClSodiumAeadAes256gcmState;
#endif

typedef struct {
    crypto_generichash_state * state;
    size_t init_bytes;
} CryptNaClSodiumGenerichashStream;

typedef struct {
    crypto_hash_sha256_state * state;
} CryptNaClSodiumHashSha256Stream;

typedef struct {
    crypto_hash_sha512_state * state;
} CryptNaClSodiumHashSha512Stream;

typedef struct {
    crypto_auth_hmacsha256_state * state;
} CryptNaClSodiumAuthHmacsha256Stream;

typedef struct {
    crypto_auth_hmacsha512_state * state;
} CryptNaClSodiumAuthHmacsha512Stream;

typedef struct {
    crypto_auth_hmacsha512256_state * state;
} CryptNaClSodiumAuthHmacsha512256Stream;

typedef struct {
    crypto_onetimeauth_state * state;
} CryptNaClSodiumOnetimeauthStream;

typedef DataBytesLocker * Data__BytesLocker;
#if defined(AES256GCM_IS_AVAILABLE)
typedef CryptNaClSodiumAeadAes256gcmState * Crypt__NaCl__Sodium__aead__aes256gcmstate;
#endif
typedef CryptNaClSodiumGenerichashStream * Crypt__NaCl__Sodium__generichash__stream;
typedef CryptNaClSodiumHashSha256Stream * Crypt__NaCl__Sodium__hash__sha256stream;
typedef CryptNaClSodiumHashSha512Stream * Crypt__NaCl__Sodium__hash__sha512stream;
typedef CryptNaClSodiumAuthHmacsha256Stream * Crypt__NaCl__Sodium__auth__hmacsha256stream;
typedef CryptNaClSodiumAuthHmacsha512Stream * Crypt__NaCl__Sodium__auth__hmacsha512stream;
typedef CryptNaClSodiumAuthHmacsha512256Stream * Crypt__NaCl__Sodium__auth__hmacsha512256stream;
typedef CryptNaClSodiumOnetimeauthStream * Crypt__NaCl__Sodium__onetimeauth__stream;

#define CLONESTATE(streamtype, statetype, padding, extra) \
    Newx(new_stream, 1, streamtype);\
    if ( new_stream == NULL ) {\
        croak("Could not allocate enough memory");\
    }\
    new_stream->state = sodium_malloc(sizeof(crypto_ ## statetype ## _state) + padding);\
    if ( new_stream->state == NULL ) {\
        croak("Could not allocate enough memory");\
    }\
    extra;\
    memcpy(new_stream->state,cur_stream->state,sizeof(crypto_ ## statetype ## _state) + padding);

#if defined(USE_ITHREADS) && defined(MGf_DUP)
STATIC int dup_byteslocker(pTHX_ MAGIC *mg, CLONE_PARAMS *params)
{
    DataBytesLocker *new_bl;
    DataBytesLocker *cur_bl;
    PERL_UNUSED_VAR(params);
    Newx(new_bl, 1, DataBytesLocker);
    if ( new_bl == NULL ) {
        croak("Could not allocate enough memory");
    }
    cur_bl = (DataBytesLocker *)mg->mg_ptr;
    new_bl->length = cur_bl->length;
    new_bl->locked = cur_bl->locked;
    new_bl->bytes = sodium_malloc(cur_bl->length + 1 );
    if ( new_bl->bytes == NULL ) {
        croak("Could not allocate enough memory");
    }
    memcpy(new_bl->bytes,cur_bl->bytes,cur_bl->length);
    mg->mg_ptr = (char *)new_bl;
    return 0;
}

#define DUPSTREAM(streamtype, statetype, padding, extra) \
STATIC int dup_ ## statetype ## _stream(pTHX_ MAGIC *mg, CLONE_PARAMS *params)\
{\
    streamtype *new_stream;\
    streamtype *cur_stream;\
    PERL_UNUSED_VAR(params);\
    cur_stream = (streamtype *)mg->mg_ptr;\
    CLONESTATE(streamtype, statetype, padding, extra)\
    mg->mg_ptr = (char *)new_stream;\
    return 0;\
}

#if defined(AES256GCM_IS_AVAILABLE)
STATIC int dup_aead_aes256gcmstate(pTHX_ MAGIC *mg, CLONE_PARAMS *params)
{
    CryptNaClSodiumAeadAes256gcmState *new_state;
    CryptNaClSodiumAeadAes256gcmState *cur_state;
    size_t sizeof_state = crypto_aead_aes256gcm_statebytes();
    PERL_UNUSED_VAR(params);
    cur_state = (CryptNaClSodiumAeadAes256gcmState *)mg->mg_ptr;

    Newx(new_state, 1, CryptNaClSodiumAeadAes256gcmState);
    if ( new_state == NULL ) {
        croak("Could not allocate enough memory");
    }
    new_state->ctx = sodium_malloc(sizeof_state);
    if ( new_state->ctx == NULL ) {
        croak("Could not allocate enough memory");
    }
    memcpy(new_state->ctx,cur_state->ctx,sizeof_state);

    mg->mg_ptr = (char *)new_state;
    return 0;
}
#endif

DUPSTREAM(CryptNaClSodiumGenerichashStream, generichash, (size_t)63U & ~(size_t) 63U, new_stream->init_bytes=cur_stream->init_bytes)
DUPSTREAM(CryptNaClSodiumHashSha256Stream, hash_sha256, 0, ((void)0))
DUPSTREAM(CryptNaClSodiumHashSha512Stream, hash_sha512, 0, ((void)0))
DUPSTREAM(CryptNaClSodiumAuthHmacsha256Stream, auth_hmacsha256, 0, ((void)0))
DUPSTREAM(CryptNaClSodiumAuthHmacsha512Stream, auth_hmacsha512, 0, ((void)0))
DUPSTREAM(CryptNaClSodiumAuthHmacsha512256Stream, auth_hmacsha512256, 0, ((void)0))
DUPSTREAM(CryptNaClSodiumOnetimeauthStream, onetimeauth, 0, ((void)0))

#endif

STATIC MGVTBL vtbl_byteslocker = {
    NULL, /* get */ NULL, /* set */ NULL, /* len */ NULL, /* clear */ NULL, /* free */
#ifdef MGf_COPY
    NULL, /* copy */
#endif

Sodium.xs  view on Meta::CPAN

#endif
#ifdef MGf_DUP
# ifdef USE_ITHREADS
    dup_auth_hmacsha512_stream,
# else
    NULL, /* dup */
# endif
#endif
#ifdef MGf_LOCAL
    NULL /* local */
#endif
};
STATIC MGVTBL vtbl_auth_hmacsha512256 = {
    NULL, /* get */ NULL, /* set */ NULL, /* len */ NULL, /* clear */ NULL, /* free */
#ifdef MGf_COPY
    NULL, /* copy */
#endif
#ifdef MGf_DUP
# ifdef USE_ITHREADS
    dup_auth_hmacsha512256_stream,
# else
    NULL, /* dup */
# endif
#endif
#ifdef MGf_LOCAL
    NULL /* local */
#endif
};
STATIC MGVTBL vtbl_onetimeauth = {
    NULL, /* get */ NULL, /* set */ NULL, /* len */ NULL, /* clear */ NULL, /* free */
#ifdef MGf_COPY
    NULL, /* copy */
#endif
#ifdef MGf_DUP
# ifdef USE_ITHREADS
    dup_onetimeauth_stream,
# else
    NULL, /* dup */
# endif
#endif
#ifdef MGf_LOCAL
    NULL /* local */
#endif
};

static DataBytesLocker * InitDataBytesLocker(pTHX_ STRLEN size) {
    DataBytesLocker *bl;
    Newx(bl, 1, DataBytesLocker);

    if ( bl == NULL ) {
        croak("Could not allocate enough memory");
    }

    bl->bytes = sodium_malloc(size + 1 );

    if ( bl->bytes == NULL ) {
        croak("Could not allocate enough memory");
    }

    bl->length = size;
    bl->locked = 0;

    return bl;
}

static SV * DataBytesLocker2SV(pTHX_ DataBytesLocker *bl) {
    SV *sv = newSV(0);
    SV *obj = newRV_noinc(sv);
    SV *default_locked;
#ifdef USE_ITHREADS
    MAGIC *mg;
#endif

    sv_bless(obj, gv_stashpv("Data::BytesLocker", 0));

    if ( (default_locked = get_sv("Data::BytesLocker::DEFAULT_LOCKED", 0)) ) {
        if ( SvTRUE(default_locked) ) {
            int rc = sodium_mprotect_noaccess((void *)bl->bytes);

            if ( rc != 0 ) {
                croak("Unable to protect BytesLocker object");
            }
            bl->locked = 1;
        }
    } else {
        int rc = sodium_mprotect_readonly((void *)bl->bytes);

        if ( rc != 0 ) {
            croak("Unable to protect BytesLocker object");
        }
    }

#ifdef USE_ITHREADS
    mg =
#endif
        sv_magicext(sv, NULL, PERL_MAGIC_ext, &vtbl_byteslocker, (const char *)bl, 0);

#if defined(USE_ITHREADS) && defined(MGf_DUP)
    mg->mg_flags |= MGf_DUP;
#endif

    return obj;
}

static DataBytesLocker* GetBytesLocker(pTHX_ SV* sv)
{
    MAGIC *mg;

    if (!sv_derived_from(sv, "Data::BytesLocker"))
        croak("Not a reference to a Data::BytesLocker object");

    for (mg = SvMAGIC(SvRV(sv)); mg; mg = mg->mg_moremagic) {
        if (mg->mg_type == PERL_MAGIC_ext && mg->mg_virtual == &vtbl_byteslocker) {
            return (DataBytesLocker *)mg->mg_ptr;
        }
    }

    croak("Failed to get Data::BytesLocker pointer");
    return (DataBytesLocker*)0; /* some compilers insist on a return value */
}

#if defined(AES256GCM_IS_AVAILABLE)
static CryptNaClSodiumAeadAes256gcmState * InitAeadAes256gcmState(pTHX_ unsigned char * key) {
    CryptNaClSodiumAeadAes256gcmState *pk;
    Newx(pk, 1, CryptNaClSodiumAeadAes256gcmState);

    if ( pk == NULL ) {
        croak("Could not allocate enough memory");
    }

    pk->ctx = sodium_malloc(crypto_aead_aes256gcm_statebytes());

    if ( pk->ctx == NULL ) {
        croak("Could not allocate enough memory");
    }


    crypto_aead_aes256gcm_beforenm(pk->ctx, key);
    pk->locked = 0;

    return pk;
}

static SV * AeadAes256gcmState2SV(pTHX_ CryptNaClSodiumAeadAes256gcmState *state) {
    SV *sv = newSV(0);
    SV *obj = newRV_noinc(sv);
    SV *default_locked;
#ifdef USE_ITHREADS
    MAGIC *mg;
#endif

    sv_bless(obj, gv_stashpv("Crypt::NaCl::Sodium::aead::aes256gcmstate", 0));

    if ( (default_locked = get_sv("Data::BytesLocker::DEFAULT_LOCKED", 0)) ) {
        if ( SvTRUE(default_locked) ) {
            int rc = sodium_mprotect_noaccess((void *)state->ctx);

            if ( rc != 0 ) {
                croak("Unable to protect AES256GCM precalculated key object");
            }
            state->locked = 1;
        }
    }

#ifdef USE_ITHREADS
    mg =
#endif
        sv_magicext(sv, NULL, PERL_MAGIC_ext, &vtbl_aead_aes256gcmstate, (const char *)state, 0);

#if defined(USE_ITHREADS) && defined(MGf_DUP)
    mg->mg_flags |= MGf_DUP;
#endif

    return obj;
}

static CryptNaClSodiumAeadAes256gcmState* GetAeadAes256gcmState(pTHX_ SV* sv)
{
    MAGIC *mg;

    if (!sv_derived_from(sv, "Crypt::NaCl::Sodium::aead::aes256gcmstate"))
        croak("Not a reference to a Crypt::NaCl::Sodium::aead::aes256gcmstate object");

    for (mg = SvMAGIC(SvRV(sv)); mg; mg = mg->mg_moremagic) {
        if (mg->mg_type == PERL_MAGIC_ext && mg->mg_virtual == &vtbl_aead_aes256gcmstate) {
            return (CryptNaClSodiumAeadAes256gcmState *)mg->mg_ptr;
        }
    }

    croak("Failed to get Crypt::NaCl::Sodium::aead::aes256gcmstate pointer");
    return (CryptNaClSodiumAeadAes256gcmState*)0; /* some compilers insist on a return value */
}
#endif

static SV * GenerichashStream2SV(pTHX_ CryptNaClSodiumGenerichashStream *stream) {
    SV *sv = newSV(0);
    SV *obj = newRV_noinc(sv);
#ifdef USE_ITHREADS
    MAGIC *mg;
#endif

    sv_bless(obj, gv_stashpv("Crypt::NaCl::Sodium::generichash::stream", 0));

#ifdef USE_ITHREADS
    mg =
#endif
        sv_magicext(sv, NULL, PERL_MAGIC_ext, &vtbl_generichash, (const char *)stream, 0);

#if defined(USE_ITHREADS) && defined(MGf_DUP)
    mg->mg_flags |= MGf_DUP;
#endif

    return obj;
}

static CryptNaClSodiumGenerichashStream* GetGenerichashStream(pTHX_ SV* sv)
{
    MAGIC *mg;

    if (!sv_derived_from(sv, "Crypt::NaCl::Sodium::generichash::stream"))
        croak("Not a reference to a Crypt::NaCl::Sodium::generichash::stream object");

Sodium.xs  view on Meta::CPAN

        STRLEN key_len = 0;
        unsigned char * key_buf = NULL;
#if defined(AES256GCM_IS_AVAILABLE)
        CryptNaClSodiumAeadAes256gcmState *state;
#endif
    PPCODE:
    {
        PERL_UNUSED_VAR(self);
#if defined(AES256GCM_IS_AVAILABLE)
        key_buf = (unsigned char *)SvPV(key, key_len);
        if ( key_len != crypto_aead_aes256gcm_KEYBYTES ) {
            croak("Invalid key");
        }

        state = InitAeadAes256gcmState(aTHX_ key_buf);

        ST(0) = sv_2mortal(AeadAes256gcmState2SV(aTHX_ state));

        XSRETURN(1);
#else
        croak("AES256-GCM is not supported by this CPU");
#endif
    }

void
aes256gcm_encrypt_afternm(self, msg, adata, nonce, precalculated_key)
    SV * self
    SV * msg
    SV * adata
    SV * nonce
    SV * precalculated_key
    PROTOTYPE: $$$$$
    INIT:
        STRLEN msg_len;
        STRLEN adata_len;
        STRLEN nonce_len;
        STRLEN enc_len;
        unsigned char * msg_buf;
        unsigned char * adata_buf;
        unsigned char * nonce_buf;
#if defined(AES256GCM_IS_AVAILABLE)
        CryptNaClSodiumAeadAes256gcmState * precal_key;
#endif
        DataBytesLocker *bl;
    PPCODE:
    {
        PERL_UNUSED_VAR(self);
#if defined(AES256GCM_IS_AVAILABLE)

        if ( GIMME_V == G_VOID ) {
            XSRETURN_EMPTY;
        }

        nonce_buf = (unsigned char *)SvPV(nonce, nonce_len);
        if ( nonce_len != crypto_aead_aes256gcm_NPUBBYTES ) {
            croak("Invalid nonce");
        }

        precal_key = GetAeadAes256gcmState(aTHX_ precalculated_key);

        if ( precal_key->locked ) {
            croak("Unlock AES256GCM precalculated key object before accessing the state");
        }

        msg_buf = (unsigned char *)SvPV(msg, msg_len);

        adata_buf = (unsigned char *)SvPV(adata, adata_len);

        if (msg_len > SIZE_MAX - crypto_aead_aes256gcm_ABYTES) {
            croak("Encrypted length exceeds system memory limit (size_t overflow)");
        }
        enc_len = msg_len + crypto_aead_aes256gcm_ABYTES;
        bl = InitDataBytesLocker(aTHX_ enc_len);

        crypto_aead_aes256gcm_encrypt_afternm( bl->bytes, NULL, msg_buf, msg_len,
            adata_buf, adata_len, NULL, nonce_buf, (const crypto_aead_aes256gcm_state *)precal_key->ctx);

        bl->bytes[enc_len] = '\0';

        mXPUSHs( DataBytesLocker2SV(aTHX_ bl) );

        XSRETURN(1);
#else
        croak("AES256-GCM is not supported by this CPU");
#endif
    }

void
aes256gcm_decrypt_afternm(self, msg, adata, nonce, precalculated_key)
    SV * self
    SV * msg
    SV * adata
    SV * nonce
    SV * precalculated_key
    PROTOTYPE: $$$$
    INIT:
        STRLEN msg_len;
        STRLEN adata_len;
        STRLEN nonce_len;
        STRLEN enc_len;
        unsigned char * msg_buf;
        unsigned char * adata_buf;
        unsigned char * nonce_buf;
#if defined(AES256GCM_IS_AVAILABLE)
        CryptNaClSodiumAeadAes256gcmState * precal_key;
#endif
        DataBytesLocker *bl;
    PPCODE:
    {
        PERL_UNUSED_VAR(self);
#if defined(AES256GCM_IS_AVAILABLE)

        if ( GIMME_V == G_VOID ) {
            XSRETURN_EMPTY;
        }

        nonce_buf = (unsigned char *)SvPV(nonce, nonce_len);
        if ( nonce_len != crypto_aead_aes256gcm_NPUBBYTES ) {
            croak("Invalid nonce");
        }

        msg_buf = (unsigned char *)SvPV(msg, msg_len);

        if ( msg_len < crypto_aead_aes256gcm_ABYTES ) {
            croak("Invalid ciphertext");
        }

        precal_key = GetAeadAes256gcmState(aTHX_ precalculated_key);

        if ( precal_key->locked ) {
            croak("Unlock AES256GCM precalculated key object before accessing the state");
        }

        adata_buf = (unsigned char *)SvPV(adata, adata_len);

        enc_len = msg_len - crypto_aead_aes256gcm_ABYTES;
        bl = InitDataBytesLocker(aTHX_ enc_len);

        if ( crypto_aead_aes256gcm_decrypt_afternm( bl->bytes, NULL, NULL, msg_buf, msg_len, adata_buf, adata_len, nonce_buf, (const crypto_aead_aes256gcm_state *) precal_key->ctx) == 0 ) {

            bl->bytes[enc_len] = '\0';
            mXPUSHs( DataBytesLocker2SV(aTHX_ bl) );
            XSRETURN(1);
        }
        else {
            sodium_free(bl->bytes);
            Safefree(bl);
            croak("Message forged");
        }
#else
        croak("AES256-GCM is not supported by this CPU");
#endif
    }

MODULE = Crypt::NaCl::Sodium        PACKAGE = Crypt::NaCl::Sodium::aead::aes256gcmstate

void
lock(self)
    SV * self
    PPCODE:
    {
        int rc;
#if defined(AES256GCM_IS_AVAILABLE)
        CryptNaClSodiumAeadAes256gcmState* state;

        state = GetAeadAes256gcmState(aTHX_ self);

        rc = sodium_mprotect_noaccess((void *)state->ctx);

        if (rc == 0 ) {
            state->locked = 1;
            XSRETURN_YES;
        }

        croak("Unable to lock memory: %s", Strerror(errno));
#else
        croak("AES256-GCM is not supported by this CPU");
#endif
    }

void
unlock(self)
    SV * self
    PPCODE:
    {
        int rc;
#if defined(AES256GCM_IS_AVAILABLE)
        CryptNaClSodiumAeadAes256gcmState* state;

        state = GetAeadAes256gcmState(aTHX_ self);

        rc = sodium_mprotect_readonly((void *)state->ctx);

        if (rc == 0 ) {
            state->locked = 0;
            XSRETURN_YES;
        }
        croak("Unable to unlock memory: %s", Strerror(errno));
#else
        croak("AES256-GCM is not supported by this CPU");
#endif
    }

void
is_locked(self, ...)
    SV * self
    PPCODE:
    {
#if defined(AES256GCM_IS_AVAILABLE)
        CryptNaClSodiumAeadAes256gcmState* state;

        state = GetAeadAes256gcmState(aTHX_ self);
        if ( state->locked ) {
            XSRETURN_YES;
        } else {
            XSRETURN_NO;
        }
#else
        croak("AES256-GCM is not supported by this CPU");
#endif
    }

void
DESTROY(self)
    SV * self
    PPCODE:
    {
#if defined(AES256GCM_IS_AVAILABLE)
        CryptNaClSodiumAeadAes256gcmState* state;
        state = GetAeadAes256gcmState(aTHX_ self);
        sodium_free( state->ctx );
        Safefree(state);
#else
        croak("AES256-GCM is not supported by this CPU");
#endif
    }


MODULE = Crypt::NaCl::Sodium        PACKAGE = Crypt::NaCl::Sodium::box

PROTOTYPES: DISABLE

unsigned int
PUBLICKEYBYTES(...)
    CODE:
        RETVAL = crypto_box_PUBLICKEYBYTES;
    OUTPUT:
        RETVAL

unsigned int
SECRETKEYBYTES(...)
    CODE:
        RETVAL = crypto_box_SECRETKEYBYTES;
    OUTPUT:
        RETVAL

unsigned int
NONCEBYTES(...)
    CODE:
        RETVAL = crypto_box_NONCEBYTES;
    OUTPUT:
        RETVAL

unsigned int
MACBYTES(...)
    CODE:
        RETVAL = crypto_box_MACBYTES;
    OUTPUT:
        RETVAL

unsigned int
SEEDBYTES(...)
    CODE:

Sodium.xs  view on Meta::CPAN

        DataBytesLocker *bl;
        unsigned char *buf;
        STRLEN buf_len;
        int wipe = 0;
        int readonly = 0;
    CODE:
    {
        if ( SvREADONLY(bytes) ) {
            buf = (unsigned char *)SvPV(bytes, buf_len);
            readonly = 1;
        } else {
            buf = (unsigned char *)SvPV_force(bytes, buf_len);
        }

        if ( items > 2 && items != 4 ) {
            croak("Invalid number of arguments");
        } else if ( items > 2 ) {
            int i = 0;
            STRLEN keylen = 0;
            char * key;

            for ( i = 2; i < items; i += 2 ) {
                key = SvPV(ST(i), keylen);
                if ( keylen == 4 && strnEQ(key, "wipe", 4) ) {
                    wipe = SvTRUE(ST(i+1));
                    if ( wipe && readonly ) {
                        croak("Modification of a read-only value attempted");
                    }
                } else {
                    croak("Invalid argument: %s", key);
                }
            }
        }

        bl = InitDataBytesLocker(aTHX_ buf_len);
        memcpy(bl->bytes, buf, buf_len);

        if ( wipe ) {
            sodium_memzero( buf, buf_len);
        }

        RETVAL = DataBytesLocker2SV(aTHX_ bl);
    }
    OUTPUT:
        RETVAL

SV *
_overload_mult(self, other, swapped)
    SV * self
    SV * other
    SV * swapped
    PREINIT:
        DataBytesLocker* sbl = GetBytesLocker(aTHX_ self);
    INIT:
        DataBytesLocker *bl;
        unsigned int count = 0;
        unsigned int cur = 0;
    OVERLOAD: x
    CODE:
    {
        if ( sbl->locked ) {
            croak("Unlock BytesLocker object before accessing the data");
        }

        count = SvUV(other);

        bl = InitDataBytesLocker(aTHX_ sbl->length * count);

        while(count--) {
            memcpy(bl->bytes + sbl->length * cur++, sbl->bytes, sbl->length);
        }

        RETVAL = DataBytesLocker2SV(aTHX_ bl);
    }
    OUTPUT:
        RETVAL


SV *
_overload_concat(self, other, swapped)
    SV * self
    SV * other
    SV * swapped
    PREINIT:
        DataBytesLocker* sbl = GetBytesLocker(aTHX_ self);
    INIT:
        unsigned char *buf;
        STRLEN buf_len;
        DataBytesLocker *bl;
    OVERLOAD: .
    CODE:
    {
        if ( sbl->locked ) {
            croak("Unlock BytesLocker object before accessing the data");
        }

        buf = (unsigned char *)SvPV(other, buf_len);

        bl = InitDataBytesLocker(aTHX_ sbl->length + buf_len);

        if ( SvTRUE(swapped) ) {
            memcpy(memcpy(bl->bytes, buf, buf_len) + buf_len, sbl->bytes, sbl->length);
        }
        else {
            memcpy(memcpy(bl->bytes, sbl->bytes, sbl->length) + sbl->length, buf, buf_len);
        }

        RETVAL = DataBytesLocker2SV(aTHX_ bl);
    }
    OUTPUT:
        RETVAL

void
_overload_bool(self, ...)
    SV * self
    PREINIT:
        DataBytesLocker* sbl = GetBytesLocker(aTHX_ self);
    INIT:
        int res;
    OVERLOAD: bool
    PPCODE:
    {

        if ( sbl->locked ) {
            croak("Unlock BytesLocker object before accessing the data");
        }

        if ( sbl->length ) {
            res = 1;
        } else {
            res = 0;
        }

        if ( res ) {
            XSRETURN_YES;
        } else {
            XSRETURN_NO;
        }
    }

void
_overload_not(self, ...)
    SV * self
    PREINIT:
        DataBytesLocker* sbl = GetBytesLocker(aTHX_ self);
    INIT:
        int res;
    OVERLOAD: !
    PPCODE:
    {

        if ( sbl->locked ) {
            croak("Unlock BytesLocker object before accessing the data");
        }

        if ( sbl->length ) {
            res = 0;
        } else {
            res = 1;
        }

        if ( res ) {
            XSRETURN_YES;
        } else {
            XSRETURN_NO;
        }
    }

void
_overload_eq(self, other, swapped)
    SV * self
    SV * other
    SV * swapped
    PREINIT:
        DataBytesLocker* sbl = GetBytesLocker(aTHX_ self);
    INIT:
        unsigned char *buf;
        STRLEN buf_len;
        int res;
    OVERLOAD: eq
    PPCODE:
    {
        if ( sbl->locked ) {
            croak("Unlock BytesLocker object before accessing the data");
        }

        buf = (unsigned char *)SvPV(other, buf_len);

        if ( sbl->length != buf_len ) {
            croak("Variables of unequal length cannot be automatically compared. Please use memcmp() with the length argument provided");
        }

        if ( sodium_memcmp(sbl->bytes, buf, sbl->length) == 0 ) {
            res = 1;
        } else {
            res = 0;
        }

        if ( res ) {
            XSRETURN_YES;
        } else {
            XSRETURN_NO;
        }
    }


void
_overload_ne(self, other, swapped)
    SV * self
    SV * other
    SV * swapped
    PREINIT:
        DataBytesLocker* sbl = GetBytesLocker(aTHX_ self);
    INIT:
        unsigned char *buf;
        STRLEN buf_len;
        int res;
    OVERLOAD: ne
    PPCODE:
    {
        if ( sbl->locked ) {
            croak("Unlock BytesLocker object before accessing the data");
        }

        buf = (unsigned char *)SvPV(other, buf_len);

        if ( sbl->length != buf_len ) {
            croak("Variables of unequal length cannot be automatically compared. Please use memcmp() with the length argument provided");
        }

        if ( sodium_memcmp(sbl->bytes, buf, sbl->length) == 0 ) {
            res = 0;
        } else {
            res = 1;
        }

        if ( res ) {
            XSRETURN_YES;
        } else {
            XSRETURN_NO;
        }
    }

void
_overload_str(self, ...)
    SV * self
    PREINIT:
        DataBytesLocker* sbl = GetBytesLocker(aTHX_ self);
    INIT:
        SV * pv;
    OVERLOAD: \"\"
    PPCODE:
    {
        if ( sbl->locked ) {
            croak("Unlock BytesLocker object before accessing the data");
        }

        pv = newSVpvn((unsigned char *)sbl->bytes, sbl->length);
        SvREADONLY_on(pv);

        mXPUSHs(pv);
    }

void
_overload_nomethod(self, ...)
    SV * self
    OVERLOAD: nomethod
    INIT:
        char * operator;
    PPCODE:
    {
        operator = SvPV_nolen(ST(3));
        croak("Operation \"%s\" is not supported", operator);
    }

SV *
clone(self)
    SV * self
    PREINIT:
        DataBytesLocker* sbl = GetBytesLocker(aTHX_ self);
    INIT:
        DataBytesLocker *bl;
    CODE:
    {
        if ( sbl->locked ) {
            croak("Unlock BytesLocker object before accessing the data");
        }

        bl = InitDataBytesLocker(aTHX_ sbl->length);

        memcpy(bl->bytes, sbl->bytes, sbl->length);

        RETVAL = DataBytesLocker2SV(aTHX_ bl);
    }
    OUTPUT:
        RETVAL

void
lock(self)
    SV * self
    PREINIT:
        DataBytesLocker* sbl = GetBytesLocker(aTHX_ self);
    INIT:
        int rc;
    PPCODE:
    {
        rc = sodium_mprotect_noaccess((void *)sbl->bytes);

        if (rc == 0 ) {
            sbl->locked = 1;
            XSRETURN_YES;
        }

        croak("Unable to lock memory: %s", Strerror(errno));
    }

void
unlock(self)
    SV * self
    PREINIT:
        DataBytesLocker* sbl = GetBytesLocker(aTHX_ self);
    INIT:
        int rc;
    PPCODE:
    {
        rc = sodium_mprotect_readonly((void *)sbl->bytes);

        if (rc == 0 ) {
            sbl->locked = 0;
            XSRETURN_YES;
        }

        croak("Unable to unlock memory: %s", Strerror(errno));
    }

SV *
length(self)
    SV * self
    PREINIT:
        DataBytesLocker* sbl = GetBytesLocker(aTHX_ self);
    CODE:
    {
        if ( sbl->locked ) {
            croak("Unlock BytesLocker object before accessing the data");
        }

        RETVAL = newSVuv((UV)sbl->length);
    }
    OUTPUT:
        RETVAL

void
is_locked(self, ...)
    SV * self
    PREINIT:
        DataBytesLocker* sbl = GetBytesLocker(aTHX_ self);
    PPCODE:
    {
        if ( sbl->locked ) {
            XSRETURN_YES;
        } else {
            XSRETURN_NO;
        }
    }

SV *
to_hex(self)
    SV * self
    PREINIT:
        DataBytesLocker* sbl = GetBytesLocker(aTHX_ self);
    INIT:
        char * hex;
        size_t hex_len;
    CODE:
    {
        if ( sbl->locked ) {
            croak("Unlock BytesLocker object before accessing the data");
        }

        hex_len = sbl->length * 2;
        hex = sodium_malloc(hex_len + 1);
        if ( hex == NULL ) {
            croak("Could not allocate memory");
        }
        sodium_bin2hex(hex, hex_len + 1, sbl->bytes, sbl->length);

        RETVAL = newSVpvn((const char * const)hex, hex_len);
    }
    OUTPUT:
        RETVAL
    CLEANUP:
        sodium_free(hex);

void
bytes(self)
    SV * self
    PREINIT:
        DataBytesLocker* sbl = GetBytesLocker(aTHX_ self);
    INIT:
        SV * pv;
    PPCODE:
    {
        if ( sbl->locked ) {
            croak("Unlock BytesLocker object before accessing the data");
        }

        pv = newSVpvn((unsigned char *)sbl->bytes, sbl->length);

        mXPUSHs(pv);
    }

void
memcmp(self, bytes, length = 0)
    SV * self
    SV * bytes
    unsigned long length
    PREINIT:
        DataBytesLocker* sbl = GetBytesLocker(aTHX_ self);
    INIT:
        unsigned char * bytes_buf;
        STRLEN bytes_len;
    PPCODE:
    {
        if ( GIMME_V == G_VOID ) {
            XSRETURN_EMPTY;
        }

        if ( sbl->locked ) {
            croak("Unlock BytesLocker object before accessing the data");
        }

        if (sv_derived_from(bytes, "Data::BytesLocker")) {
            DataBytesLocker* rbl = GetBytesLocker(aTHX_ bytes);
            if ( rbl->locked ) {
                croak("Unlock BytesLocker object before accessing the data");
            }
            bytes_buf = rbl->bytes;
            bytes_len = rbl->length;
        }
        else {
            bytes_buf = (unsigned char *)SvPV(bytes, bytes_len);
        }

        if ( length == 0 ) {
            if ( sbl->length != bytes_len ) {
                croak("Variables of unequal length cannot be automatically compared. Please provide the length argument");
            }
            length = bytes_len;
        } else {
            if ( length > sbl->length ) {
                croak("The data is shorter then requested length");
            }
            else if ( length > bytes_len ) {
                croak("The argument is shorter then requested length");
            }
        }

        if ( sodium_memcmp(sbl->bytes, bytes_buf, length) == 0 ) {
            XSRETURN_YES;
        } else {
            XSRETURN_NO;
        }
    }


void
compare(self, num, length = 0)
    SV * self
    SV * num
    unsigned long length
    PREINIT:
        DataBytesLocker* sbl = GetBytesLocker(aTHX_ self);
    INIT:
        unsigned char * num_buf;
        STRLEN num_len;
    PPCODE:
    {
        if ( GIMME_V == G_VOID ) {
            XSRETURN_EMPTY;
        }

        if ( sbl->locked ) {
            croak("Unlock BytesLocker object before accessing the data");
        }

        if (sv_derived_from(num, "Data::BytesLocker")) {
            DataBytesLocker* rbl = GetBytesLocker(aTHX_ num);
            if ( rbl->locked ) {
                croak("Unlock BytesLocker object before accessing the data");
            }
            num_buf = rbl->bytes;
            num_len = rbl->length;
        }
        else {
            num_buf = (unsigned char *)SvPV(num, num_len);
        }

        if ( length == 0 ) {
            if ( sbl->length != num_len ) {
                croak("Variables of unequal length cannot be automatically compared. Please provide the length argument");
            }
            length = num_len;
        } else {
            if ( length > sbl->length ) {
                croak("The data is shorter then requested length");
            }
            else if ( length > num_len ) {
                croak("The argument is shorter then requested length");
            }
        }

        XSRETURN_IV( sodium_compare(sbl->bytes, num_buf, length) );
    }

void
increment(self)
    SV * self
    PREINIT:
        DataBytesLocker* sbl = GetBytesLocker(aTHX_ self);
    INIT:
        SV * pv;
        DataBytesLocker *bl;
    PPCODE:
    {
        if ( GIMME_V == G_VOID ) {
            XSRETURN_EMPTY;
        }

        if ( sbl->locked ) {
            croak("Unlock BytesLocker object before accessing the data");
        }

        bl = InitDataBytesLocker(aTHX_ sbl->length);

        memcpy(bl->bytes, sbl->bytes, sbl->length);

        sodium_increment(bl->bytes, sbl->length);

        mXPUSHs( DataBytesLocker2SV(aTHX_ bl) );

        XSRETURN(1);
    }

void
add(self, num, ...)
    SV * self
    SV * num
    PREINIT:
        DataBytesLocker* sbl = GetBytesLocker(aTHX_ self);
    INIT:
        unsigned char * num_buf;
        STRLEN num_len;
        STRLEN inc_len;
        DataBytesLocker *bl;
    PPCODE:
    {
        if ( GIMME_V == G_VOID ) {
            XSRETURN_EMPTY;
        }

        if ( sbl->locked ) {
            croak("Unlock BytesLocker object before accessing the data");
        }

        if (sv_derived_from(num, "Data::BytesLocker")) {
            DataBytesLocker* rbl = GetBytesLocker(aTHX_ num);
            if ( rbl->locked ) {
                croak("Unlock BytesLocker object before accessing the data");
            }
            num_buf = rbl->bytes;
            num_len = rbl->length;
        }
        else {
            num_buf = (unsigned char *)SvPV(num, num_len);
        }

        if ( items == 3 ) {
            inc_len = (STRLEN)SvUV(ST(2));
            if ( inc_len > sbl->length ) {
                croak("The data is shorter then requested length");
            }
            else if ( inc_len > num_len ) {
                croak("The argument is shorter then requested length");
            }
        } else {
            if ( sbl->length != num_len ) {
                croak("Length of argument has to be equal to the length of data. Please provide the length argument");
            }
            inc_len = num_len;
        }

        bl = InitDataBytesLocker(aTHX_ sbl->length);

        memcpy(bl->bytes, sbl->bytes, sbl->length);

        sodium_add( bl->bytes, num_buf, inc_len );

        mXPUSHs( DataBytesLocker2SV(aTHX_ bl) );

        XSRETURN(1);
    }

void
is_zero(self, ...)
    SV * self
    PREINIT:
        DataBytesLocker* sbl = GetBytesLocker(aTHX_ self);
    INIT:
        SV * pv;
    PPCODE:
    {
        if ( sbl->locked ) {
            croak("Unlock BytesLocker object before accessing the data");
        }

        if ( sodium_is_zero(sbl->bytes, sbl->length) == 1 ) {
            XSRETURN_YES;
        }
        XSRETURN_NO;
    }


void
DESTROY(self)
    SV * self
    PREINIT:
        DataBytesLocker* bl = GetBytesLocker(aTHX_ self);
    PPCODE:
    {
        sodium_free( bl->bytes );
        Safefree(bl);
    }



( run in 2.508 seconds using v1.01-cache-2.11-cpan-bbc515a03b3 )