File Coverage

blib/lib/Redis/RdbParser.pm
Criterion Covered Total %
statement 407 567 71.7
branch 124 206 60.1
condition 2 11 18.1
subroutine 59 90 65.5
pod 0 64 0.0
total 592 938 63.1


line stmt bran cond sub pod time code
1             package Redis::RdbParser;
2              
3 14     14   547039 use 5.008008;
  14         134  
  14         666  
4 14     14   94 use strict;
  14         31  
  14         484  
5 14     14   87 use warnings;
  14         32  
  14         1144  
6              
7             require Exporter;
8              
9             our @ISA = qw(Exporter);
10              
11             our $VERSION = '0.05';
12              
13 14     14   79 use Carp;
  14         25  
  14         1160  
14              
15 14     14   86 use constant REDIS_RDB_6BITLEN => 0;
  14         37  
  14         1315  
16 14     14   77 use constant REDIS_RDB_14BITLEN => 1;
  14         29  
  14         793  
17 14     14   79 use constant REDIS_RDB_32BITLEN => 2;
  14         23  
  14         635  
18 14     14   84 use constant REDIS_RDB_ENCVAL => 3;
  14         27  
  14         761  
19 14     14   73 use constant REDIS_RDB_OPCODE_EXPIRETIME_MS => 252;
  14         27  
  14         714  
20 14     14   207 use constant REDIS_RDB_OPCODE_EXPIRETIME => 253;
  14         23  
  14         918  
21 14     14   86 use constant REDIS_RDB_OPCODE_SELECTDB => 254;
  14         129  
  14         754  
22 14     14   151 use constant REDIS_RDB_OPCODE_EOF => 255;
  14         27  
  14         639  
23 14     14   71 use constant REDIS_RDB_TYPE_STRING => 0;
  14         24  
  14         554  
24 14     14   74 use constant REDIS_RDB_TYPE_LIST => 1;
  14         28  
  14         677  
25 14     14   84 use constant REDIS_RDB_TYPE_SET => 2;
  14         33  
  14         683  
26 14     14   78 use constant REDIS_RDB_TYPE_ZSET => 3;
  14         30  
  14         595  
27 14     14   87 use constant REDIS_RDB_TYPE_HASH => 4;
  14         25  
  14         621  
28 14     14   71 use constant REDIS_RDB_TYPE_HASH_ZIPMAP => 9;
  14         28  
  14         671  
29 14     14   84 use constant REDIS_RDB_TYPE_LIST_ZIPLIST => 10;
  14         24  
  14         716  
30 14     14   71 use constant REDIS_RDB_TYPE_SET_INTSET => 11;
  14         24  
  14         643  
31 14     14   72 use constant REDIS_RDB_TYPE_ZSET_ZIPLIST => 12;
  14         27  
  14         585  
32 14     14   789 use constant REDIS_RDB_TYPE_HASH_ZIPLIST => 13;
  14         29  
  14         723  
33 14     14   75 use constant REDIS_RDB_ENC_INT8 => 0;
  14         23  
  14         557  
34 14     14   67 use constant REDIS_RDB_ENC_INT16 => 1;
  14         31  
  14         622  
35 14     14   150 use constant REDIS_RDB_ENC_INT32 => 2;
  14         25  
  14         699  
36 14     14   83 use constant REDIS_RDB_ENC_LZF => 3;
  14         37  
  14         120967  
