package Crypt::Libmcrypt;

# ABSTRACT: Perl extension for libmcrypt,the mcrypt cryptographic library
use 5.006000;
use strict;
use warnings;
use Carp;
require Exporter;
our @ISA = qw(Exporter);
our ($AUTOLOAD);

# Items to export into callers namespace by default. Note: do not export
# names by default without a very good reason. Use EXPORT_OK instead.
# Do not simply export all your public functions/methods/constants.

# This allows declaration	use Crypt::Crypt::Libmcrypt ':all';
# If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
# will save memory.
our %EXPORT_TAGS = (
    ALGORITHMS => [
        qw(BLOWFISH
          DES
          3DES
          3WAY
          GOST
          SAFER_SK64
          SAFER_SK128
          CAST_128
          XTEA
          RC2
          TWOFISH
          CAST_256
          SAFERPLUS
          LOKI97
          SERPENT
          RIJNDAEL_128
          RIJNDAEL_192
          RIJNDAEL_256
          ENIGMA
          ARCFOUR
          WAKE)
    ],
    MODES => [
        qw(CBC
          CTR
          ECB
          CFB
          OFB
          nCFB
          nOFB
          STREAM)
    ],
    FUNCS => [
        qw(mcrypt_load
          mcrypt_unload
          mcrypt_init
          mcrypt_end
          mcrypt_encrypt
          mcrypt_decrypt
          mcrypt_get_block_size
          mcrypt_get_iv_size
          mcrypt_get_key_size)
    ],
);

our @EXPORT_OK = qw(ERROR

  BLOWFISH
  DES
  3DES
  3WAY
  GOST
  CAST_128
  XTEA
  RC2
  TWOFISH
  CAST_256
  SAFER_SK64
  SAFER_SK128
  SAFERPLUS
  LOKI97
  SERPENT
  RIJNDAEL_128
  RIJNDAEL_192
  RIJNDAEL_256
  ENIGMA
  ARCFOUR
  WAKE

  CBC
  CTR
  ECB
  CFB
  OFB
  nCFB
  nOFB
  STREAM

  mcrypt_load
  mcrypt_unload
  mcrypt_init
  mcrypt_end
  mcrypt_encrypt
  mcrypt_decrypt
  mcrypt_get_block_size
  mcrypt_get_iv_size
  mcrypt_get_key_size

  );

our @EXPORT = qw( ERROR );

our $VERSION = '1.0.2';

# Preloaded methods go here.

# Autoload methods go after =cut, and are processed by the autosplit program.

sub AUTOLOAD
{
    # This AUTOLOAD is used to 'autoload' constants from the constant()
    # XS function.  If a constant is not found then control is passed
    # to the AUTOLOAD in AutoLoader.

    my $constname;
    ($constname = $AUTOLOAD) =~ s/.*:://;
    if ($constname eq 'constant')
    {
        # This will recurse!
        croak "Problem loading Crypt::Libmcrypt libraries";
    }
    my $val = constant($constname, @_ ? $_[0] : 0);
    if ($! != 0)
    {
        if ($! =~ /Invalid/)
        {
            $AutoLoader::AUTOLOAD = $AUTOLOAD;
            goto &AutoLoader::AUTOLOAD;
        }
        else
        {
            croak "Your vendor has not defined Crypt::Libmcrypt macro $constname";
        }
    }
    eval "sub $AUTOLOAD { \"$val\" }";
    goto &$AUTOLOAD;
}

require DynaLoader;
push @ISA, 'DynaLoader';
bootstrap Crypt::Libmcrypt $VERSION;

sub new
{
    my ($self, %arg) = @_;
    my $class = ref($self) || $self;
    return undef unless defined($arg{'algorithm'});
    return undef unless defined($arg{'mode'});
    $arg{'algorithm_dir'} = '' unless defined($arg{'algorithm_dir'});
    $arg{'mode_dir'}      = '' unless defined($arg{'mode_dir'});
    my $td = mcrypt_load($arg{'algorithm'}, $arg{'algorithm_dir'},
                         $arg{'mode'},      $arg{'mode_dir'});
    my $mcrypt = bless {TD => $td}, $class;
    $mcrypt->{Verbose}  = 1;
    $mcrypt->{Verbose}  = $arg{'verbose'} if (defined($arg{'verbose'}));
    $mcrypt->{KEY_SIZE} = mcrypt_get_key_size($td);
    $mcrypt->{IV_SIZE}  = mcrypt_get_iv_size($td);
    $mcrypt->{BLOCK_SIZE} = -1;    # default to not be in block mode
    $mcrypt->{BLOCK_SIZE} = mcrypt_get_block_size($td)
      if (mcrypt_is_block_algorithm_mode($td));
    return $mcrypt;
}

sub init
{
    my ($self, $key, $iv) = @_;
    if ($self->{Verbose})
    {
        print STDERR "Crypt::Libmcrypt: your initialization vector is the wrong length.\n"
          if (   $self->{Verbose}
              && ($self->{IV_SIZE} > 0)
              && ($self->{IV_SIZE} != length($iv)));
    }
    return ($self->{Initialized} = mcrypt_init($self->{TD}, $key, $iv));
}

sub end
{
    my ($self) = shift;
    my $ret = 0;
    $ret = mcrypt_end($self->{TD})
      if ($self->{Initialized} && $self->{TD});
    $self->{TD} = 0;
    return $ret;
}

