AWS-Signature-V4

 view release on metacpan or  search on metacpan

lib/AWS/Signature/V4/Checksum.pm  view on Meta::CPAN

package AWS::Signature::V4::Checksum;
use v5.24;
use Moo;
use Compress::Raw::Zlib ();
use Digest::SHA ();
use MIME::Base64 qw< encode_base64 >;
use AWS::Signature::V4::Error qw< fail >;
use experimental qw< signatures >;

use constant {
   # name => size of the raw checksum, in bytes
   SIZE_FOR => +{ crc32 => 4, crc32c => 4, sha1 => 20, sha256 => 32 },

   # crc32c comes from String::CRC32C when it is installed (it is much
   # faster), otherwise from the slicing-by-4 code below: $T0[$b] is the
   # effect of byte $b on the register, $T1 to $T3 that of the same byte
   # followed by 1 to 3 zeros
   CRC32C_XS => eval { require String::CRC32C; \&String::CRC32C::crc32c },
};

use namespace::clean;

has algorithm => (is => 'ro', required => 1);

# running state: the CRCs keep a number, the SHA ones a Digest::SHA object
has _crc => (is => 'rw', lazy => 1, builder => 1);
has _sha => (is => 'lazy');

# the algorithm is given as the only, positional, argument: new('crc32')
around BUILDARGS => sub ($orig, $class, @args) {
   return {algorithm => $args[0]} if @args == 1 && !ref $args[0];
   return $class->$orig(@args);
};

sub _build__crc ($self) { $self->algorithm eq 'crc32c' ? 0xFFFFFFFF : 0 }
sub _build__sha ($self) { Digest::SHA->new($self->algorithm eq 'sha1' ? 1 : 256) }

sub encoded_size ($class, $algo) { 4 * int((SIZE_FOR->{$algo} + 2) / 3) }

sub add ($self, $ref) {    # $ref: reference to a byte string
   fail 400, 'checksum data must be a reference to a byte string'
      unless ref $ref eq 'SCALAR' && defined $$ref;
   utf8::is_utf8($$ref) && !utf8::downgrade(my $copy = $$ref, 1)
      and fail 400, 'checksum data must be a byte string: encode characters first';
   my $algo = $self->algorithm;
   if ($algo eq 'crc32') {
      $self->_crc(Compress::Raw::Zlib::crc32($$ref, $self->_crc));
   }
   elsif ($algo eq 'crc32c') {
      $self->_crc(_crc32c($self->_crc, $ref));
   }
   else { $self->_sha->add($$ref) }
   return $self;
}

# register after the data; String::CRC32C wants and gives it inverted
sub _crc32c ($crc, $ref) {
   return CRC32C_XS->($$ref, $crc ^ 0xFFFFFFFF) ^ 0xFFFFFFFF if CRC32C_XS;

   state $T0 = [
      map {
         my $c = $_;
         $c = ($c & 1) ? (($c >> 1) ^ 0x82F63B78) : ($c >> 1) for 1 .. 8;
         $c;
      } 0 .. 255
   ];
   state $T1 = [ map { $T0->[$T0->[$_] & 0xFF] ^ ($T0->[$_] >> 8) } 0 .. 255 ];
   state $T2 = [ map { $T0->[$T1->[$_] & 0xFF] ^ ($T1->[$_] >> 8) } 0 .. 255 ];
   state $T3 = [ map { $T0->[$T2->[$_] & 0xFF] ^ ($T2->[$_] >> 8) } 0 .. 255 ];


   my $words = length($$ref) & ~3;
   for (my $offset = 0; $offset < $words; $offset += 65536) {    # bounded lists
      my $size = $words - $offset < 65536 ? $words - $offset : 65536;
      for my $word (unpack 'V*', substr $$ref, $offset, $size) {
         my $c = $crc ^ $word;
         $crc = $T3->[$c & 0xFF]
                ^ $T2->[($c >> 8) & 0xFF]
                ^ $T1->[($c >> 16) & 0xFF]
                ^ $T0->[$c >> 24];
      }
   }
   $crc = $T0->[($crc ^ $_) & 0xFF] ^ ($crc >> 8) for unpack 'C*', substr $$ref, $words;
   return $crc;
}

sub base64 ($self) {
   my $algo = $self->algorithm;
   my $raw =
        $algo eq 'crc32'  ? pack('N', $self->_crc)
      : $algo eq 'crc32c' ? pack('N', $self->_crc ^ 0xFFFFFFFF)
      :                     $self->_sha->digest;
   return encode_base64($raw, '');
}

sub is_supported_algorithm ($co, $name) { exists(SIZE_FOR->{$name}) }

sub supported_algorithms { sort { $a cmp $b } keys(SIZE_FOR->%*) }

1;



( run in 1.631 second using v1.01-cache-2.11-cpan-16aabcbfbcb )