37              
38             my %DATA_TYPE_MAPPING = (
39             0 => 'string',
40             1 => 'list',
41             2 => 'set',
42             3 => 'sortedset',
43             4 => 'hash',
44             9 => 'hash',
45             10 => 'list',
46             11 => 'set',
47             12 => 'sortedset',
48             13 => 'hash');
49              
50             my %def_callbacks = (
51             "start_rdb" => \&def_start_rdb,
52             "start_database" => \&def_start_database,
53             "key" => \&def_key,
54             "set" => \&def_set,
55             "start_hash" => \&def_start_hash,
56             "hset" => \&def_hset,
57             "end_hash" => \&def_end_hash,
58             "start_set" => \&def_start_set,
59             "sadd" => \&def_sadd,
60             "end_set" => \&def_end_set,
61             "start_list" => \&def_start_list,
62             "rpush" => \&def_rpush,
63             "end_list" => \&def_end_list,
64             "start_sorted_set" => \&def_start_sorted_set,
65             "zadd" => \&def_zadd,
66             "end_sorted_set" => \&def_end_sorted_set,
67             "end_database" => \&def_end_database,
68             "end_rdb" => \&def_end_rdb,
69             );
70              
71             sub new {
72 14     14 0 556 my ($class, $callbacks) = @_;
73 14   50     58 $callbacks ||= \%def_callbacks;
74              
75 14         49 my $self = bless {}, $class;
76 14         123 $self->{callback} = $callbacks;
77 14         36 $self->{expiry} = undef;
78 14         33 $self->{key} = undef;
79 14         37 $self->{filename} = undef;
80 14         46 $self;
81             }
82              
83             #=================================================================
84             # Parse a redis rdb dump file, and call methods in the callback
85             # hash reference during the parsing operation.
86             #
87             # filter's structure is a hash, whose member value is of array type,
88             # the key is processed as regular expression.
89             # i.e.
90             # filter = {
91             # "dbs" => [0, 1],
92             # "keys" => ['^foo$', 'bar'],
93             # "types" => ["hash", "set", "sortedset", "list", "string"],
94             # }
95             #
96             # If filter is undef, results will not be filtered.
97             # If dbs, keys or type is undef, no filtering will be done on the
98             # result.
99             # You can also use appropriate callback to filter keys.
100             # All the 3 conditions must be satified.
101             #=================================================================
102             sub parse {
103 18     18 0 16965 my $self = shift;
104 18         39 my $filename = shift;
105 18         31 my $filter = shift;
106              
107 18 50       69 unless (defined($filename)) {
108 0         0 croak "Expected a Redis dump file name";
109             }
110 18         46 $self->{filename} = $filename;
111              
112 18         30 my $buffer;
113 18 50       1217 open my $INFH, $filename or
114             croak "Open $filename for reading failed: $!";
115 18         69 binmode $INFH;
116              
117 18 50       566 read($INFH, $buffer, 5) or croak "Read $filename failed: $!";
118 18         95 $self->verify_magic($buffer);
119              
120 18 50       68 read($INFH, $buffer, 4) or croak "Read $filename failed: $!";
121 18         81 $self->verify_version($buffer);
122              
123 18         82 $self->invoke_callback("start_rdb", $filename);
124              
125 18         90 my $db_number = 0;
126 18         38 my $is_first_database = 1;
127              
128 18         29 while (1) {
129 153         358 $self->{expiry} = undef;
130 153         293 my $data_type = &read_unsigned_char($INFH);
131              
132 153 100       455 if ($data_type == REDIS_RDB_OPCODE_EXPIRETIME_MS) {
    50          
133 1         5 $self->{expiry} = &read_unsigned_long($INFH);
134 1         3 $data_type = &read_unsigned_char($INFH);
135             } elsif ($data_type == REDIS_RDB_OPCODE_EXPIRETIME) {
136 0         0 $self->{expiry} = &read_unsigned_int($INFH) * 1000; # change to ms
137 0         0 $data_type = &read_unsigned_char($INFH);
138             }
139              
140 153 100       301 if ($data_type == REDIS_RDB_OPCODE_SELECTDB) {
141 18 100       61 unless ($is_first_database) {
142 1         3 $self->invoke_callback("end_database", $db_number);
143             }
144 18         37 $is_first_database = 0;
145 18         78 $db_number = &read_length($INFH);
146              
147 18         69 $self->invoke_callback("start_database", $db_number);
148 18         89 next;
149             }
150              
151 135 100       279 if ($data_type == REDIS_RDB_OPCODE_EOF) {
152 18 100       83 unless ($is_first_database) {
153 17         67 $self->invoke_callback("end_database", $db_number);
154             }
155 18         113 $self->invoke_callback("end_rdb", $filename);
156 18         84 last;
157             }
158              
159 117 100       196 if (defined($filter)) {
160 105 50       181 if (&match_db($filter, $db_number)) {
161 105         306 my $key = &read_string($INFH);
162 105         189 $self->{key} = $key;
163 105         219 $self->invoke_callback("key", $key);
164 105 100       468 if (match_filter($filter, $key, $data_type)) {
165 8         32 $self->read_object($INFH, $data_type);
166             } else {
167 97         169 &skip_object($INFH, $data_type);
168             }
169             } else {
170 0         0 &skip_key_and_object($INFH, $data_type);
171             }
172             } else {
173 12         40 my $key = &read_string($INFH);
174 12         69 $self->{key} = $key;
175 12         32 $self->invoke_callback("key", $key);
176 12         82 $self->read_object($INFH, $data_type);
177             }
178             }
179            
180 18 50       467 close $INFH or croak "Close $filename failed: $!";
181             }
182              
183             sub match_db {
184 105     105 0 124 my ($filter, $db_number) = @_;
185 105 100       272 return 1 unless (defined($filter->{dbs}));
186 87 50       93 if (&in_array($db_number, @{$filter->{dbs}})) {
  87         177  
187 87         185 return 1;
188             }
189 0         0 return 0;
190             }
191              
192             sub match_filter {
193 105     105 0 149 my ($filter, $key, $data_type) = @_;
194 105         118 my $ret1 = 0;
195 105         140 my $ret2 = 0;
196            
197 105 100       215 if (defined($filter->{"keys"})) {
198 104         96 foreach (@{$filter->{"keys"}}) {
  104         214  
199 104 100       498 if ($key =~ m/$_/) {
200 7         20 $ret1 = 1;
201 7         11 last;
202             }
203             }
204             } else {
205 1         2 $ret1 = 1;
206             }
207              
208 105 100       307 if (defined($filter->{"types"})) {
209 87         89 foreach (@{$filter->{"types"}}) {
  87         161  
210 87 100       271 if ($DATA_TYPE_MAPPING{$data_type} eq $_) {
211 25         32 $ret2 = 1;
212 25         35 last;
213             }
214             }
215             } else {
216 18         22 $ret2 = 1;
217             }
218              
219 105         312 return $ret1 & $ret2;
220             }
221              
222             sub in_array {
223 87     87 0 142 my ($needle, @haystack) = @_;
224 87         129 foreach (@haystack) {
225 87 50       157 if ($needle == $_) {
226 87         273 return 1;
227             }
228             }
229 0         0 return 0;
230             }
231              
232             sub invoke_callback {
233 2748     2748 0 3674 my $self = shift;
234 2748         3139 my $method = shift;
235 2748         5157 my @args = @_;
236              
237 2748 50       7078 if (defined($self->{callback}->{$method})) {
238 2748         4348 my $func = $self->{callback}->{$method};
239 2748         6725 &$func(@args);
240             }
241             }
242              
243             sub verify_magic {
244 18     18 0 54 my ($self, $magic) = @_;
245              
246 18 50       83 if ($magic ne 'REDIS') {
247 0         0 croak "Invalid File Format for file " . $self->{filename};
248             }
249             }
250              
251             sub verify_version {
252 18     18 0 38 my ($self, $version) = @_;
253              
254 18         67 $version = int($version);
255              
256 18 50 33     154 if ($version < 1 or $version > 6) {
257 0         0 croak "Invalid RDB version $version for file " . $self->{filename};
258             }
259             }
260              
261             sub read_signed_char {
262 4     4 0 5 my ($fh) = @_;
263 4         6 my $buffer;
264              
265 4 50       48 read($fh, $buffer, 1) or croak "read failed: $!";
266 4         11 return unpack('c', $buffer);
267             }
268              
269             sub read_signed_char_str {
270 0     0 0 0 my ($str, $off) = @_;
271 0         0 my $temp = substr($str, $off, 1);
272 0         0 return (unpack('c', $temp), $off + 1);
273             }
274              
275             sub read_unsigned_char {
276 4494     4494 0 4835 my ($fh) = @_;
277 4494         4764 my $buffer;
278 4494 50       10780 read($fh, $buffer, 1) or croak "read failed: $!";
279 4494         9226 return unpack('C', $buffer);
280             }
281              
282             sub read_unsigned_char_str {
283 42     42 0 55 my ($str, $off) = @_;
284 42         57 my $temp = substr($str, $off, 1);
285 42         109 return (unpack('C', $temp), $off + 1);
286             }
287              
288             sub read_signed_short {
289 2     2 0 12 my ($fh) = @_;
290 2         4 my $buffer;
291 2 50       7 read($fh, $buffer, 2) or croak "read failed: $!";
292 2         6 return unpack('s', $buffer);
293             }
294              
295             sub read_signed_short_str {
296 1     1 0 2 my ($str, $off) = @_;
297 1         2 my $temp = substr($str, $off, 2);
298 1         4 return (unpack('s', $temp), $off + 2);
299             }
300              
301             sub read_unsigned_short {
302 0     0 0 0 my ($fh) = @_;
303 0         0 my $buffer;
304 0 0       0 read($fh, $buffer, 2) or croak "read failed: $!";
305 0         0 return unpack('S', $buffer);
306             }
307              
308             sub read_unsigned_short_str {
309 7     7 0 14 my ($str, $off) = @_;
310 7         17 my $temp = substr($str, $off, 2);
311 7         27 return (unpack('S', $temp), $off + 2);
312             }
313              
314             sub read_signed_int {
315 6     6 0 8 my ($fh) = @_;
316 6         7 my $buffer;
317 6 50       14 read($fh, $buffer, 4) or croak "read failed: $!";
318 6         15 return unpack('i', $buffer);
319             }
320              
321             sub read_signed_int_str {
322 4     4 0 6 my ($str, $off) = @_;
323 4         8 my $temp = substr($str, $off, 4);
324 4         12 return (unpack('i', $temp), $off + 4);
325             }
326              
327             sub read_unsigned_int {
328 0     0 0 0 my ($fh) = @_;
329 0         0 my $buffer;
330 0 0       0 read($fh, $buffer, 4) or croak "read failed: $!";
331 0         0 return unpack('I', $buffer);
332             }
333              
334             sub read_unsigned_int_str {
335 17     17 0 29 my ($str, $off) = @_;
336 17         44 my $temp = substr($str, $off, 4);
337 17         55 return (unpack('i', $temp), $off + 4);
338             }
339              
340             sub read_big_endian_unsigned_int {
341 0     0 0 0 my ($fh) = @_;
342 0         0 my $buffer;
343 0 0       0 read($fh, $buffer, 4) or croak "read failed: $!";
344 0         0 return unpack('N', $buffer);
345             }
346              
347             sub read_big_endian_unsigned_int_str {
348 0     0 0 0 my ($str, $off) = @_;
349 0         0 my $temp = substr($str, $off, 4);
350 0         0 return (unpack('N', $temp), $off + 4);
351             }
352              
353             sub read_24bit_signed_number {
354 0     0 0 0 my ($fh) = @_;
355 0         0 my $buffer;
356 0 0       0 read($fh, $buffer, 3) or croak "read failed: $!";
357 0         0 $buffer .= '0' . $buffer;
358 0         0 return unpack('i', $buffer) >> 8;
359             }
360              
361             sub read_24bit_signed_number_str {
362 0     0 0 0 my ($str, $off) = @_;
363 0         0 my $temp = substr($str, $off, 3);
364 0         0 $temp .= '0' . $temp;
365 0         0 return (unpack('i', $temp) >> 8, $off + 3);
366             }
367              
368             sub read_signed_long {
369 0     0 0 0 my ($fh) = @_;
370 0         0 my $buffer;
371 0 0       0 read($fh, $buffer, 8) or croak "read failed: $!";
372 0         0 return unpack('q', $buffer);
373             }
374              
375             sub read_signed_long_str {
376 3     3 0 5 my ($str, $off) = @_;
377 3         6 my $temp = substr($str, $off, 8);
378 3         10 return (unpack('q', $temp), $off + 8);
379             }
380              
381             sub read_unsigned_long {
382 1     1 0 1 my ($fh) = @_;
383 1         2 my $buffer;
384 1 50       3 read($fh, $buffer, 8) or croak "read failed: $!";
385 1         3 return unpack('Q', $buffer);
386             }
387              
388             sub read_unsigned_long_str {
389 3     3 0 4 my ($str, $off) = @_;
390 3         5 my $temp = substr($str, $off, 8);
391 3         11 return (unpack('Q', $temp), $off + 8);
392             }
393              
394             sub skip {
395 117     117 0 150 my ($fh, $free) = @_;
396 117         146 my $dummy;
397 117 50       223 if ($free > 0) {
398 117         534 read($fh, $dummy, $free);
399             }
400             }
401              
402             sub ntohl {
403 0     0 0 0 my ($fh) = @_;
404 0         0 my $val = &read_unsigned_int($fh);
405 0         0 my $new_val = 0;
406 0         0 $new_val |= (($val & 0x000000ff) << 24);
407 0         0 $new_val |= (($val & 0xff000000) >> 24);
408 0         0 $new_val |= (($val & 0x0000ff00) << 8);
409 0         0 $new_val |= (($val & 0x00ff0000) >> 8);
410 0         0 return $new_val;
411             }
412              
413             sub read_length_with_encoding {
414 3822     3822 0 4249 my ($fh) = @_;
415 3822         4338 my $length = 0;
416 3822         3813 my $is_encoded = 0;
417 3822         4779 my $buffer;
418            
419 3822         12613 my $first = &read_unsigned_char($fh);
420 3822         5136 my $enc_type = ($first & 0xc0) >> 6;
421              
422 3822 100       8338 if ($enc_type == REDIS_RDB_ENCVAL) {
    100          
    50          
423 47         50 $is_encoded = 1;
424 47         68 $length = $first & 0x3f;
425             } elsif ($enc_type == REDIS_RDB_6BITLEN) {
426 3757         4780 $length = $first & 0x3f;
427             } elsif ($enc_type == REDIS_RDB_14BITLEN) {
428 18         34 my $second = &read_unsigned_char($fh);
429 18         40 $length = (($first & 0x3f) << 8) | $second;
430             } else {
431 0         0 $length = ntohl($fh);
432             }
433 3822         8010 return ($length, $is_encoded);
434             }
435              
436             sub read_length {
437 66     66 0 174 return (&read_length_with_encoding(@_))[0];
438             }
439              
440             sub read_string {
441 3639     3639 0 4412 my ($fh) = @_;
442 3639         5498 my ($length, $is_encoded) = &read_length_with_encoding($fh);
443 3639         3924 my $val;
444              
445 3639 100       7573 if ($is_encoded) {
446 16 100       81 if ($length == REDIS_RDB_ENC_INT8) {
    100          
    100          
    50          
447 4         7 $val = &read_signed_char($fh);
448             } elsif ($length == REDIS_RDB_ENC_INT16) {
449 2         5 $val = &read_signed_short($fh);
450             } elsif ($length == REDIS_RDB_ENC_INT32) {
451 6         10 $val = &read_signed_int($fh);
452             } elsif ($length == REDIS_RDB_ENC_LZF) {
453 4         16 my $clen = &read_length($fh);
454 4         11 my $len = &read_length($fh);
455 4         17 my $buffer;
456 4         12 read($fh, $buffer, $clen);
457 4         18 $val = &lzf_decompress($buffer, $len);
458             }
459             } else {
460 3623         6533 read($fh, $val, $length);
461             }
462 3639         8566 return $val;
463             }
464              
465             #==================================================================
466             # Read an object from the stream, and invoke callbacks.
467             #==================================================================
468             sub read_object {
469 20     20 0 38 my ($self, $fh, $enc_type) = @_;
470 20         110 my $val;
471             my $length;
472              
473 20 100       196 if ($enc_type == REDIS_RDB_TYPE_STRING) {
    100          
    100          
    100          
    100          
    50          
    100          
    100          
    100          
    50          
474 9         20 $val = &read_string($fh);
475 9         61 $self->invoke_callback("set", $self->{key}, $val,
476             $self->{expiry}, {'encoding' => 'string'});
477             } elsif ($enc_type == REDIS_RDB_TYPE_LIST) {
478             #=================================================================
479             # A redis list is just a sequence of strings
480             # We successively read strings from the string and create a list
481             # from it.
482             # The lists are in order i.e. the first string is the head,
483             # and the last string is the tail of the list.
484             #=================================================================
485 1         3 $length = &read_length($fh);
486 1         16 $self->invoke_callback("start_list",
487             $self->{key}, $length, $self->{expiry},
488             {'encoding' => 'linkedlist'});
489 1         10 for (my $i = 0; $i < $length; ++$i) {
490 1000         5469 $val = &read_string($fh);
491 1000         2353 $self->invoke_callback("rpush", $self->{key}, $val);
492             }
493 1         8 $self->invoke_callback("end_list", $self->{key});
494             } elsif ($enc_type == REDIS_RDB_TYPE_SET) {
495             #================================================================
496             # A redis set is just a sequence of strings.
497             # We successively read strings from the stream and create a set
498             # from it. Note that the order of strings is non-deterministic.
499             #================================================================
500 1         2 $length = &read_length($fh);
501 1         11 $self->invoke_callback("start_set",
502             $self->{key}, $length, $self->{expiry},
503             {'encoding' => 'hashtable'});
504              
505 1         6 for (my $i = 0; $i < $length; ++$i) {
506 6         173 $val = &read_string($fh);
507 6         17 $self->invoke_callback("sadd", $self->{key}, $val);
508             }
509 1         11 $self->invoke_callback("end_set", $self->{key});
510             } elsif ($enc_type == REDIS_RDB_TYPE_ZSET) {
511 1         8 $length = &read_length($fh);
512 1         12 $self->invoke_callback("start_sorted_set",
513             $self->{key}, $length, $self->{expiry},
514             {'encoding' => 'skiplist'});
515 1         7 for (my $i = 0; $i < $length; ++$i) {
516 500         9320 $val = &read_string($fh);
517 500         887 my $dbl_length = &read_unsigned_char($fh);
518 500         763 read($fh, my $score, $dbl_length);
519 500         1208 $self->invoke_callback("zadd", $self->{key}, $score, $val);
520             }
521 1         12 $self->invoke_callback("end_sorted_set", $self->{key});
522             } elsif ($enc_type == REDIS_RDB_TYPE_HASH) {
523 1         8 $length = &read_length($fh);
524              
525 1         7 $self->invoke_callback("start_hash",
526             $self->{key}, $length, $self->{expiry},
527             {'encoding' => 'hashtable'});
528 1         8 for (my $i = 0; $i < $length; ++$i) {
529 1000         7541 my $field = &read_string($fh);
530 1000         2456 my $value = &read_string($fh);
531 1000         2907 $self->invoke_callback("hset", $self->{key}, $field, $value);
532             }
533 1         15 $self->invoke_callback("end_hash", $self->{key});
534             } elsif ($enc_type == REDIS_RDB_TYPE_HASH_ZIPMAP) {
535 0         0 $self->read_zipmap($fh);
536             } elsif ($enc_type == REDIS_RDB_TYPE_LIST_ZIPLIST) {
537 2         12 $self->read_ziplist($fh);
538             } elsif ($enc_type == REDIS_RDB_TYPE_SET_INTSET) {
539 3         11 $self->read_intset($fh);
540             } elsif ($enc_type == REDIS_RDB_TYPE_ZSET_ZIPLIST) {
541 1         5 $self->read_zset_from_ziplist($fh);
542             } elsif ($enc_type == REDIS_RDB_TYPE_HASH_ZIPLIST) {
543 1         6 $self->read_hash_from_ziplist($fh);
544             } else {
545 0         0 croak "Invalid object type $enc_type";
546             }
547             }
548              
549             sub read_intset {
550 3     3 0 4 my ($self, $fh) = @_;
551 3         4 my $seek = 0;
552 3         5 my $entry;
553             my $encode;
554 0         0 my $num_entries;
555              
556 3         6 my $raw_string = &read_string($fh);
557              
558 3         11 ($encode, $seek) = &read_unsigned_int_str($raw_string, $seek);
559 3         7 ($num_entries, $seek) = &read_unsigned_int_str($raw_string, $seek);
560              
561 3         151 $self->invoke_callback("start_set",
562             $self->{key}, $num_entries, $self->{expiry},
563             {'encoding' => 'intset', 'sizeof_value' => length($raw_string)});
564              
565 3         27 for (my $i = 0; $i < $num_entries; ++$i) {
566 9 100       60 if ($encode == 8) {
    100          
    50          
567 3         8 ($entry, $seek) = &read_unsigned_long_str($raw_string, $seek);
568             } elsif ($encode == 4) {
569 3         6 ($entry, $seek) = &read_unsigned_int_str($raw_string, $seek);
570             } elsif ($encode == 2) {
571 3         9 ($entry, $seek) = &read_unsigned_short_str($raw_string, $seek);
572             } else {
573 0         0 croak "Invalid encoding $encode";
574             }
575 9         25 $self->invoke_callback("sadd", $self->{key}, $entry);
576             }
577 3         26 $self->invoke_callback("end_set", $self->{key});
578             }
579              
580             sub read_ziplist_entry_str {
581 19     19 0 30 my ($str, $off) = @_;
582 19         20 my ($length, $value, $prev_length, $entry_header);
583            
584 19         44 ($prev_length, $off) = &read_unsigned_char_str($str, $off);
585 19 50       46 if ($prev_length == 254) {
586 0         0 ($prev_length, $off) = &read_unsigned_int_str($str, $off);
587             }
588              
589 19         75 ($entry_header, $off) = &read_unsigned_char_str($str, $off);
590              
591 19 100 0     90 if ($entry_header >> 6 == 0) {
    50          
    50          
    100          
    100          
    50          
    0          
    0          
    0          
592 11         13 $length = $entry_header & 0x3f;
593 11         19 $value = substr($str, $off, $length);
594 11         12 $off += $length;
595             } elsif ($entry_header >> 6 == 1) {
596 0         0 ($length, $off) = &read_unsigned_char_str($str, $off);
597 0         0 $length |= (($entry_header & 0x3f) << 8);
598 0         0 $value = substr($str, $off, $length);
599 0         0 $off += $length;
600             } elsif ($entry_header >> 6 == 2) {
601 0         0 ($length, $off) = &read_big_endian_unsigned_int_str($str, $off);
602 0         0 $value = substr($str, $off, $length);
603 0         0 $off += $length;
604             } elsif ($entry_header >> 4 == 12) {
605 1         3 ($value, $off) = &read_signed_short_str($str, $off);
606             } elsif ($entry_header >> 4 == 13) {
607 4         10 ($value, $off) = &read_signed_int_str($str, $off);
608             } elsif ($entry_header >> 4 == 14) {
609 3         16 ($value, $off) = &read_signed_long_str($str, $off);
610             } elsif ($entry_header == 240) {
611 0         0 ($value, $off) = &read_24bit_signed_number_str($str, $off);
612             } elsif ($entry_header == 254) {
613 0         0 ($value, $off) = &read_signed_char_str($str, $off);
614             } elsif ($entry_header >= 241 and $entry_header <= 253) {
615 0         0 $value = $entry_header - 241;
616             } else {
617 0         0 croak "Invalid entry_header $entry_header";
618             }
619              
620 19         43 return ($value, $off);
621             }
622              
623             sub read_ziplist_entry {
624 0     0 0 0 my ($fh) = @_;
625 0         0 my ($length, $value);
626              
627 0         0 my $prev_length = &read_unsigned_char($fh);
628 0 0       0 if ($prev_length == 254) {
629 0         0 $prev_length = &read_unsigned_int($fh);
630             }
631              
632 0         0 my $entry_header = &read_unsigned_char($fh);
633 0 0 0     0 if ($entry_header >> 6 == 0) {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
634 0         0 $length = $entry_header & 0x3f;
635 0         0 read($fh, $value, $length);
636             } elsif ($entry_header >> 6 == 1) {
637 0         0 $length = (($entry_header & 0x3f) << 8) | &read_unsigned_char($fh);
638 0         0 read($fh, $value, $length);
639             } elsif ($entry_header >> 6 == 2) {
640 0         0 $length = &read_big_endian_unsigned_int($fh);
641 0         0 read($fh, $value, $length);
642             } elsif ($entry_header >> 4 == 12) {
643 0         0 $value = &read_signed_short($fh);
644             } elsif ($entry_header >> 4 == 13) {
645 0         0 $value = &read_signed_int($fh);
646             } elsif ($entry_header >> 4 == 14) {
647 0         0 $value = &read_signed_long($fh);
648             } elsif ($entry_header == 240) {
649 0         0 $value = &read_24bit_signed_number($fh);
650             } elsif ($entry_header == 254) {
651 0         0 $value = &read_signed_char($fh);
652             } elsif ($entry_header >= 241 and $entry_header <= 253) {
653 0         0 $value = $entry_header - 241;
654             } else {
655 0         0 croak "Invalid entry_header $entry_header";
656             }
657              
658 0         0 return $value;
659             }
660              
661             sub read_ziplist {
662 2     2 0 4 my ($self, $fh) = @_;
663 2         3 my $seek = 0;
664 2         4 my ($entry, $zlbytes, $zltail, $num_entries, $value, $zlist_end);
665 2         3 my $raw_string = &read_string($fh);
666              
667 2         20 ($zlbytes, $seek) = &read_unsigned_int_str($raw_string, $seek);
668 2         5 ($zltail, $seek) = &read_unsigned_int_str($raw_string, $seek);
669 2         9 ($num_entries, $seek) = &read_unsigned_short_str($raw_string, $seek);
670              
671 2         18 $self->invoke_callback("start_list",
672             $self->{key}, $num_entries, $self->{expiry},
673             {'encoding' => 'ziplist', 'sizeof_value' => length($raw_string)});
674 2         20 for (my $i = 0; $i < $num_entries; ++$i) {
675 7         51 ($value, $seek) = &read_ziplist_entry_str($raw_string, $seek);
676 7         26 $self->invoke_callback("rpush", $self->{key}, $value);
677             }
678 2         17 ($zlist_end, $seek) = &read_unsigned_char_str($raw_string, $seek);
679 2 50       6 if ($zlist_end != 255) {
680 0         0 croak "Invalid zip list end $zlist_end";
681             }
682 2         6 $self->invoke_callback("end_list", $self->{key});
683             }
684              
685             sub read_zset_from_ziplist {
686 1     1 0 2 my ($self, $fh) = @_;
687 1         3 my $seek = 0;
688 1         2 my ($entry, $zlbytes, $zltail, $num_entries, $member, $score, $zlist_end);
689 1         2 my $raw_string = &read_string($fh);
690              
691 1         6 ($zlbytes, $seek) = &read_unsigned_int_str($raw_string, $seek);
692 1         12 ($zltail, $seek) = &read_unsigned_int_str($raw_string, $seek);
693 1         4 ($num_entries, $seek) = &read_unsigned_short_str($raw_string, $seek);
694              
695 1 50       5 if ($num_entries % 2) {
696 0         0 croak "Expected even number of elements but found $num_entries";
697             }
698              
699 1         3 $num_entries /= 2;
700              
701 1         8 $self->invoke_callback("start_sorted_set",
702             $self->{key}, $num_entries, $self->{expiry},
703             {'encoding' => 'ziplist', 'sizeof_value' => length($raw_string)});
704 1         9 for (my $i = 0; $i < $num_entries; ++$i) {
705 3         22 ($member, $seek) = &read_ziplist_entry_str($raw_string, $seek);
706 3         8 ($score, $seek) = &read_ziplist_entry_str($raw_string, $seek);
707 3         8 $self->invoke_callback("zadd", $self->{key}, $score, $member);
708             }
709 1         11 ($zlist_end, $seek) = &read_unsigned_char_str($raw_string, $seek);
710 1 50       5 if ($zlist_end != 255) {
711 0         0 croak "Invalid zip list end $zlist_end";
712             }
713              
714 1         8 $self->invoke_callback("end_sorted_set", $self->{key});
715             }
716              
717             sub read_hash_from_ziplist {
718 1     1 0 2 my ($self, $fh) = @_;
719 1         1 my $seek = 0;
720 1         2 my ($entry, $zlbytes, $zltail, $num_entries, $field, $value, $zlist_end);
721 1         2 my $raw_string = &read_string($fh);
722              
723 1         4 ($zlbytes, $seek) = &read_unsigned_int_str($raw_string, $seek);
724 1         4 ($zltail, $seek) = &read_unsigned_int_str($raw_string, $seek);
725 1         5 ($num_entries, $seek) = &read_unsigned_short_str($raw_string, $seek);
726              
727 1 50       9 if ($num_entries % 2) {
728 0         0 croak "Expected even number of elements but found $num_entries";
729             }
730              
731 1         3 $num_entries /= 2;
732              
733 1         15 $self->invoke_callback("start_hash",
734             $self->{key}, $num_entries, $self->{expiry},
735             {'encoding' => 'ziplist', 'sizeof_value' => length($raw_string)});
736              
737 1         9 for (my $i = 0; $i < $num_entries; ++$i) {
738 3         19 ($field, $seek) = &read_ziplist_entry_str($raw_string, $seek);
739 3         10 ($value, $seek) = &read_ziplist_entry_str($raw_string, $seek);
740 3         7 $self->invoke_callback("hset", $self->{key}, $field, $value);
741             }
742 1         9 ($zlist_end, $seek) = &read_unsigned_char_str($raw_string, $seek);
743 1 50       4 if ($zlist_end != 255) {
744 0         0 croak "Invalid zip list end $zlist_end";
745             }
746 1         3 $self->invoke_callback("end_hash", $self->{key});
747             }
748              
749             sub read_zipmap_next_length {
750 0     0 0 0 my ($str, $off) = @_;
751 0         0 my $length;
752 0         0 ($length, $off) = &read_unsigned_char_str($str, $off);
753              
754 0 0       0 if ($length < 254) {
    0          
755 0         0 return ($length, $off);
756             } elsif ($length == 254) {
757 0         0 return &read_unsigned_int_str($str, $off);
758             } else {
759 0         0 return (undef, $off);
760             }
761             }
762              
763             sub read_zipmap {
764 0     0 0 0 my ($self, $fh) = @_;
765 0         0 my $seek = 0;
766 0         0 my ($num_entries, $next_length, $key, $free, $value);
767 0         0 my $raw_string = &read_string($fh);
768              
769 0         0 ($num_entries, $seek) = &read_unsigned_char_str($raw_string, $seek);
770              
771 0         0 $self->invoke_callback("start_hash",
772             $self->{key}, $num_entries, $self->{expiry},
773             {'encoding' => 'zipmap', 'sizeof_value' => length($raw_string)});
774 0         0 while (1) {
775 0         0 ($next_length, $seek) = &read_zipmap_next_length($raw_string, $seek);
776 0 0       0 last unless defined($next_length);
777 0         0 $key = substr($raw_string, $seek, $next_length);
778 0         0 $seek += $next_length;
779              
780 0         0 ($next_length, $seek) = &read_zipmap_next_length($raw_string, $seek);
781 0 0       0 unless (defined($next_length)) {
782 0         0 croak "Unexpected end of zip map";
783             }
784              
785 0         0 ($free, $seek) = &read_unsigned_char_str($raw_string, $seek);
786 0         0 $value = substr($raw_string, $seek, $next_length);
787 0         0 $seek += $next_length;
788              
789 0         0 $seek += $free;
790              
791 0         0 $self->invoke_callback("hset", $self->{key}, $key, $value);
792             }
793              
794 0         0 $self->invoke_callback("end_hash", $self->{key});
795             }
796              
797             sub skip_string {
798 117     117 0 137 my ($fh) = @_;
799              
800 117         116 my $bytes_to_skip = 0;
801              
802 117         175 my ($length, $is_encoded) = &read_length_with_encoding($fh);
803 117 100       239 if ($is_encoded) {
804 31 100       88 if ($length == REDIS_RDB_ENC_INT8) {
    100          
    100          
    50          
805 6         11 $bytes_to_skip = 1;
806             } elsif ($length == REDIS_RDB_ENC_INT16) {
807 6         10 $bytes_to_skip = 2;
808             } elsif ($length == REDIS_RDB_ENC_INT32) {
809 6         12 $bytes_to_skip = 4;
810             } elsif ($length == REDIS_RDB_ENC_LZF) {
811 13         22 my $clen = &read_length($fh);
812 13         25 &read_length($fh);
813 13         22 $bytes_to_skip = $clen;
814             } else {
815 0         0 croak "Never get here";
816             }
817             } else {
818 86         135 $bytes_to_skip = $length;
819             }
820              
821 117         195 &skip($fh, $bytes_to_skip);
822             }
823              
824             sub skip_object {
825 97     97 0 119 my ($fh, $enc_type) = @_;
826              
827 97         111 my $skip_strings = 0;
828 97 100       309 if ($enc_type == REDIS_RDB_TYPE_STRING) {
    100          
    100          
    50          
    100          
    100          
    100          
    100          
    50          
    0          
829 49         63 $skip_strings = 1;
830             } elsif ($enc_type == REDIS_RDB_TYPE_LIST) {
831 2         5 $skip_strings = &read_length($fh);
832             } elsif ($enc_type == REDIS_RDB_TYPE_SET) {
833 6         10 $skip_strings = &read_length($fh);
834             } elsif ($enc_type == REDIS_RDB_TYPE_ZSET) {
835 0         0 $skip_strings = &read_length($fh) * 2;
836             } elsif ($enc_type == REDIS_RDB_TYPE_HASH) {
837 2         5 $skip_strings = &read_length($fh) * 2;
838             } elsif ($enc_type == REDIS_RDB_TYPE_HASH_ZIPMAP) {
839 4         6 $skip_strings = 1;
840             } elsif ($enc_type == REDIS_RDB_TYPE_LIST_ZIPLIST) {
841 20         26 $skip_strings = 1;
842             } elsif ($enc_type == REDIS_RDB_TYPE_SET_INTSET) {
843 6         10 $skip_strings = 1;
844             } elsif ($enc_type == REDIS_RDB_TYPE_ZSET_ZIPLIST) {
845 8         9 $skip_strings = 1;
846             } elsif ($enc_type == REDIS_RDB_TYPE_HASH_ZIPLIST) {
847 0         0 $skip_strings = 1;
848             } else {
849 0         0 croak "Invalid object type $enc_type\n";
850             }
851              
852 97         221 for (my $i = 0; $i < $skip_strings; ++$i) {
853 117         187 &skip_string($fh);
854             }
855             }
856              
857             sub skip_key_and_object {
858 0     0 0 0 my ($fh, $data_type) = @_;
859              
860 0         0 &skip_string($fh);
861 0         0 &skip_object($fh, $data_type);
862             }
863              
864             sub lzf_decompress {
865 4     4 0 11 my ($compressed, $expected_length) = @_;
866              
867 4         12 my $in_len = length($compressed);
868 4         7 my $in_index = 0;
869 4         17 my @out_bytes;
870 4         7 my $out_index = 0;
871 4         7 my $ref;
872              
873 4         18 while ($in_index < $in_len) {
874 35         70 my $ctrl = unpack('C', substr($compressed, $in_index, 1));
875 35         768 ++$in_index;
876              
877 35 100       68 if ($ctrl < 32) {
878 20         55 for (my $i = 0; $i < $ctrl + 1; ++$i) {
879 171         267 $out_bytes[$out_index] = substr($compressed, $in_index, 1);
880 171         150 ++$in_index;
881 171         337 ++$out_index;
882             }
883             } else {
884 15         52 my $length = $ctrl >> 5;
885              
886 15 100       3001 if ($length == 7) {
887 4         11 $length += unpack('C', substr($compressed, $in_index, 1));
888 4         7 ++$in_index;
889             }
890              
891 15         1281 $ref = $out_index - (($ctrl & 0x1f) << 8) -
892             unpack('C', substr($compressed, $in_index, 1)) - 1;
893 15         18 ++$in_index;
894              
895 15         372 for (my $i = 0; $i < $length + 2; ++$i) {
896 265         549 $out_bytes[$out_index] = $out_bytes[$ref];
897 265         390 ++$ref;
898 265         930 ++$out_index;
899             }
900             }
901             }
902              
903 4 50       27 if ($out_index != $expected_length) {
904 0         0 croak "Expected lengths do not match: $out_index != $expected_length\n";
905             }
906 4         70 return join("", @out_bytes);
907             }
908              
909             #==========================================
910             # default callbacks
911             #==========================================
912             sub def_start_rdb {
913 0     0 0   my $filename = shift;
914 0           print '[';
915             }
916              
917             sub def_start_database {
918 0     0 0   my $db_number = shift;
919 0           print "{";
920             }
921              
922             sub def_key {
923 0     0 0   my $key = shift;
924             ### do nothing
925             }
926              
927             sub def_set {
928 0     0 0   my ($key, $value, $expiry) = @_;
929 0           print "\"$key\" : \"$value\", ";
930             }
931              
932             sub def_start_hash {
933 0     0 0   my ($key, $length, $expiry) = @_;
934 0           print "\"$key\" : {";
935             }
936              
937             sub def_hset {
938 0     0 0   my ($key, $field, $value) = @_;
939 0           print "\"$field\" : \"$value\", ";
940             }
941              
942             sub def_end_hash {
943 0     0 0   my $key = shift;
944 0           print '}, ';
945             }
946              
947             sub def_start_set {
948 0     0 0   my ($key, $cardinality, $expiry) = @_;
949 0           print "\"$key\" : [";
950             }
951              
952             sub def_sadd {
953 0     0 0   my ($key, $member) = @_;
954 0           print "\"$member\", ";
955             }
956              
957             sub def_end_set {
958 0     0 0   my ($key) = @_;
959 0           print "], ";
960             }
961              
962             sub def_start_list {
963 0     0 0   my ($key, $length, $expiry) = @_;
964 0           print "\"$key\" : [";
965             }
966              
967             sub def_rpush {
968 0     0 0   my ($key, $value) = @_;
969 0           print "\"$value\", ";
970             }
971              
972             sub def_end_list {
973 0     0 0   my ($key) = @_;
974 0           print "], ";
975             }
976              
977             sub def_start_sorted_set {
978 0     0 0   my ($key, $length, $expiry) = @_;
979 0           print "\"$key\" : {";
980             }
981              
982             sub def_zadd {
983 0     0 0   my ($key, $score, $member) = @_;
984 0           print "\"$member\" : \"$score\", ";
985             }
986              
987             sub def_end_sorted_set {
988 0     0 0   my ($key) = @_;
989 0           print "}, ";
990             }
991              
992             sub def_end_database {
993 0     0 0   my $db_number = shift;
994 0           print "}, ";
995             }
996              
997             sub def_end_rdb {
998 0     0 0   my $filename = shift;
999 0           print "], ";
1000             }
1001              
1002             1;
1003              
1004             __END__