File Coverage

lib/App/MtAws/TreeHash.pm
Criterion Covered Total %
statement 116 125 92.8
branch 31 40 77.5
condition 9 9 100.0
subroutine 15 16 93.7
pod 0 8 0.0
total 171 198 86.3


line stmt bran cond sub pod time code
1             # mt-aws-glacier - Amazon Glacier sync client
2             # Copyright (C) 2012-2014 Victor Efimov
3             # http://mt-aws.com (also http://vs-dev.com) vs@vs-dev.com
4             # License: GPLv3
5             #
6             # This file is part of "mt-aws-glacier"
7             #
8             # mt-aws-glacier is free software: you can redistribute it and/or modify
9             # it under the terms of the GNU General Public License as published by
10             # the Free Software Foundation, either version 3 of the License, or
11             # (at your option) any later version.
12             #
13             # mt-aws-glacier is distributed in the hope that it will be useful,
14             # but WITHOUT ANY WARRANTY; without even the implied warranty of
15             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16             # GNU General Public License for more details.
17             #
18             # You should have received a copy of the GNU General Public License
19             # along with this program. If not, see <http://www.gnu.org/licenses/>.
20              
21              
22             # 1) eat_data() for data
23             # 2) eat_1mb for data
24             # 3) define_hash
25             #
26              
27              
28             # treehash() = treehash(1,7) = sha ( treehash(1,4), teehash(5,7))
29             # treehash(1,4) = treehash(1,2).treehash(3,4)
30             # treehash(1,
31             #
32              
33              
34             package App::MtAws::TreeHash;
35              
36             our $VERSION = '1.114_2';
37              
38 33     33   143467 use strict;
  33         45  
  33         972  
39 33     33   156 use warnings;
  33         76  
  33         1061  
40 33     33   10863 use Digest::SHA qw/sha256/;
  33         51537  
  33         2068  
41 33     33   259 use List::Util qw/max/;
  33         51  
  33         2126  
42 33     33   171 use Carp;
  33         59  
  33         42583  