sub encrypt
{
    my ($self, $input) = @_;
    return undef unless ($self->{Initialized});
    if ($self->{Verbose})
    {
        print STDERR "Crypt::Libmcrypt: running block mode and your input is not a valid length.\n"
          if (   $self->{Verbose}
              && ($self->{BLOCK_SIZE} > 0)
              && ($self->{BLOCK_SIZE} != length($input)));
    }
    return mcrypt_encrypt($self->{TD}, $input);
}

sub decrypt
{
    my ($self, $input) = @_;
    return undef unless ($self->{Initialized});
    if ($self->{Verbose})
    {
        print STDERR "Crypt::Libmcrypt: running block mode and your input is not a valid length.\n"
          if (   $self->{Verbose}
              && ($self->{BLOCK_SIZE} > 0)
              && ($self->{BLOCK_SIZE} != length($input)));
    }
    return mcrypt_decrypt($self->{TD}, $input);
}

sub DESTROY
{
    my ($self) = shift;
    if ($self->{Initialized})
    {
        $self->end();
    }
    else
    {
        mcrypt_unload($self->{TD});
    }
}

1;

__END__

=pod

=encoding utf-8
=head1 NAME

Crypt::Libmcrypt - Perl extension for libmcrypt,the mcrypt cryptographic library

=head1 VERSION

version 1.0.2

=head1 SYNOPSIS

use Crypt::Libmcrypt;

# Procedural routines

$td = Crypt::Libmcrypt::mcrypt_load($algorithm, $algorithm_dir,
$mode, $mode_dir);

Crypt::Libmcrypt::mcrypt_get_key_size($td);   # in bytes
Crypt::Libmcrypt::mcrypt_get_iv_size($td);    # in bytes
Crypt::Libmcrypt::mcrypt_get_block_size($td); # in bytes

Crypt::Libmcrypt::mcrypt_init($td, $key, $iv);

$encryptedstr = Crypt::Libmcrypt::mcrypt_encrypt($td, $decryptedstr);
$decryptedstr = Crypt::Libmcrypt::mcrypt_decrypt($td, $encryptedstr);

Crypt::Libmcrypt::mcrypt_end($td);

# Object-oriented methods

$td = Crypt::Libmcrypt->new( algorithm => $algorithm,
mode => $mode );

$keysize = $td->{KEY_SIZE};
$ivsize  = $td->{IV_SIZE};
$blksize = $td->{BLOCK_SIZE};

$td->init($key, $iv);

$encryptedstr = $td->encrypt($decryptedstr);
$decryptedstr = $td->decrypt($encryptedstr);

# If the $td goes out of context,
# the destructor will do this for you
$td->end();

=head1 DESCRIPTION

Perl extension for libmcrypt,the mcrypt cryptographic library.

Please note that the CTR mode needs this pacthed libmcrypt-2.5.8
https://osdn.net/projects/mcrypt/ to work.

=head1 Exported constants

The predefined groups of exports in the use statements are as follows:

use Crypt::Libmcrypt qw(:ALGORITHMS);

Exports the BLOWFISH DES 3DES 3WAY GOST SAFER_SK64 SAFER_SK128
CAST_128 XTEA RC2 TWOFISH CAST_256 SAFERPLUS LOKI97 SERPENT
RIJNDAEL_128 RIJNDAEL_192 RIJNDAEL_256 ENIGMA ARCFOUR WAKE 
algorithms.  See the mcrypt(3) man page for more details.

use Crypt::Libmcrypt qw(:MODES);

Exports the CBC CFB CTR ECB OFB nCFB nOFB STREAM modes of encryption.  See the
mcrypt(3) man page for more details.

use Crypt::Libmcrypt qw(:FUNCS);

Exports the following functions: mcrypt_load, mcrypt_unload,
mcrypt_init, mcrypt_end, mcrypt_encrypt, mcrypt_decrypt,
mcrypt_get_block_size, mcrypt_get_iv_size, mcrypt_get_key_size.

=head1 EXAMPLES

# Procedural approach:
# create an ecryption descriptor:
#   ALGORITHM: blowfish (256 bit key + 16 byte IV)
#   MODE:      cfb
# The user application has set:
#   $method to either "encrypt" or "decrypt"
#   $infile to the input filename
#   $outfile to the output filename
my($td) = Crypt::Libmcrypt::mcrypt_load( Crypt::Libmcrypt::BLOWFISH, '',
Crypt::Libmcrypt::CFB, '' );
my($key) = "32 bytes of your apps secret key";  # secret key
my($iv) = "16 bytes of rand"; # shared initialization vector
Crypt::Libmcrypt::mcrypt_init($td, $key, $iv) || die "Could not initialize td";
print Crypt::Libmcrypt::mcrypt_encrypt($td, $_) while(<>);
Crypt::Libmcrypt::mcrypt_end($td);

# OO approach of the above except decrypting
my($td) = Crypt::Libmcrypt->new( algorithm => Crypt::Libmcrypt::BLOWFISH,
mode => Crypt::Libmcrypt::CFB,
verbose => 0 );
my($key) = "k" x $td->{KEY_SIZE};
my($iv) = "i" x $td->{IV_SIZE};
$td->init($key, $iv);
print $td->decrypt($_) while (<>);
$td->end();

=head1 AUTHOR

Li ZHOU <lzh@cpan.org>

=head1 SEE ALSO

The libmcrypt man page: mcrypt(3).  Other libmcrypt information is
available at http://mcrypt.sourceforge.net.


=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2017 by Li ZHOU.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut
