11import struct
22import io
3- import sys
43import datetime
54import re
65
76from rdbtools .encodehelpers import STRING_ESCAPE_RAW , apply_escape_bytes , bval
87from .compat import range , str2regexp
8+ from .iowrapper import IOWrapper
99
1010try :
1111 try :
4141REDIS_RDB_TYPE_HASH = 4
4242REDIS_RDB_TYPE_ZSET_2 = 5 # ZSET version 2 with doubles stored in binary.
4343REDIS_RDB_TYPE_MODULE = 6
44+ REDIS_RDB_TYPE_MODULE_2 = 7
4445REDIS_RDB_TYPE_HASH_ZIPMAP = 9
4546REDIS_RDB_TYPE_LIST_ZIPLIST = 10
4647REDIS_RDB_TYPE_SET_INTSET = 11
5354REDIS_RDB_ENC_INT32 = 2
5455REDIS_RDB_ENC_LZF = 3
5556
57+ REDIS_RDB_MODULE_OPCODE_EOF = 0 # End of module value.
58+ REDIS_RDB_MODULE_OPCODE_SINT = 1
59+ REDIS_RDB_MODULE_OPCODE_UINT = 2
60+ REDIS_RDB_MODULE_OPCODE_FLOAT = 3
61+ REDIS_RDB_MODULE_OPCODE_DOUBLE = 4
62+ REDIS_RDB_MODULE_OPCODE_STRING = 5
63+
5664DATA_TYPE_MAPPING = {
57- 0 : "string" , 1 : "list" , 2 : "set" , 3 : "sortedset" , 4 : "hash" , 5 : "sortedset" , 6 : "module" ,
65+ 0 : "string" , 1 : "list" , 2 : "set" , 3 : "sortedset" , 4 : "hash" , 5 : "sortedset" , 6 : "module" , 7 : "module" ,
5866 9 : "hash" , 10 : "list" , 11 : "set" , 12 : "sortedset" , 13 : "hash" , 14 : "list" }
5967
6068class RdbCallback (object ):
@@ -106,7 +114,23 @@ def start_database(self, db_number):
106114
107115 Typically, callbacks store the current database number in a class variable
108116
109- """
117+ """
118+ pass
119+
120+ def start_module (self , key , module_name , expiry ):
121+ """
122+ Called to indicate start of a module key
123+ :param key: string
124+ :param module_name: string
125+ :param expiry:
126+ :return: boolean to indicate whatever to record the full buffer or not
127+ """
128+ return False
129+
130+ def handle_module_data (self , key , opcode , data ):
131+ pass
132+
133+ def end_module (self , key , buffer_size , buffer = None ):
110134 pass
111135
112136 def db_size (self , db_size , expires_size ):
@@ -366,14 +390,14 @@ def parse_fd(self, fd):
366390 self ._callback .db_size (db_size , expire_size )
367391 continue
368392
369- if data_type == REDIS_RDB_OPCODE_EOF :
393+ if data_type == REDIS_RDB_OPCODE_EOF :
370394 self ._callback .end_database (db_number )
371395 self ._callback .end_rdb ()
372396 if self ._rdb_version >= 5 :
373397 f .read (8 )
374398 break
375399
376- if self .matches_filter (db_number ) :
400+ if self .matches_filter (db_number ):
377401 self ._key = self .read_string (f )
378402 if self .matches_filter (db_number , self ._key , data_type ):
379403 self .read_object (f , data_type )
@@ -382,20 +406,20 @@ def parse_fd(self, fd):
382406 else :
383407 self .skip_key_and_object (f , data_type )
384408
385- def read_length_with_encoding (self , f ) :
409+ def read_length_with_encoding (self , f ):
386410 length = 0
387411 is_encoded = False
388412 bytes = []
389413 bytes .append (read_unsigned_char (f ))
390414 enc_type = (bytes [0 ] & 0xC0 ) >> 6
391- if enc_type == REDIS_RDB_ENCVAL :
415+ if enc_type == REDIS_RDB_ENCVAL :
392416 is_encoded = True
393417 length = bytes [0 ] & 0x3F
394- elif enc_type == REDIS_RDB_6BITLEN :
418+ elif enc_type == REDIS_RDB_6BITLEN :
395419 length = bytes [0 ] & 0x3F
396- elif enc_type == REDIS_RDB_14BITLEN :
420+ elif enc_type == REDIS_RDB_14BITLEN :
397421 bytes .append (read_unsigned_char (f ))
398- length = ((bytes [0 ]& 0x3F )<< 8 ) | bytes [1 ]
422+ length = ((bytes [0 ] & 0x3F ) << 8 ) | bytes [1 ]
399423 elif bytes [0 ] == REDIS_RDB_32BITLEN :
400424 length = read_unsigned_int_be (f )
401425 elif bytes [0 ] == REDIS_RDB_64BITLEN :
@@ -460,47 +484,49 @@ def read_object(self, f, enc_type) :
460484 val = self .read_string (f )
461485 self ._callback .rpush (self ._key , val )
462486 self ._callback .end_list (self ._key , info = {'encoding' :'linkedlist' })
463- elif enc_type == REDIS_RDB_TYPE_SET :
487+ elif enc_type == REDIS_RDB_TYPE_SET :
464488 # A redis list is just a sequence of strings
465489 # We successively read strings from the stream and create a set from it
466490 # Note that the order of strings is non-deterministic
467491 length = self .read_length (f )
468492 self ._callback .start_set (self ._key , length , self ._expiry , info = {'encoding' :'hashtable' })
469- for count in range (0 , length ) :
493+ for count in range (0 , length ):
470494 val = self .read_string (f )
471495 self ._callback .sadd (self ._key , val )
472496 self ._callback .end_set (self ._key )
473497 elif enc_type == REDIS_RDB_TYPE_ZSET or enc_type == REDIS_RDB_TYPE_ZSET_2 :
474498 length = self .read_length (f )
475499 self ._callback .start_sorted_set (self ._key , length , self ._expiry , info = {'encoding' :'skiplist' })
476- for count in range (0 , length ) :
500+ for count in range (0 , length ):
477501 val = self .read_string (f )
478502 score = read_double (f ) if enc_type == REDIS_RDB_TYPE_ZSET_2 else self .read_float (f )
479503 self ._callback .zadd (self ._key , score , val )
480504 self ._callback .end_sorted_set (self ._key )
481- elif enc_type == REDIS_RDB_TYPE_HASH :
505+ elif enc_type == REDIS_RDB_TYPE_HASH :
482506 length = self .read_length (f )
483507 self ._callback .start_hash (self ._key , length , self ._expiry , info = {'encoding' :'hashtable' })
484- for count in range (0 , length ) :
508+ for count in range (0 , length ):
485509 field = self .read_string (f )
486510 value = self .read_string (f )
487511 self ._callback .hset (self ._key , field , value )
488512 self ._callback .end_hash (self ._key )
489- elif enc_type == REDIS_RDB_TYPE_HASH_ZIPMAP :
513+ elif enc_type == REDIS_RDB_TYPE_HASH_ZIPMAP :
490514 self .read_zipmap (f )
491- elif enc_type == REDIS_RDB_TYPE_LIST_ZIPLIST :
515+ elif enc_type == REDIS_RDB_TYPE_LIST_ZIPLIST :
492516 self .read_ziplist (f )
493- elif enc_type == REDIS_RDB_TYPE_SET_INTSET :
517+ elif enc_type == REDIS_RDB_TYPE_SET_INTSET :
494518 self .read_intset (f )
495- elif enc_type == REDIS_RDB_TYPE_ZSET_ZIPLIST :
519+ elif enc_type == REDIS_RDB_TYPE_ZSET_ZIPLIST :
496520 self .read_zset_from_ziplist (f )
497- elif enc_type == REDIS_RDB_TYPE_HASH_ZIPLIST :
521+ elif enc_type == REDIS_RDB_TYPE_HASH_ZIPLIST :
498522 self .read_hash_from_ziplist (f )
499523 elif enc_type == REDIS_RDB_TYPE_LIST_QUICKLIST :
500524 self .read_list_from_quicklist (f )
501- elif enc_type == REDIS_RDB_TYPE_MODULE :
502- raise Exception ('read_object' , 'Unable to read Redis Modules RDB objects (key %s)' % (enc_type , self ._key ))
503- else :
525+ elif enc_type == REDIS_RDB_TYPE_MODULE :
526+ raise Exception ('read_object' , 'Unable to read Redis Modules RDB objects (key %s)' % self ._key )
527+ elif enc_type == REDIS_RDB_TYPE_MODULE_2 :
528+ self .read_module (f )
529+ else :
504530 raise Exception ('read_object' , 'Invalid object type %d for key %s' % (enc_type , self ._key ))
505531
506532 def skip_key_and_object (self , f , data_type ):
@@ -564,8 +590,10 @@ def skip_object(self, f, enc_type):
564590 elif enc_type == REDIS_RDB_TYPE_LIST_QUICKLIST :
565591 skip_strings = self .read_length (f )
566592 elif enc_type == REDIS_RDB_TYPE_MODULE :
567- raise Exception ('skip_object' , 'Unable to skip Redis Modules RDB objects (key %s)' % (enc_type , self ._key ))
568- else :
593+ raise Exception ('skip_object' , 'Unable to skip Redis Modules RDB objects (key %s)' % self ._key )
594+ elif enc_type == REDIS_RDB_TYPE_MODULE_2 :
595+ self .read_module (f )
596+ else :
569597 raise Exception ('skip_object' , 'Invalid object type %d for key %s' % (enc_type , self ._key ))
570598 for x in range (0 , skip_strings ):
571599 self .skip_string (f )
@@ -728,6 +756,56 @@ def read_zipmap_next_length(self, f) :
728756 else :
729757 return None
730758
759+ def read_module (self , f ):
760+ # this method is based on the actual implementation in redis (src/rdb.c:rdbLoadObject)
761+ iowrapper = IOWrapper (f )
762+ iowrapper .start_recording_size ()
763+ iowrapper .start_recording ()
764+ length , encoding = self .read_length_with_encoding (iowrapper )
765+ record_buffer = self ._callback .start_module (self ._key , self ._decode_module_id (length ), self ._expiry )
766+
767+ if not record_buffer :
768+ iowrapper .stop_recording ()
769+
770+ opcode = self .read_length (iowrapper )
771+ while opcode != REDIS_RDB_MODULE_OPCODE_EOF :
772+ if opcode == REDIS_RDB_MODULE_OPCODE_SINT or opcode == REDIS_RDB_MODULE_OPCODE_UINT :
773+ data = self .read_length (iowrapper )
774+ elif opcode == REDIS_RDB_MODULE_OPCODE_FLOAT :
775+ data = self .read_float (iowrapper )
776+ elif opcode == REDIS_RDB_MODULE_OPCODE_DOUBLE :
777+ data = read_double (iowrapper )
778+ elif opcode == REDIS_RDB_MODULE_OPCODE_STRING :
779+ data = self .read_string (iowrapper )
780+ else :
781+ raise Exception ("Unknown module opcode %s" % opcode )
782+ self ._callback .handle_module_data (self ._key , opcode , data )
783+ # read the next item in the module data type
784+ opcode = self .read_length (iowrapper )
785+
786+ buffer = None
787+ if record_buffer :
788+ # prepand the buffer with REDIS_RDB_TYPE_MODULE_2 type
789+ buffer = struct .pack ('B' , REDIS_RDB_TYPE_MODULE_2 ) + iowrapper .get_recorded_buffer ()
790+ iowrapper .stop_recording ()
791+ self ._callback .end_module (self ._key , buffer_size = iowrapper .get_recorded_size (), buffer = buffer )
792+
793+ charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'
794+
795+ def _decode_module_id (self , module_id ):
796+ """
797+ decode module id to string
798+ based on @antirez moduleTypeNameByID function from redis/src/module.c
799+ :param module_id: 64bit integer
800+ :return: string
801+ """
802+ name = ['' ] * 9
803+ module_id >>= 10
804+ for i in reversed (range (9 )):
805+ name [i ] = self .charset [module_id & 63 ]
806+ module_id >>= 6
807+ return '' .join (name )
808+
731809 def verify_magic_string (self , magic_string ) :
732810 if magic_string != b'REDIS' :
733811 raise Exception ('verify_magic_string' , 'Invalid File Format' )
0 commit comments