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 20 20 100.0
pod 1 1 100.0
total 189 219 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     565   220716 use warnings;
  7         19  
  7         223  
5 7     401   31 use strict;
  7         17  
  7         142  
6              
7 7     7   883 use Crypt::Digest qw(digest_data);
  7         1585  
  7         303  
8 7     7   868 use Errno;
  7         2521  
  7         240  
9 7     7   43 use File::KDBX::Error;
  7         20  
  7         345  
10 7     7   125 use File::KDBX::Util qw(:class :io);
  7         13  
  7         828  
11 7     7   45 use IO::Handle;
  7         13  
  7         206  
12 7     7   2903 use namespace::clean;
  7         20  
  7         67  
13              
14             extends 'File::KDBX::IO';
15              
16             our $VERSION = '0.906'; # 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   2942 no strict 'refs'; ## no critic (ProhibitNoStrict)
  7         14  
  7         7922  
31             *$attr = sub {
32 614     614   808 my $self = shift;
        614      
33 614 100       1172 *$self->{$attr} = shift if @_;
34 614 100 100     238364 *$self->{$attr} //= (ref $default eq 'CODE') ? $default->($self) : $default;
35             };
36             }
37              
38              
39             sub new {
40 28     28 1 36885 my $class = shift;
41 28 50       268 my %args = @_ % 2 == 1 ? (fh => shift, @_) : @_;
42 28         200 my $self = $class->SUPER::new;
43 28 50       134 $self->_fh($args{fh}) or throw 'IO handle required';
44 28         188 $self->algorithm($args{algorithm});
45 28         165 $self->block_size($args{block_size});
46 28         101 $self->_buffer;
47 28         78 return $self;
48             }
49              
50             sub _FILL {
51 107     107   200 my ($self, $fh) = @_;
52              
53 107 50       270 $ENV{DEBUG_STREAM} and print STDERR "FILL\t$self\n";
54 107 100       228 return if $self->_finished;
55              
56 56         101 my $block = eval { $self->_read_hash_block($fh) };
  56         188  
57 56 100       246 if (my $err = $@) {
58 1         18 $self->_set_error($err);
59 1         8 return;
60             }
61 55 100       798 return $$block if defined $block;
62             }
63              
64             sub _WRITE {
65 19     19   14338 my ($self, $buf, $fh) = @_;
66              
67 19 50       97 $ENV{DEBUG_STREAM} and print STDERR "WRITE\t$self\n";
68 19 50       81 return 0 if $self->_finished;
69              
70 19         39 ${$self->_buffer} .= $buf;
  19         51  
71              
72 19         126 $self->_FLUSH($fh);
73              
74 19         1469 return length($buf);
75             }
76              
77             sub _POPPED {
78 28     28   80 my ($self, $fh) = @_;
79              
80 28 50       101 $ENV{DEBUG_STREAM} and print STDERR "POPPED\t$self\n";
81 28 100       94 return if $self->_mode ne 'w';
82              
83 11         45 $self->_FLUSH($fh);
84 11         25 eval {
85 11         55 $self->_write_next_hash_block($fh); # partial block with remaining content
86 11         82 $self->_write_final_hash_block($fh); # terminating block
87             };
88 11 50       47 $self->_set_error($@) if $@;
89             }
90              
91             sub _FLUSH {
92 30     30   80 my ($self, $fh) = @_;
93              
94 30 50       92 $ENV{DEBUG_STREAM} and print STDERR "FLUSH\t$self\n";
95 30 50       86 return if $self->_mode ne 'w';
96              
97 30         68 eval {
98 30         80 while ($self->block_size <= length(${*$self->{_buffer}})) {
  55         214  
99 25         126 $self->_write_next_hash_block($fh);
100             }
101             };
102 30 50       90 if (my $err = $@) {
103 0         0 $self->_set_error($err);
104 0         0 return -1;
105             }
106              
107 30         50 return 0;
108             }
109              
110             ##############################################################################
111              
112             sub _read_hash_block {
113 56     56   119 my $self = shift;
114 56         85 my $fh = shift;
115              
116 56 50       246 read_all $fh, my $buf, 4 or throw 'Failed to read hash block index';
117 56         278 my ($index) = unpack('L<', $buf);
118              
119 56 100       166 $index == $self->_block_index or throw 'Invalid block index', index => $index;
120              
121 55 50       139 read_all $fh, my $hash, 32 or throw 'Failed to read hash';
122              
123 55 50       154 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       140 if ($size == 0) {
127 16 50       68 $hash eq ("\0" x 32) or throw 'Invalid final block hash', hash => $hash;
128 16         54 $self->_finished(1);
129 16         115 return undef;
130             }
131              
132 39 50       113 read_all $fh, my $block, $size or throw 'Failed to read hash block', index => $index, size => $size;
133              
134 39         230 my $got_hash = digest_data($self->algorithm, $block);
135 39 50       384 $hash eq $got_hash
136             or throw 'Hash mismatch', index => $index, size => $size, got => $got_hash, expected => $hash;
137              
138 39         143 *$self->{_block_index}++;
139 39         295 return \$block;
140             }
141              
142             sub _write_next_hash_block {
143 36     36   64 my $self = shift;
144 36         70 my $fh = shift;
145              
146 36         63 my $size = length(${$self->_buffer});
  36         93  
147 36 100       91 $size = $self->block_size if $self->block_size < $size;
148 36 100       110 return 0 if $size == 0;
149              
150 34         58 my $block = substr(${$self->_buffer}, 0, $size, '');
  34         108  
151              
152 34         169 my $buf = pack('L<', $self->_block_index);
153 34 50       230 print $fh $buf or throw 'Failed to write hash block index';
154              
155 34         148 my $hash = digest_data($self->algorithm, $block);
156 34 50       409 print $fh $hash or throw 'Failed to write hash';
157              
158 34         198 $buf = pack('L<', length($block));
159 34 50       102 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       182843 print $fh $block or throw 'Failed to hash write block';
163              
164 34         229 *$self->{_block_index}++;
165 34         214 return 0;
166             }
167              
168             sub _write_final_hash_block {
169 11     11   27 my $self = shift;
170 11         21 my $fh = shift;
171              
172 11         38 my $buf = pack('L<', $self->_block_index);
173 11 50       49 print $fh $buf or throw 'Failed to write hash block index';
174              
175 11         42 my $hash = "\0" x 32;
176 11 50       31 print $fh $hash or throw 'Failed to write hash';
177              
178 11         33 $buf = pack('L<', 0);
179 11 50       42 print $fh $buf or throw 'Failed to write hash block size';
180              
181 11         45 $self->_finished(1);
182 11         26 return 0;
183             }
184              
185             sub _set_error {
186 1     1   3 my $self = shift;
187 1 50       9 $ENV{DEBUG_STREAM} and print STDERR "err\t$self\n";
188 1 50       13 if (exists &Errno::EPROTO) {
    0          
189 1         12 $! = &Errno::EPROTO;
190             }
191             elsif (exists &Errno::EIO) {
192 0         0 $! = &Errno::EIO;
193             }
194 1         9 $self->_error($ERROR = error(@_));
195             }
196              
197             1;
198              
199             __END__