File Coverage

blib/lib/File/KDBX/IO/HashBlock.pm
Criterion Covered Total %
statement 120 123 97.5
branch 45 72 62.5
condition 3 3 100.0
subroutine 21 21 100.0
pod 1 1 100.0
total 190 220 86.3


line stmt bran cond sub pod time code
1             package File::KDBX::IO::HashBlock;
2             # ABSTRACT: Hash block stream IO handle
3              
4 7     56   206233 use warnings;
  7         14  
  7         199  
5 7     7   31 use strict;
  7         13  
  7         139  
6              
7 7     7   732 use Crypt::Digest qw(digest_data);
  7         1329  
  7         251  
8 7     7   728 use Errno;
  7         2194  
  7         176  
9 7     7   40 use File::KDBX::Error;
  7         13  
  7         292  
10 7     7   98 use File::KDBX::Util qw(:class :io);
  7         24  
  7         738  
11 7     7   49 use IO::Handle;
  7         11  
  7         189  
12 7     7   1958 use namespace::clean;
  7         14  
  7         57  
13              
14             extends 'File::KDBX::IO';
15              
16             our $VERSION = '0.905'; # VERSION
17             our $ALGORITHM = 'SHA256';
18             our $BLOCK_SIZE = 1048576; # 1MiB
19             our $ERROR;
20              
21              
22             my %ATTRS = (
23             _block_index => 0,
24             _buffer => sub { \(my $buf = '') },
25             _finished => 0,
26             algorithm => sub { $ALGORITHM },
27             block_size => sub { $BLOCK_SIZE },
28             );
29             while (my ($attr, $default) = each %ATTRS) {
30 7     7   2575 no strict 'refs'; ## no critic (ProhibitNoStrict)
  7         12  
  7         7548  
31             *$attr = sub {
32 614     614   761 my $self = shift;
        614      
        530      
33 614 100       1108 *$self->{$attr} = shift if @_;
34 614 100 100     226427 *$self->{$attr} //= (ref $default eq 'CODE') ? $default->($self) : $default;
35             };
36             }
37              
38              
39             sub new {
40 28     28 1 29201 my $class = shift;
41 28 50       205 my %args = @_ % 2 == 1 ? (fh => shift, @_) : @_;
42 28         157 my $self = $class->SUPER::new;
43 28 50       127 $self->_fh($args{fh}) or throw 'IO handle required';
44 28         143 $self->algorithm($args{algorithm});
45 28         133 $self->block_size($args{block_size});
46 28         94 $self->_buffer;
47 28         70 return $self;
48             }
49              
50             sub _FILL {
51 107     107   211 my ($self, $fh) = @_;
52              
53 107 50       253 $ENV{DEBUG_STREAM} and print STDERR "FILL\t$self\n";
54 107 100       215 return if $self->_finished;
55              
56 56         97 my $block = eval { $self->_read_hash_block($fh) };
  56         142  
57 56 100       176 if (my $err = $@) {
58 1         14 $self->_set_error($err);
59 1         14 return;
60             }
61 55 100       453 return $$block if defined $block;
62             }
63              
64             sub _WRITE {
65 19     19   12561 my ($self, $buf, $fh) = @_;
66              
67 19 50       76 $ENV{DEBUG_STREAM} and print STDERR "WRITE\t$self\n";
68 19 50       79 return 0 if $self->_finished;
69              
70 19         38 ${$self->_buffer} .= $buf;
  19         59  
71              
72 19         85 $self->_FLUSH($fh);
73              
74 19         958 return length($buf);
75             }
76              
77             sub _POPPED {
78 28     28   66 my ($self, $fh) = @_;
79              
80 28 50       93 $ENV{DEBUG_STREAM} and print STDERR "POPPED\t$self\n";
81 28 100       79 return if $self->_mode ne 'w';
82              
83 11         36 $self->_FLUSH($fh);
84 11         17 eval {
85 11         43 $self->_write_next_hash_block($fh); # partial block with remaining content
86 11         40 $self->_write_final_hash_block($fh); # terminating block
87             };
88 11 50       69 $self->_set_error($@) if $@;
89             }
90              
91             sub _FLUSH {
92 30     30   59 my ($self, $fh) = @_;
93              
94 30 50       66 $ENV{DEBUG_STREAM} and print STDERR "FLUSH\t$self\n";
95 30 50       74 return if $self->_mode ne 'w';
96              
97 30         69 eval {
98 30         69 while ($self->block_size <= length(${*$self->{_buffer}})) {
  55         151  
99 25         70 $self->_write_next_hash_block($fh);
100             }
101             };
102 30 50       87 if (my $err = $@) {
103 0         0 $self->_set_error($err);
104 0         0 return -1;
105             }
106              
107 30         45 return 0;
108             }
109              
110             ##############################################################################
111              
112             sub _read_hash_block {
113 56     56   112 my $self = shift;
114 56         79 my $fh = shift;
115              
116 56 50       190 read_all $fh, my $buf, 4 or throw 'Failed to read hash block index';
117 56         201 my ($index) = unpack('L<', $buf);
118              
119 56 100       136 $index == $self->_block_index or throw 'Invalid block index', index => $index;
120              
121 55 50       127 read_all $fh, my $hash, 32 or throw 'Failed to read hash';
122              
123 55 50       124 read_all $fh, $buf, 4 or throw 'Failed to read hash block size';
124 55         145 my ($size) = unpack('L<', $buf);
125              
126 55 100       152 if ($size == 0) {
127 16 50       46 $hash eq ("\0" x 32) or throw 'Invalid final block hash', hash => $hash;
128 16         50 $self->_finished(1);
129 16         98 return undef;
130             }
131              
132 39 50       92 read_all $fh, my $block, $size or throw 'Failed to read hash block', index => $index, size => $size;
133              
134 39         117 my $got_hash = digest_data($self->algorithm, $block);
135 39 50       160 $hash eq $got_hash
136             or throw 'Hash mismatch', index => $index, size => $size, got => $got_hash, expected => $hash;
137              
138 39         74 *$self->{_block_index}++;
139 39         182 return \$block;
140             }
141              
142             sub _write_next_hash_block {
143 36     36   51 my $self = shift;
144 36         51 my $fh = shift;
145              
146 36         51 my $size = length(${$self->_buffer});
  36         62  
147 36 100       63 $size = $self->block_size if $self->block_size < $size;
148 36 100       78 return 0 if $size == 0;
149              
150 34         63 my $block = substr(${$self->_buffer}, 0, $size, '');
  34         56  
151              
152 34         99 my $buf = pack('L<', $self->_block_index);
153 34 50       157 print $fh $buf or throw 'Failed to write hash block index';
154              
155 34         87 my $hash = digest_data($self->algorithm, $block);
156 34 50       125 print $fh $hash or throw 'Failed to write hash';
157              
158 34         86 $buf = pack('L<', length($block));
159 34 50       79 print $fh $buf or throw 'Failed to write hash block size';
160              
161             # $fh->write($block, $size) or throw 'Failed to hash write block';
162 34 50       160853 print $fh $block or throw 'Failed to hash write block';
163              
164 34         141 *$self->{_block_index}++;
165 34         111 return 0;
166             }
167              
168             sub _write_final_hash_block {
169 11     11   20 my $self = shift;
170 11         20 my $fh = shift;
171              
172 11         30 my $buf = pack('L<', $self->_block_index);
173 11 50       38 print $fh $buf or throw 'Failed to write hash block index';
174              
175 11         26 my $hash = "\0" x 32;
176 11 50       25 print $fh $hash or throw 'Failed to write hash';
177              
178 11         25 $buf = pack('L<', 0);
179 11 50       32 print $fh $buf or throw 'Failed to write hash block size';
180              
181 11         38 $self->_finished(1);
182 11         23 return 0;
183             }
184              
185             sub _set_error {
186 1     1   10 my $self = shift;
187 1 50       11 $ENV{DEBUG_STREAM} and print STDERR "err\t$self\n";
188 1 50       7 if (exists &Errno::EPROTO) {
    0          
189 1         9 $! = &Errno::EPROTO;
190             }
191             elsif (exists &Errno::EIO) {
192 0         0 $! = &Errno::EIO;
193             }
194 1         5 $self->_error($ERROR = error(@_));
195             }
196              
197             1;
198              
199             __END__