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