| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package Digest::ssdeep; | 
| 2 |  |  |  |  |  |  |  | 
| 3 | 3 |  |  | 3 |  | 147630 | use warnings; | 
|  | 3 |  |  |  |  | 7 |  | 
|  | 3 |  |  |  |  | 196 |  | 
| 4 | 3 |  |  | 3 |  | 17 | use strict; | 
|  | 3 |  |  |  |  | 5 |  | 
|  | 3 |  |  |  |  | 106 |  | 
| 5 | 3 |  |  | 3 |  | 16 | use Carp; | 
|  | 3 |  |  |  |  | 8 |  | 
|  | 3 |  |  |  |  | 277 |  | 
| 6 | 3 |  |  | 3 |  | 3176 | use Text::WagnerFischer qw/distance/; | 
|  | 3 |  |  |  |  | 2707 |  | 
|  | 3 |  |  |  |  | 250 |  | 
| 7 | 3 |  |  | 3 |  | 19 | use List::Util qw/max/; | 
|  | 3 |  |  |  |  | 7 |  | 
|  | 3 |  |  |  |  | 348 |  | 
| 8 |  |  |  |  |  |  |  | 
| 9 | 3 |  |  | 3 |  | 3429 | use version; | 
|  | 3 |  |  |  |  | 10035 |  | 
|  | 3 |  |  |  |  | 18 |  | 
| 10 |  |  |  |  |  |  | our $VERSION = qv('0.9.3'); | 
| 11 |  |  |  |  |  |  |  | 
| 12 |  |  |  |  |  |  | BEGIN { | 
| 13 | 3 |  |  | 3 |  | 16 | require Exporter; | 
| 14 | 3 |  |  | 3 |  | 333 | use vars qw(@ISA @EXPORT_OK); | 
|  | 3 |  |  |  |  | 5 |  | 
|  | 3 |  |  |  |  | 180 |  | 
| 15 | 3 |  |  |  |  | 46 | @ISA       = qw(Exporter); | 
| 16 | 3 |  |  |  |  | 345 | @EXPORT_OK = qw( | 
| 17 |  |  |  |  |  |  | ssdeep_hash | 
| 18 |  |  |  |  |  |  | ssdeep_hash_file | 
| 19 |  |  |  |  |  |  | ssdeep_compare | 
| 20 |  |  |  |  |  |  | ssdeep_dump_last | 
| 21 |  |  |  |  |  |  | ); | 
| 22 |  |  |  |  |  |  | } | 
| 23 |  |  |  |  |  |  |  | 
| 24 | 3 |  |  | 3 |  | 16 | use constant FNV_PRIME  => 0x01000193; | 
|  | 3 |  |  |  |  | 7 |  | 
|  | 3 |  |  |  |  | 211 |  | 
| 25 | 3 |  |  | 3 |  | 35 | use constant FNV_INIT   => 0x28021967; | 
|  | 3 |  |  |  |  | 17 |  | 
|  | 3 |  |  |  |  | 112 |  | 
| 26 | 3 |  |  | 3 |  | 14 | use constant MAX_LENGTH => 64; | 
|  | 3 |  |  |  |  | 5 |  | 
|  | 3 |  |  |  |  | 716 |  | 
| 27 |  |  |  |  |  |  |  | 
| 28 |  |  |  |  |  |  | # Weights: | 
| 29 |  |  |  |  |  |  | #  same                = 0 | 
| 30 |  |  |  |  |  |  | #  insertion/deletion  = 1 | 
| 31 |  |  |  |  |  |  | #  mismatch            = 2 | 
| 32 |  |  |  |  |  |  | #  swap                = N/A (should be 5) | 
| 33 |  |  |  |  |  |  | $Text::WagnerFischer::REFC = [ 0, 1, 2 ]; | 
| 34 |  |  |  |  |  |  |  | 
| 35 |  |  |  |  |  |  | my @b64 = split '', | 
| 36 |  |  |  |  |  |  | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; | 
| 37 |  |  |  |  |  |  | my @DEBUG_LAST; | 
| 38 |  |  |  |  |  |  |  | 
| 39 |  |  |  |  |  |  | my @last7chars;    # will use character 7 places before | 
| 40 |  |  |  |  |  |  | {                  # begin rolling hash internals | 
| 41 |  |  |  |  |  |  |  | 
| 42 |  |  |  |  |  |  | my $roll_h1;    # rolling hash internal | 
| 43 |  |  |  |  |  |  | my $roll_h2;    # rolling hash internal | 
| 44 |  |  |  |  |  |  | my $roll_h3;    # rolling hash internal | 
| 45 |  |  |  |  |  |  |  | 
| 46 |  |  |  |  |  |  | # Resets the roll hash internal status | 
| 47 |  |  |  |  |  |  | sub _reset_rollhash { | 
| 48 | 37 |  |  | 37 |  | 173 | @last7chars = | 
| 49 |  |  |  |  |  |  | ( 0, 0, 0, 0, 0, 0, 0 );    # will use character 7 places before | 
| 50 | 37 |  |  |  |  | 46 | $roll_h1 = 0; | 
| 51 | 37 |  |  |  |  | 61 | $roll_h2 = 0; | 
| 52 | 37 |  |  |  |  | 59 | $roll_h3 = 0; | 
| 53 |  |  |  |  |  |  | } | 
| 54 |  |  |  |  |  |  |  | 
| 55 |  |  |  |  |  |  | # Updates rolling_hash's internal state and return the rolling_hash value. | 
| 56 |  |  |  |  |  |  | # Parameters: the next character. | 
| 57 |  |  |  |  |  |  | # Returns: the actual rolling hash value | 
| 58 |  |  |  |  |  |  | sub _roll_hash { | 
| 59 | 522683 |  |  | 522683 |  | 532979 | my $char    = shift; | 
| 60 | 522683 |  |  |  |  | 572572 | my $char7bf = shift @last7chars; | 
| 61 |  |  |  |  |  |  |  | 
| 62 | 522683 |  |  |  |  | 612422 | push @last7chars, $char; | 
| 63 |  |  |  |  |  |  |  | 
| 64 | 522683 |  |  |  |  | 604934 | $roll_h2 += 7 * $char - $roll_h1; | 
| 65 | 522683 |  |  |  |  | 541506 | $roll_h1 += $char - $char7bf; | 
| 66 |  |  |  |  |  |  |  | 
| 67 | 522683 |  |  |  |  | 491504 | $roll_h3 <<= 5;    # 5*7 = 35 (so it vanish after 7 iterations) | 
| 68 | 522683 |  |  |  |  | 503172 | $roll_h3 &= 0xffffffff; | 
| 69 | 522683 |  |  |  |  | 507575 | $roll_h3 ^= $char; | 
| 70 |  |  |  |  |  |  |  | 
| 71 |  |  |  |  |  |  | #printf("c=%d  cAnt=%d    H1=%u  H2=%u  H3=%u\n", | 
| 72 |  |  |  |  |  |  | #	$char, $char7bf, | 
| 73 |  |  |  |  |  |  | #	$roll_h1, $roll_h2, $roll_h3); | 
| 74 |  |  |  |  |  |  |  | 
| 75 | 522683 |  |  |  |  | 773369 | return $roll_h1 + $roll_h2 + $roll_h3; | 
| 76 |  |  |  |  |  |  | } | 
| 77 |  |  |  |  |  |  |  | 
| 78 |  |  |  |  |  |  | }    # end rolling hash internals | 
| 79 |  |  |  |  |  |  |  | 
| 80 |  |  |  |  |  |  | # In-place updates the FNV hash using the new character | 
| 81 |  |  |  |  |  |  | # _update_fnv($fnvhash, $newchar); | 
| 82 |  |  |  |  |  |  | sub _update_fnv { | 
| 83 | 3 |  |  | 3 |  | 3098 | use integer;    # we need integer overflow in multiplication | 
|  | 3 |  |  |  |  | 31 |  | 
|  | 3 |  |  |  |  | 16 |  | 
| 84 | 1045366 |  |  | 1045366 |  | 1002002 | $_[0] *= FNV_PRIME; | 
| 85 | 1045366 |  |  |  |  | 1005640 | $_[0] &= 0xffffffff; | 
| 86 | 1045366 |  |  |  |  | 1106062 | $_[0] ^= $_[1]; | 
| 87 | 3 |  |  | 3 |  | 184 | no integer; | 
|  | 3 |  |  |  |  | 5 |  | 
|  | 3 |  |  |  |  | 11 |  | 
| 88 |  |  |  |  |  |  | } | 
| 89 |  |  |  |  |  |  |  | 
| 90 |  |  |  |  |  |  | # Calculates initial blocksize | 
| 91 |  |  |  |  |  |  | # Parameter: the length of the whole data | 
| 92 |  |  |  |  |  |  | sub _calc_initbs { | 
| 93 | 32 |  |  | 32 |  | 53 | my $length = shift; | 
| 94 |  |  |  |  |  |  |  | 
| 95 |  |  |  |  |  |  | # MAX_LENGTH * bs < length | 
| 96 |  |  |  |  |  |  | # MAX_LENGTH * 3 * 2 * 2 * 2 * ... < length | 
| 97 |  |  |  |  |  |  | #my $n = int(log($length / (MAX_LENGTH * 3)) / log(2)); | 
| 98 |  |  |  |  |  |  | #my $bs = 3 * 2**$n; | 
| 99 | 32 |  |  |  |  | 49 | my $bs = 3; | 
| 100 | 32 |  |  |  |  | 223 | $bs *= 2 while ( $bs * MAX_LENGTH < $length ); | 
| 101 |  |  |  |  |  |  |  | 
| 102 | 32 | 100 |  |  |  | 115 | return $bs > 3 ? $bs : 3; | 
| 103 |  |  |  |  |  |  | } | 
| 104 |  |  |  |  |  |  |  | 
| 105 |  |  |  |  |  |  | # Calculates the ssdeep fuzzy hash of a string | 
| 106 |  |  |  |  |  |  | # Parameters: the string | 
| 107 |  |  |  |  |  |  | # Returns: the fuzzy hash in string or array | 
| 108 |  |  |  |  |  |  | sub ssdeep_hash { | 
| 109 | 32 |  |  | 32 | 1 | 370637 | my $string = shift; | 
| 110 |  |  |  |  |  |  |  | 
| 111 | 32 | 50 |  |  |  | 127 | return unless defined $string; | 
| 112 |  |  |  |  |  |  |  | 
| 113 | 32 |  |  |  |  | 271 | my $bs = _calc_initbs( length $string ); | 
| 114 | 32 |  |  |  |  | 1459 | @DEBUG_LAST = (); | 
| 115 |  |  |  |  |  |  |  | 
| 116 | 32 |  |  |  |  | 43 | my $hash1; | 
| 117 |  |  |  |  |  |  | my $hash2; | 
| 118 |  |  |  |  |  |  |  | 
| 119 | 32 |  |  |  |  | 208 | while (1) { | 
| 120 | 37 |  |  |  |  | 96 | _reset_rollhash(); | 
| 121 | 37 |  |  |  |  | 44 | my $fnv1 = FNV_INIT;    # traditional hash blocksize | 
| 122 | 37 |  |  |  |  | 42 | my $fnv2 = FNV_INIT;    # traditional hash 2*blocksize | 
| 123 |  |  |  |  |  |  |  | 
| 124 | 37 |  |  |  |  | 262 | $hash1 = ''; | 
| 125 | 37 |  |  |  |  | 44 | $hash2 = ''; | 
| 126 |  |  |  |  |  |  |  | 
| 127 | 37 |  |  |  |  | 110 | for my $i ( 0 .. length($string) - 1 ) { | 
| 128 | 522683 |  |  |  |  | 648079 | my $c = ord( substr( $string, $i, 1 ) ); | 
| 129 |  |  |  |  |  |  |  | 
| 130 |  |  |  |  |  |  | #printf("c: %u, H1=%x\tH2=%x\n", $c, $fnv1, $fnv2); | 
| 131 |  |  |  |  |  |  |  | 
| 132 | 522683 |  |  |  |  | 721671 | my $h = _roll_hash($c); | 
| 133 | 522683 |  |  |  |  | 795140 | _update_fnv( $fnv1, $c );    # blocksize FNV hash | 
| 134 | 522683 |  |  |  |  | 743176 | _update_fnv( $fnv2, $c );    # 2* blocksize FNV hash | 
| 135 |  |  |  |  |  |  |  | 
| 136 | 522683 | 100 | 100 |  |  | 1262209 | if ( $h % $bs == ( $bs - 1 ) and length $hash1 < MAX_LENGTH - 1 ) { | 
| 137 |  |  |  |  |  |  |  | 
| 138 |  |  |  |  |  |  | #printf "Hash $h Trigger 1 at $i\n"; | 
| 139 | 1296 |  |  |  |  | 2670 | my $b64char = $b64[ $fnv1 & 63 ]; | 
| 140 | 1296 |  |  |  |  | 1678 | $hash1 .= $b64char; | 
| 141 |  |  |  |  |  |  |  | 
| 142 | 1296 |  |  |  |  | 9280 | push @DEBUG_LAST, | 
| 143 |  |  |  |  |  |  | [ 1, $i + 1, join( '|', @last7chars ), $fnv1, $b64char ]; | 
| 144 |  |  |  |  |  |  |  | 
| 145 | 1296 |  |  |  |  | 2342 | $fnv1 = FNV_INIT; | 
| 146 |  |  |  |  |  |  |  | 
| 147 |  |  |  |  |  |  | } | 
| 148 |  |  |  |  |  |  |  | 
| 149 | 522683 | 100 | 100 |  |  | 1431342 | if ( $h % ( 2 * $bs ) == ( 2 * $bs - 1 ) | 
| 150 |  |  |  |  |  |  | and length $hash2 < MAX_LENGTH / 2 - 1 ) | 
| 151 |  |  |  |  |  |  | { | 
| 152 |  |  |  |  |  |  |  | 
| 153 |  |  |  |  |  |  | #printf "Hash $h Trigger 2 at $i\n"; | 
| 154 | 608 |  |  |  |  | 884 | my $b64char = $b64[ $fnv2 & 63 ]; | 
| 155 | 608 |  |  |  |  | 757 | $hash2 .= $b64char; | 
| 156 |  |  |  |  |  |  |  | 
| 157 | 608 |  |  |  |  | 3161 | push @DEBUG_LAST, | 
| 158 |  |  |  |  |  |  | [ 2, $i + 1, join( '|', @last7chars ), $fnv2, $b64char ]; | 
| 159 |  |  |  |  |  |  |  | 
| 160 | 608 |  |  |  |  | 1102 | $fnv2 = FNV_INIT; | 
| 161 |  |  |  |  |  |  | } | 
| 162 |  |  |  |  |  |  |  | 
| 163 |  |  |  |  |  |  | } | 
| 164 |  |  |  |  |  |  |  | 
| 165 | 37 |  |  |  |  | 109 | $hash1 .= $b64[ $fnv1 & 63 ]; | 
| 166 | 37 |  |  |  |  | 61 | $hash2 .= $b64[ $fnv2 & 63 ]; | 
| 167 |  |  |  |  |  |  |  | 
| 168 | 37 |  |  |  |  | 272 | push @DEBUG_LAST, | 
| 169 |  |  |  |  |  |  | [ | 
| 170 |  |  |  |  |  |  | 1, length($string), | 
| 171 |  |  |  |  |  |  | join( '|', @last7chars ), $fnv1, | 
| 172 |  |  |  |  |  |  | $b64[ $fnv1 & 63 ] | 
| 173 |  |  |  |  |  |  | ]; | 
| 174 |  |  |  |  |  |  |  | 
| 175 | 37 |  |  |  |  | 203 | push @DEBUG_LAST, | 
| 176 |  |  |  |  |  |  | [ | 
| 177 |  |  |  |  |  |  | 2, length($string), | 
| 178 |  |  |  |  |  |  | join( '|', @last7chars ), $fnv2, | 
| 179 |  |  |  |  |  |  | $b64[ $fnv2 & 63 ] | 
| 180 |  |  |  |  |  |  | ]; | 
| 181 |  |  |  |  |  |  |  | 
| 182 | 37 | 100 | 100 |  |  | 224 | last if $bs <= 3 or length $hash1 >= MAX_LENGTH / 2; | 
| 183 |  |  |  |  |  |  |  | 
| 184 | 5 |  |  |  |  | 15 | $bs = int( $bs / 2 ); # repeat with half blocksize if no enough triggers | 
| 185 | 5 | 100 |  |  |  | 17 | $bs > 3 or $bs = 3; | 
| 186 |  |  |  |  |  |  | } | 
| 187 |  |  |  |  |  |  |  | 
| 188 | 32 |  |  |  |  | 88 | my @outarray = ( $bs, $hash1, $hash2 ); | 
| 189 | 32 | 100 |  |  |  | 427 | return wantarray ? @outarray : join ':', @outarray; | 
| 190 |  |  |  |  |  |  | } | 
| 191 |  |  |  |  |  |  |  | 
| 192 |  |  |  |  |  |  | # Convenient function. Slurps file. You should not use it for long files. | 
| 193 |  |  |  |  |  |  | # You should not use pure perl implementation for long files anyway. | 
| 194 |  |  |  |  |  |  | # Parameter: filename | 
| 195 |  |  |  |  |  |  | # Returns: ssdeep hash in string or array format | 
| 196 |  |  |  |  |  |  | sub ssdeep_hash_file { | 
| 197 | 2 |  |  | 2 | 1 | 9963 | my $file = shift; | 
| 198 |  |  |  |  |  |  |  | 
| 199 |  |  |  |  |  |  | # Slurp the file (we can also use File::Slurp | 
| 200 | 2 |  |  |  |  | 14 | local ($/); | 
| 201 | 2 | 50 |  |  |  | 152 | open( my $fh, '<', $file ) or return; | 
| 202 | 2 |  |  |  |  | 348 | my $string = <$fh>; | 
| 203 | 2 |  |  |  |  | 36 | close $fh; | 
| 204 |  |  |  |  |  |  |  | 
| 205 | 2 |  |  |  |  | 8 | return ssdeep_hash($string); | 
| 206 |  |  |  |  |  |  | } | 
| 207 |  |  |  |  |  |  |  | 
| 208 |  |  |  |  |  |  | # Determines the longest common substring | 
| 209 |  |  |  |  |  |  | sub _lcss { | 
| 210 | 25 |  |  | 25 |  | 69 | my $strings = join "\0", @_; | 
| 211 | 25 |  |  |  |  | 38 | my $lcs = ''; | 
| 212 |  |  |  |  |  |  |  | 
| 213 | 25 |  |  |  |  | 61 | for my $n ( 1 .. length $strings ) { | 
| 214 | 506 |  |  |  |  | 934 | my $re = "(.{$n})" . '.*\0.*\1' x ( @_ - 1 ); | 
| 215 | 506 | 100 |  |  |  | 11378 | last unless $strings =~ $re; | 
| 216 | 481 |  |  |  |  | 975 | $lcs = $1; | 
| 217 |  |  |  |  |  |  | } | 
| 218 |  |  |  |  |  |  |  | 
| 219 | 25 |  |  |  |  | 87 | return $lcs; | 
| 220 |  |  |  |  |  |  | } | 
| 221 |  |  |  |  |  |  |  | 
| 222 |  |  |  |  |  |  | # Calculates how similar two strings are using the Wagner-Fischer package. | 
| 223 |  |  |  |  |  |  | # Parameters: min_lcs, string A, string B | 
| 224 |  |  |  |  |  |  | # Returns: the likeliness being 0 totally dissimilar and 100 same string | 
| 225 |  |  |  |  |  |  | # Returns 0 also if the longest common substring is shorter than min_lcs | 
| 226 |  |  |  |  |  |  | sub _likeliness { | 
| 227 | 25 |  |  | 25 |  | 56 | my ( $min_lcs, $a, $b ) = @_; | 
| 228 |  |  |  |  |  |  |  | 
| 229 | 25 | 100 |  |  |  | 55 | return 0 unless length( _lcss( $a, $b ) ) >= $min_lcs; | 
| 230 |  |  |  |  |  |  |  | 
| 231 | 22 |  |  |  |  | 86 | my $dist = distance( $a, $b ); | 
| 232 |  |  |  |  |  |  |  | 
| 233 |  |  |  |  |  |  | #$DB::single = 2; | 
| 234 |  |  |  |  |  |  |  | 
| 235 |  |  |  |  |  |  | # Must follow ssdeep original's code for compatibility | 
| 236 |  |  |  |  |  |  | #  $dist = 100 * $dist / (length($a) + length($b)); | 
| 237 | 22 |  |  |  |  | 540557 | $dist = int( $dist * MAX_LENGTH / ( length($a) + length($b) ) ); | 
| 238 | 22 |  |  |  |  | 48 | $dist = int( 100 * $dist / 64 ); | 
| 239 |  |  |  |  |  |  |  | 
| 240 | 22 | 50 |  |  |  | 73 | $dist > 100 and $dist = 100; | 
| 241 | 22 |  |  |  |  | 59 | return 100 - $dist; | 
| 242 |  |  |  |  |  |  | } | 
| 243 |  |  |  |  |  |  |  | 
| 244 |  |  |  |  |  |  | # We accept hash in both array and scalar format | 
| 245 |  |  |  |  |  |  | # Parameters: $hashA, $hashB, [$min_lcs] | 
| 246 |  |  |  |  |  |  | # Parameters: \@hashA, \@hashB, [$min_lcs] | 
| 247 |  |  |  |  |  |  | # Returns: file matching in % | 
| 248 |  |  |  |  |  |  | sub ssdeep_compare { | 
| 249 | 19 |  |  | 19 | 1 | 11687 | my @hashA;    # hash = bs:hash1:hash2 | 
| 250 |  |  |  |  |  |  | my @hashB;    # hash = bs:hash1:hash2 | 
| 251 | 19 | 100 |  |  |  | 117 | @hashA = ref( $_[0] ) eq 'ARRAY' ? @{ $_[0] } : split ':', $_[0]; | 
|  | 1 |  |  |  |  | 2 |  | 
| 252 | 19 | 100 |  |  |  | 99 | @hashB = ref( $_[1] ) eq 'ARRAY' ? @{ $_[1] } : split ':', $_[1]; | 
|  | 1 |  |  |  |  | 3 |  | 
| 253 | 19 |  | 50 |  |  | 113 | my $min_lcs = $_[2] || 7; | 
| 254 |  |  |  |  |  |  |  | 
| 255 | 19 | 50 | 33 |  |  | 159 | if ( @hashA != 3 or $hashA[0] !~ /\d+/ ) { | 
| 256 | 0 |  |  |  |  | 0 | carp "Argument 1 is not a ssdeep hash."; | 
| 257 | 0 |  |  |  |  | 0 | return; | 
| 258 |  |  |  |  |  |  | } | 
| 259 |  |  |  |  |  |  |  | 
| 260 | 19 | 50 | 33 |  |  | 127 | if ( @hashB != 3 or $hashB[0] !~ /\d+/ ) { | 
| 261 | 0 |  |  |  |  | 0 | carp "Argument 2 is not a ssdeep hash."; | 
| 262 | 0 |  |  |  |  | 0 | return; | 
| 263 |  |  |  |  |  |  | } | 
| 264 |  |  |  |  |  |  |  | 
| 265 |  |  |  |  |  |  | # Remove sequences of more than three repeated character | 
| 266 | 19 |  |  |  |  | 393 | s/(.)\1{3,}/$1/gi for @hashA; | 
| 267 | 19 |  |  |  |  | 366 | s/(.)\1{3,}/$1/gi for @hashB; | 
| 268 |  |  |  |  |  |  |  | 
| 269 |  |  |  |  |  |  | # Remove trailing newlines | 
| 270 | 19 |  |  |  |  | 84 | s/\s+$//gi for @hashA; | 
| 271 | 19 |  |  |  |  | 130 | s/\s+$//gi for @hashB; | 
| 272 |  |  |  |  |  |  |  | 
| 273 |  |  |  |  |  |  | #$DB::single = 2; | 
| 274 |  |  |  |  |  |  |  | 
| 275 | 19 |  |  |  |  | 34 | my $like; | 
| 276 |  |  |  |  |  |  |  | 
| 277 |  |  |  |  |  |  | # Blocksize comparison | 
| 278 |  |  |  |  |  |  | # bsA:hash_bsA:hash_2*bsA | 
| 279 |  |  |  |  |  |  | # bsB:hash_bsB:hash_2*bsB | 
| 280 | 19 | 100 |  |  |  | 6631 | if ( $hashA[0] == $hashB[0] ) { | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
| 281 |  |  |  |  |  |  |  | 
| 282 |  |  |  |  |  |  | # Compare both hashes | 
| 283 | 7 |  |  |  |  | 21 | my $like1 = _likeliness( $min_lcs, $hashA[1], $hashB[1] ); | 
| 284 | 7 |  |  |  |  | 33 | my $like2 = _likeliness( $min_lcs, $hashA[2], $hashB[2] ); | 
| 285 | 7 |  |  |  |  | 47 | $like = max( $like1, $like2 ); | 
| 286 |  |  |  |  |  |  | } | 
| 287 |  |  |  |  |  |  | elsif ( $hashA[0] == 2 * $hashB[0] ) { | 
| 288 |  |  |  |  |  |  |  | 
| 289 |  |  |  |  |  |  | # Compare hash_bsA with hash_2*bsB | 
| 290 | 6 |  |  |  |  | 20 | $like = _likeliness( $min_lcs, $hashA[1], $hashB[2] ); | 
| 291 |  |  |  |  |  |  | } | 
| 292 |  |  |  |  |  |  | elsif ( 2 * $hashA[0] == $hashB[0] ) { | 
| 293 |  |  |  |  |  |  |  | 
| 294 |  |  |  |  |  |  | # Compare hash_2*bsA with hash_bsB | 
| 295 | 5 |  |  |  |  | 18 | $like = _likeliness( $min_lcs, $hashA[2], $hashB[1] ); | 
| 296 |  |  |  |  |  |  | } | 
| 297 |  |  |  |  |  |  | else { | 
| 298 |  |  |  |  |  |  |  | 
| 299 |  |  |  |  |  |  | # Nothing suitable to compare, sorry | 
| 300 | 1 |  |  |  |  | 9 | return 0; | 
| 301 |  |  |  |  |  |  | } | 
| 302 |  |  |  |  |  |  |  | 
| 303 | 18 |  |  |  |  | 131 | return $like; | 
| 304 |  |  |  |  |  |  | } | 
| 305 |  |  |  |  |  |  |  | 
| 306 |  |  |  |  |  |  | # Dump internals information. See help. | 
| 307 |  |  |  |  |  |  | sub ssdeep_dump_last { | 
| 308 | 0 |  |  | 0 | 1 |  | my @result; | 
| 309 | 0 |  |  |  |  |  | for (@DEBUG_LAST) { | 
| 310 | 0 |  |  |  |  |  | push @result, join ",", @{$_}; | 
|  | 0 |  |  |  |  |  |  | 
| 311 |  |  |  |  |  |  | } | 
| 312 | 0 |  |  |  |  |  | return @result; | 
| 313 |  |  |  |  |  |  | } | 
| 314 |  |  |  |  |  |  |  | 
| 315 |  |  |  |  |  |  | 1;    # Magic true value required at end of module | 
| 316 |  |  |  |  |  |  | __END__ |