43              
44              
45              
46             sub new
47             {
48 1998     1998 0 495637 my ($class, %args) = @_;
49 1998         2128 my $self = \%args;
50 1998         2529 $self->{tree} = [];
51 1998         2256 $self->{pending} = {};
52 1998   100     3577 $self->{unit} ||= 1048576;
53 1998         2008 $self->{processed_size} = 0; # MB
54 1998         1949 bless $self, $class;
55 1998         2831 return $self;
56             }
57              
58              
59             sub eat_file
60             {
61 0     0 0 0 my ($self, $fh) = @_;
62 0         0 while () {
63 0         0 my $r = read($fh, my $data, $self->{unit});
64 0 0       0 if (!defined($r)) {
    0          
65 0         0 die $!;
66             } elsif ($r > 0) {
67 0         0 $self->_eat_data_one_mb(\$data);
68             } else {
69 0         0 return;
70             }
71             }
72             }
73              
74             sub eat_data
75             {
76 1123     1123 0 2532 my $self = $_[0];
77 1123 100       1831 my $dataref = (ref($_[1]) eq '') ? \$_[1] : $_[1];
78 1123         1123 my $mb = $self->{unit};
79 1123         1078 my $n = length($$dataref);
80             # TODO: we should preserve last chunk of data actually, if it's smaller that chunk. (or create new method)
81 1123 100       1934 if ($n <= $mb) {
82 35         67 $self->_eat_data_one_mb($dataref);
83             } else {
84 1088         837 my $i = 0;
85 1088         1698 while ($i < $n) {
86 91033         104308 my $part = substr($$dataref, $i, $mb);
87 91033         112421 $self->_eat_data_one_mb(\$part);
88 91033         136365 $i += $mb
89             }
90             }
91             }
92              
93             sub eat_data_any_size
94             {
95 2384     2384 0 7881 my $self = $_[0];
96 2384 50       3128 my $dataref = (ref($_[1]) eq '') ? \$_[1] : $_[1];
97 2384         1898 my $mb = $self->{unit};
98 2384         1910 my $n = length($$dataref);
99 2384 100       2674 if (defined $self->{buffer}) {
100 1754         2484 $self->{buffer} .= $$dataref;
101             } else {
102 630         5379 $self->{buffer} = $$dataref;
103             }
104 2384 100       4419 if (length($self->{buffer}) == $mb) {
    100          
105 375         475 $self->_eat_data_one_mb($self->{buffer});
106 375         534 $self->{buffer} = '';
107             } elsif (length($self->{buffer}) > $mb) {
108 1560         1044 my $i = -0;
109 1560         2228 while ($i + $mb <= length($self->{buffer})) { # TODO this loop for performance optimization, and optimization is not tested
110 4634         8590 my $part = substr($self->{buffer}, $i, $mb);
111 4634         4987 $self->_eat_data_one_mb($part);
112 4634         6808 $i += $mb;
113             }
114 1560         2879 $self->{buffer} = substr($self->{buffer}, $i);
115             }
116             }
117              
118             sub eat_another_treehash
119             {
120 200     200 0 327 my ($self, $th) = @_;
121 200 50       359 croak unless $th->isa("App::MtAws::TreeHash");
122 200   100     238 $self->{tree}->[0] ||= [];
123 200         119 my $cnt = scalar @{ $self->{tree}->[0] };
  200         150  
124 200 100       259 my $newstart = $cnt ? $self->{tree}->[0]->[$cnt - 1]->{finish} + 1 : 0;
125            
126 200         164 push @{$self->{tree}->[0]}, map {
127 200         114 $newstart++;
128 200         660 { joined => 9, start => $newstart-1, finish => $newstart-1, hash => $_->{hash} };
129 200         129 } @{$th->{tree}->[0]};
  200         179  
130             }
131              
132              
133             sub _eat_data_one_mb
134             {
135 96998     96998   66395 my $self = $_[0];
136 96998 100       122221 my $dataref = (ref($_[1]) eq '') ? \$_[1] : $_[1];
137 96998   100     138731 $self->{tree}->[0] ||= [];
138              
139 96998 100       119945 if ($self->{last_chunk}) {
140 1         173 croak "Previous chunk of data was less than 1MiB";
141             }
142 96997 50       170639 if (length($$dataref) > $self->{unit}) {
    100          
143 0         0 croak "data chunk exceed 1MiB".length($$dataref);
144             } elsif (length($$dataref) < $self->{unit}) {
145 1215         1219 $self->{last_chunk} = 1;
146             }
147            
148 96997         61044 push @{ $self->{tree}->[0] }, { joined => 0, start => $self->{processed_size}, finish => $self->{processed_size}, hash => sha256($$dataref) };
  96997         1206013  
149 96997         113063 $self->{processed_size}++;
150             }
151              
152             sub calc_tree
153             {
154 1739     1739 0 216800 my ($self) = @_;
155 1739 100 100     5313 $self->_eat_data_one_mb($self->{buffer}) if defined($self->{buffer}) && length($self->{buffer});
156 1739         1315 my $prev_level = 0;
157 1739         1344 while (scalar @{ $self->{tree}->[$prev_level] } > 1) {
  7762         12947  
158 6023         4818 my $curr_level = $prev_level+1;
159 6023         5572 $self->{tree}->[$curr_level] = [];
160            
161 6023         4576 my $prev_tree = $self->{tree}->[$prev_level];
162 6023         4441 my $curr_tree = $self->{tree}->[$curr_level];
163 6023         4171 my $len = scalar @$prev_tree;
164 6023         8103 for (my $i = 0; $i < $len; $i += 2) {
165 57387 100       59576 if ($len - $i > 1) {
166 54459         41051 my $a = $prev_tree->[$i];
167 54459         43454 my $b = $prev_tree->[$i+1];
168 54459         389449 push @$curr_tree, { joined => 0, start => $a->{start}, finish => $b->{finish}, hash => sha256( $a->{hash}.$b->{hash} ) };
169             } else {
170 2928         4935 push @$curr_tree, $prev_tree->[$i];
171             }
172             }
173            
174 6023         4624 $prev_level = $curr_level;
175             }
176             }
177              
178              
179             sub calc_tree_recursive
180             {
181 117     117 0 341 my ($self) = @_;
182 117         131 my %h = map { $_->{start} => $_ } @{$self->{tree}->[0]};
  40890         72544  
  117         355  
183 117         16128 $self->{max} = max keys %h;
184 117         1752 $self->{by_position} = \%h;
185            
186 117         225 $self->{treehash_recursive_tree} = $self->_treehash_recursive();
187             }
188              
189             sub _treehash_recursive
190             {
191 81780     81780   68659 my ($self, $a, $b) = @_;
192 81780 100       85290 if (defined($a)) {
193 81663 100       79461 if ($a == $b) {
194 40890         296438 return $self->{by_position}->{$a}->{hash};
195             } else {
196 40773         42090 my $middle = _maxpower($b-$a) + $a;
197 40773         51832 return sha256 ($self->_treehash_recursive($a, $middle - 1 ).$self->_treehash_recursive($middle, $b));
198             }
199             } else {
200 117         215 return $self->_treehash_recursive(0,$self->{max});
201             }
202             }
203              
204             sub _maxpower
205             {
206 40773     40773   29171 my ($x) = @_;
207 40773 50       52927 die if $x == 0;
208 40773         30589 $x |= $x >> 1;
209 40773         27329 $x |= $x >> 2;
210 40773         27272 $x |= $x >> 4;
211 40773         26428 $x |= $x >> 8;
212 40773         27048 $x |= $x >> 16;
213 40773         25913 $x >>= 1;
214 40773         25015 $x++;
215 40773         35961 return $x;
216             }
217              
218              
219              
220             sub get_final_hash
221             {
222 1756     1756 0 3341 my ($self) = @_;
223 1756 50       2239 if (defined $self->{treehash_recursive_tree}) {
224 0         0 return unpack('H*', $self->{treehash_recursive_tree} );
225             } else {
226 1756         1500 return unpack('H*', $self->{tree}->[ $#{$self->{tree}} ]->[0]->{hash} );
  1756         7725  
227             }
228             }
229              
230              
231             1;