mirror of https://github.com/ledisdb/ledisdb.git
825 lines
19 KiB
Go
825 lines
19 KiB
Go
// Package rdb implements parsing and encoding of the Redis RDB file format.
|
|
package rdb
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
"math"
|
|
"strconv"
|
|
|
|
"github.com/cupcake/rdb/crc64"
|
|
)
|
|
|
|
// A Decoder must be implemented to parse a RDB file.
|
|
type Decoder interface {
|
|
// StartRDB is called when parsing of a valid RDB file starts.
|
|
StartRDB()
|
|
// StartDatabase is called when database n starts.
|
|
// Once a database starts, another database will not start until EndDatabase is called.
|
|
StartDatabase(n int)
|
|
// AUX field
|
|
Aux(key, value []byte)
|
|
// ResizeDB hint
|
|
ResizeDatabase(dbSize, expiresSize uint32)
|
|
// Set is called once for each string key.
|
|
Set(key, value []byte, expiry int64)
|
|
// StartHash is called at the beginning of a hash.
|
|
// Hset will be called exactly length times before EndHash.
|
|
StartHash(key []byte, length, expiry int64)
|
|
// Hset is called once for each field=value pair in a hash.
|
|
Hset(key, field, value []byte)
|
|
// EndHash is called when there are no more fields in a hash.
|
|
EndHash(key []byte)
|
|
// StartSet is called at the beginning of a set.
|
|
// Sadd will be called exactly cardinality times before EndSet.
|
|
StartSet(key []byte, cardinality, expiry int64)
|
|
// Sadd is called once for each member of a set.
|
|
Sadd(key, member []byte)
|
|
// EndSet is called when there are no more fields in a set.
|
|
EndSet(key []byte)
|
|
// StartList is called at the beginning of a list.
|
|
// Rpush will be called exactly length times before EndList.
|
|
// If length of the list is not known, then length is -1
|
|
StartList(key []byte, length, expiry int64)
|
|
// Rpush is called once for each value in a list.
|
|
Rpush(key, value []byte)
|
|
// EndList is called when there are no more values in a list.
|
|
EndList(key []byte)
|
|
// StartZSet is called at the beginning of a sorted set.
|
|
// Zadd will be called exactly cardinality times before EndZSet.
|
|
StartZSet(key []byte, cardinality, expiry int64)
|
|
// Zadd is called once for each member of a sorted set.
|
|
Zadd(key []byte, score float64, member []byte)
|
|
// EndZSet is called when there are no more members in a sorted set.
|
|
EndZSet(key []byte)
|
|
// EndDatabase is called at the end of a database.
|
|
EndDatabase(n int)
|
|
// EndRDB is called when parsing of the RDB file is complete.
|
|
EndRDB()
|
|
}
|
|
|
|
// Decode parses a RDB file from r and calls the decode hooks on d.
|
|
func Decode(r io.Reader, d Decoder) error {
|
|
decoder := &decode{d, make([]byte, 8), bufio.NewReader(r)}
|
|
return decoder.decode()
|
|
}
|
|
|
|
// Decode a byte slice from the Redis DUMP command. The dump does not contain the
|
|
// database, key or expiry, so they must be included in the function call (but
|
|
// can be zero values).
|
|
func DecodeDump(dump []byte, db int, key []byte, expiry int64, d Decoder) error {
|
|
err := verifyDump(dump)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
decoder := &decode{d, make([]byte, 8), bytes.NewReader(dump[1:])}
|
|
decoder.event.StartRDB()
|
|
decoder.event.StartDatabase(db)
|
|
|
|
err = decoder.readObject(key, ValueType(dump[0]), expiry)
|
|
|
|
decoder.event.EndDatabase(db)
|
|
decoder.event.EndRDB()
|
|
return err
|
|
}
|
|
|
|
type byteReader interface {
|
|
io.Reader
|
|
io.ByteReader
|
|
}
|
|
|
|
type decode struct {
|
|
event Decoder
|
|
intBuf []byte
|
|
r byteReader
|
|
}
|
|
|
|
type ValueType byte
|
|
|
|
const (
|
|
TypeString ValueType = 0
|
|
TypeList ValueType = 1
|
|
TypeSet ValueType = 2
|
|
TypeZSet ValueType = 3
|
|
TypeHash ValueType = 4
|
|
|
|
TypeHashZipmap ValueType = 9
|
|
TypeListZiplist ValueType = 10
|
|
TypeSetIntset ValueType = 11
|
|
TypeZSetZiplist ValueType = 12
|
|
TypeHashZiplist ValueType = 13
|
|
TypeListQuicklist ValueType = 14
|
|
)
|
|
|
|
const (
|
|
rdb6bitLen = 0
|
|
rdb14bitLen = 1
|
|
rdb32bitLen = 2
|
|
rdbEncVal = 3
|
|
|
|
rdbFlagAux = 0xfa
|
|
rdbFlagResizeDB = 0xfb
|
|
rdbFlagExpiryMS = 0xfc
|
|
rdbFlagExpiry = 0xfd
|
|
rdbFlagSelectDB = 0xfe
|
|
rdbFlagEOF = 0xff
|
|
|
|
rdbEncInt8 = 0
|
|
rdbEncInt16 = 1
|
|
rdbEncInt32 = 2
|
|
rdbEncLZF = 3
|
|
|
|
rdbZiplist6bitlenString = 0
|
|
rdbZiplist14bitlenString = 1
|
|
rdbZiplist32bitlenString = 2
|
|
|
|
rdbZiplistInt16 = 0xc0
|
|
rdbZiplistInt32 = 0xd0
|
|
rdbZiplistInt64 = 0xe0
|
|
rdbZiplistInt24 = 0xf0
|
|
rdbZiplistInt8 = 0xfe
|
|
rdbZiplistInt4 = 15
|
|
)
|
|
|
|
func (d *decode) decode() error {
|
|
err := d.checkHeader()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
d.event.StartRDB()
|
|
|
|
var db uint32
|
|
var expiry int64
|
|
firstDB := true
|
|
for {
|
|
objType, err := d.r.ReadByte()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
switch objType {
|
|
case rdbFlagAux:
|
|
auxKey, err := d.readString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
auxVal, err := d.readString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
d.event.Aux(auxKey, auxVal)
|
|
case rdbFlagResizeDB:
|
|
dbSize, _, err := d.readLength()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
expiresSize, _, err := d.readLength()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
d.event.ResizeDatabase(dbSize, expiresSize)
|
|
case rdbFlagExpiryMS:
|
|
_, err := io.ReadFull(d.r, d.intBuf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
expiry = int64(binary.LittleEndian.Uint64(d.intBuf))
|
|
case rdbFlagExpiry:
|
|
_, err := io.ReadFull(d.r, d.intBuf[:4])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
expiry = int64(binary.LittleEndian.Uint32(d.intBuf)) * 1000
|
|
case rdbFlagSelectDB:
|
|
if !firstDB {
|
|
d.event.EndDatabase(int(db))
|
|
}
|
|
db, _, err = d.readLength()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
d.event.StartDatabase(int(db))
|
|
case rdbFlagEOF:
|
|
d.event.EndDatabase(int(db))
|
|
d.event.EndRDB()
|
|
return nil
|
|
default:
|
|
key, err := d.readString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = d.readObject(key, ValueType(objType), expiry)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
expiry = 0
|
|
}
|
|
}
|
|
|
|
panic("not reached")
|
|
}
|
|
|
|
func (d *decode) readObject(key []byte, typ ValueType, expiry int64) error {
|
|
switch typ {
|
|
case TypeString:
|
|
value, err := d.readString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
d.event.Set(key, value, expiry)
|
|
case TypeList:
|
|
length, _, err := d.readLength()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
d.event.StartList(key, int64(length), expiry)
|
|
for i := uint32(0); i < length; i++ {
|
|
value, err := d.readString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
d.event.Rpush(key, value)
|
|
}
|
|
d.event.EndList(key)
|
|
case TypeListQuicklist:
|
|
length, _, err := d.readLength()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
d.event.StartList(key, int64(-1), expiry)
|
|
for i := uint32(0); i < length; i++ {
|
|
d.readZiplist(key, 0, false)
|
|
}
|
|
d.event.EndList(key)
|
|
case TypeSet:
|
|
cardinality, _, err := d.readLength()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
d.event.StartSet(key, int64(cardinality), expiry)
|
|
for i := uint32(0); i < cardinality; i++ {
|
|
member, err := d.readString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
d.event.Sadd(key, member)
|
|
}
|
|
d.event.EndSet(key)
|
|
case TypeZSet:
|
|
cardinality, _, err := d.readLength()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
d.event.StartZSet(key, int64(cardinality), expiry)
|
|
for i := uint32(0); i < cardinality; i++ {
|
|
member, err := d.readString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
score, err := d.readFloat64()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
d.event.Zadd(key, score, member)
|
|
}
|
|
d.event.EndZSet(key)
|
|
case TypeHash:
|
|
length, _, err := d.readLength()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
d.event.StartHash(key, int64(length), expiry)
|
|
for i := uint32(0); i < length; i++ {
|
|
field, err := d.readString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
value, err := d.readString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
d.event.Hset(key, field, value)
|
|
}
|
|
d.event.EndHash(key)
|
|
case TypeHashZipmap:
|
|
return d.readZipmap(key, expiry)
|
|
case TypeListZiplist:
|
|
return d.readZiplist(key, expiry, true)
|
|
case TypeSetIntset:
|
|
return d.readIntset(key, expiry)
|
|
case TypeZSetZiplist:
|
|
return d.readZiplistZset(key, expiry)
|
|
case TypeHashZiplist:
|
|
return d.readZiplistHash(key, expiry)
|
|
default:
|
|
return fmt.Errorf("rdb: unknown object type %d for key %s", typ, key)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *decode) readZipmap(key []byte, expiry int64) error {
|
|
var length int
|
|
zipmap, err := d.readString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
buf := newSliceBuffer(zipmap)
|
|
lenByte, err := buf.ReadByte()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if lenByte >= 254 { // we need to count the items manually
|
|
length, err = countZipmapItems(buf)
|
|
length /= 2
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
length = int(lenByte)
|
|
}
|
|
d.event.StartHash(key, int64(length), expiry)
|
|
for i := 0; i < length; i++ {
|
|
field, err := readZipmapItem(buf, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
value, err := readZipmapItem(buf, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
d.event.Hset(key, field, value)
|
|
}
|
|
d.event.EndHash(key)
|
|
return nil
|
|
}
|
|
|
|
func readZipmapItem(buf *sliceBuffer, readFree bool) ([]byte, error) {
|
|
length, free, err := readZipmapItemLength(buf, readFree)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if length == -1 {
|
|
return nil, nil
|
|
}
|
|
value, err := buf.Slice(length)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
_, err = buf.Seek(int64(free), 1)
|
|
return value, err
|
|
}
|
|
|
|
func countZipmapItems(buf *sliceBuffer) (int, error) {
|
|
n := 0
|
|
for {
|
|
strLen, free, err := readZipmapItemLength(buf, n%2 != 0)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if strLen == -1 {
|
|
break
|
|
}
|
|
_, err = buf.Seek(int64(strLen)+int64(free), 1)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
n++
|
|
}
|
|
_, err := buf.Seek(0, 0)
|
|
return n, err
|
|
}
|
|
|
|
func readZipmapItemLength(buf *sliceBuffer, readFree bool) (int, int, error) {
|
|
b, err := buf.ReadByte()
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
switch b {
|
|
case 253:
|
|
s, err := buf.Slice(5)
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
return int(binary.BigEndian.Uint32(s)), int(s[4]), nil
|
|
case 254:
|
|
return 0, 0, fmt.Errorf("rdb: invalid zipmap item length")
|
|
case 255:
|
|
return -1, 0, nil
|
|
}
|
|
var free byte
|
|
if readFree {
|
|
free, err = buf.ReadByte()
|
|
}
|
|
return int(b), int(free), err
|
|
}
|
|
|
|
func (d *decode) readZiplist(key []byte, expiry int64, addListEvents bool) error {
|
|
ziplist, err := d.readString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
buf := newSliceBuffer(ziplist)
|
|
length, err := readZiplistLength(buf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if addListEvents {
|
|
d.event.StartList(key, length, expiry)
|
|
}
|
|
for i := int64(0); i < length; i++ {
|
|
entry, err := readZiplistEntry(buf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
d.event.Rpush(key, entry)
|
|
}
|
|
if addListEvents {
|
|
d.event.EndList(key)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *decode) readZiplistZset(key []byte, expiry int64) error {
|
|
ziplist, err := d.readString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
buf := newSliceBuffer(ziplist)
|
|
cardinality, err := readZiplistLength(buf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cardinality /= 2
|
|
d.event.StartZSet(key, cardinality, expiry)
|
|
for i := int64(0); i < cardinality; i++ {
|
|
member, err := readZiplistEntry(buf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
scoreBytes, err := readZiplistEntry(buf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
score, err := strconv.ParseFloat(string(scoreBytes), 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
d.event.Zadd(key, score, member)
|
|
}
|
|
d.event.EndZSet(key)
|
|
return nil
|
|
}
|
|
|
|
func (d *decode) readZiplistHash(key []byte, expiry int64) error {
|
|
ziplist, err := d.readString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
buf := newSliceBuffer(ziplist)
|
|
length, err := readZiplistLength(buf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
length /= 2
|
|
d.event.StartHash(key, length, expiry)
|
|
for i := int64(0); i < length; i++ {
|
|
field, err := readZiplistEntry(buf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
value, err := readZiplistEntry(buf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
d.event.Hset(key, field, value)
|
|
}
|
|
d.event.EndHash(key)
|
|
return nil
|
|
}
|
|
|
|
func readZiplistLength(buf *sliceBuffer) (int64, error) {
|
|
buf.Seek(8, 0) // skip the zlbytes and zltail
|
|
lenBytes, err := buf.Slice(2)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return int64(binary.LittleEndian.Uint16(lenBytes)), nil
|
|
}
|
|
|
|
func readZiplistEntry(buf *sliceBuffer) ([]byte, error) {
|
|
prevLen, err := buf.ReadByte()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if prevLen == 254 {
|
|
buf.Seek(4, 1) // skip the 4-byte prevlen
|
|
}
|
|
|
|
header, err := buf.ReadByte()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
switch {
|
|
case header>>6 == rdbZiplist6bitlenString:
|
|
return buf.Slice(int(header & 0x3f))
|
|
case header>>6 == rdbZiplist14bitlenString:
|
|
b, err := buf.ReadByte()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return buf.Slice((int(header&0x3f) << 8) | int(b))
|
|
case header>>6 == rdbZiplist32bitlenString:
|
|
lenBytes, err := buf.Slice(4)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return buf.Slice(int(binary.BigEndian.Uint32(lenBytes)))
|
|
case header == rdbZiplistInt16:
|
|
intBytes, err := buf.Slice(2)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return []byte(strconv.FormatInt(int64(int16(binary.LittleEndian.Uint16(intBytes))), 10)), nil
|
|
case header == rdbZiplistInt32:
|
|
intBytes, err := buf.Slice(4)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return []byte(strconv.FormatInt(int64(int32(binary.LittleEndian.Uint32(intBytes))), 10)), nil
|
|
case header == rdbZiplistInt64:
|
|
intBytes, err := buf.Slice(8)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return []byte(strconv.FormatInt(int64(binary.LittleEndian.Uint64(intBytes)), 10)), nil
|
|
case header == rdbZiplistInt24:
|
|
intBytes := make([]byte, 4)
|
|
_, err := buf.Read(intBytes[1:])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return []byte(strconv.FormatInt(int64(int32(binary.LittleEndian.Uint32(intBytes))>>8), 10)), nil
|
|
case header == rdbZiplistInt8:
|
|
b, err := buf.ReadByte()
|
|
return []byte(strconv.FormatInt(int64(int8(b)), 10)), err
|
|
case header>>4 == rdbZiplistInt4:
|
|
return []byte(strconv.FormatInt(int64(header&0x0f)-1, 10)), nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("rdb: unknown ziplist header byte: %d", header)
|
|
}
|
|
|
|
func (d *decode) readIntset(key []byte, expiry int64) error {
|
|
intset, err := d.readString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
buf := newSliceBuffer(intset)
|
|
intSizeBytes, err := buf.Slice(4)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
intSize := binary.LittleEndian.Uint32(intSizeBytes)
|
|
|
|
if intSize != 2 && intSize != 4 && intSize != 8 {
|
|
return fmt.Errorf("rdb: unknown intset encoding: %d", intSize)
|
|
}
|
|
|
|
lenBytes, err := buf.Slice(4)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cardinality := binary.LittleEndian.Uint32(lenBytes)
|
|
|
|
d.event.StartSet(key, int64(cardinality), expiry)
|
|
for i := uint32(0); i < cardinality; i++ {
|
|
intBytes, err := buf.Slice(int(intSize))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var intString string
|
|
switch intSize {
|
|
case 2:
|
|
intString = strconv.FormatInt(int64(int16(binary.LittleEndian.Uint16(intBytes))), 10)
|
|
case 4:
|
|
intString = strconv.FormatInt(int64(int32(binary.LittleEndian.Uint32(intBytes))), 10)
|
|
case 8:
|
|
intString = strconv.FormatInt(int64(int64(binary.LittleEndian.Uint64(intBytes))), 10)
|
|
}
|
|
d.event.Sadd(key, []byte(intString))
|
|
}
|
|
d.event.EndSet(key)
|
|
return nil
|
|
}
|
|
|
|
func (d *decode) checkHeader() error {
|
|
header := make([]byte, 9)
|
|
_, err := io.ReadFull(d.r, header)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !bytes.Equal(header[:5], []byte("REDIS")) {
|
|
return fmt.Errorf("rdb: invalid file format")
|
|
}
|
|
|
|
version, _ := strconv.ParseInt(string(header[5:]), 10, 64)
|
|
if version < 1 || version > 7 {
|
|
return fmt.Errorf("rdb: invalid RDB version number %d", version)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *decode) readString() ([]byte, error) {
|
|
length, encoded, err := d.readLength()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if encoded {
|
|
switch length {
|
|
case rdbEncInt8:
|
|
i, err := d.readUint8()
|
|
return []byte(strconv.FormatInt(int64(int8(i)), 10)), err
|
|
case rdbEncInt16:
|
|
i, err := d.readUint16()
|
|
return []byte(strconv.FormatInt(int64(int16(i)), 10)), err
|
|
case rdbEncInt32:
|
|
i, err := d.readUint32()
|
|
return []byte(strconv.FormatInt(int64(int32(i)), 10)), err
|
|
case rdbEncLZF:
|
|
clen, _, err := d.readLength()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ulen, _, err := d.readLength()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
compressed := make([]byte, clen)
|
|
_, err = io.ReadFull(d.r, compressed)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
decompressed := lzfDecompress(compressed, int(ulen))
|
|
if len(decompressed) != int(ulen) {
|
|
return nil, fmt.Errorf("decompressed string length %d didn't match expected length %d", len(decompressed), ulen)
|
|
}
|
|
return decompressed, nil
|
|
}
|
|
}
|
|
|
|
str := make([]byte, length)
|
|
_, err = io.ReadFull(d.r, str)
|
|
return str, err
|
|
}
|
|
|
|
func (d *decode) readUint8() (uint8, error) {
|
|
b, err := d.r.ReadByte()
|
|
return uint8(b), err
|
|
}
|
|
|
|
func (d *decode) readUint16() (uint16, error) {
|
|
_, err := io.ReadFull(d.r, d.intBuf[:2])
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return binary.LittleEndian.Uint16(d.intBuf), nil
|
|
}
|
|
|
|
func (d *decode) readUint32() (uint32, error) {
|
|
_, err := io.ReadFull(d.r, d.intBuf[:4])
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return binary.LittleEndian.Uint32(d.intBuf), nil
|
|
}
|
|
|
|
func (d *decode) readUint64() (uint64, error) {
|
|
_, err := io.ReadFull(d.r, d.intBuf)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return binary.LittleEndian.Uint64(d.intBuf), nil
|
|
}
|
|
|
|
func (d *decode) readUint32Big() (uint32, error) {
|
|
_, err := io.ReadFull(d.r, d.intBuf[:4])
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return binary.BigEndian.Uint32(d.intBuf), nil
|
|
}
|
|
|
|
// Doubles are saved as strings prefixed by an unsigned
|
|
// 8 bit integer specifying the length of the representation.
|
|
// This 8 bit integer has special values in order to specify the following
|
|
// conditions:
|
|
// 253: not a number
|
|
// 254: + inf
|
|
// 255: - inf
|
|
func (d *decode) readFloat64() (float64, error) {
|
|
length, err := d.readUint8()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
switch length {
|
|
case 253:
|
|
return math.NaN(), nil
|
|
case 254:
|
|
return math.Inf(0), nil
|
|
case 255:
|
|
return math.Inf(-1), nil
|
|
default:
|
|
floatBytes := make([]byte, length)
|
|
_, err := io.ReadFull(d.r, floatBytes)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
f, err := strconv.ParseFloat(string(floatBytes), 64)
|
|
return f, err
|
|
}
|
|
|
|
panic("not reached")
|
|
}
|
|
|
|
func (d *decode) readLength() (uint32, bool, error) {
|
|
b, err := d.r.ReadByte()
|
|
if err != nil {
|
|
return 0, false, err
|
|
}
|
|
// The first two bits of the first byte are used to indicate the length encoding type
|
|
switch (b & 0xc0) >> 6 {
|
|
case rdb6bitLen:
|
|
// When the first two bits are 00, the next 6 bits are the length.
|
|
return uint32(b & 0x3f), false, nil
|
|
case rdb14bitLen:
|
|
// When the first two bits are 01, the next 14 bits are the length.
|
|
bb, err := d.r.ReadByte()
|
|
if err != nil {
|
|
return 0, false, err
|
|
}
|
|
return (uint32(b&0x3f) << 8) | uint32(bb), false, nil
|
|
case rdbEncVal:
|
|
// When the first two bits are 11, the next object is encoded.
|
|
// The next 6 bits indicate the encoding type.
|
|
return uint32(b & 0x3f), true, nil
|
|
default:
|
|
// When the first two bits are 10, the next 6 bits are discarded.
|
|
// The next 4 bytes are the length.
|
|
length, err := d.readUint32Big()
|
|
return length, false, err
|
|
}
|
|
|
|
panic("not reached")
|
|
}
|
|
|
|
func verifyDump(d []byte) error {
|
|
if len(d) < 10 {
|
|
return fmt.Errorf("rdb: invalid dump length")
|
|
}
|
|
version := binary.LittleEndian.Uint16(d[len(d)-10:])
|
|
if version != uint16(Version) {
|
|
return fmt.Errorf("rdb: invalid version %d, expecting %d", version, Version)
|
|
}
|
|
|
|
if binary.LittleEndian.Uint64(d[len(d)-8:]) != crc64.Digest(d[:len(d)-8]) {
|
|
return fmt.Errorf("rdb: invalid CRC checksum")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func lzfDecompress(in []byte, outlen int) []byte {
|
|
out := make([]byte, outlen)
|
|
for i, o := 0, 0; i < len(in); {
|
|
ctrl := int(in[i])
|
|
i++
|
|
if ctrl < 32 {
|
|
for x := 0; x <= ctrl; x++ {
|
|
out[o] = in[i]
|
|
i++
|
|
o++
|
|
}
|
|
} else {
|
|
length := ctrl >> 5
|
|
if length == 7 {
|
|
length = length + int(in[i])
|
|
i++
|
|
}
|
|
ref := o - ((ctrl & 0x1f) << 8) - int(in[i]) - 1
|
|
i++
|
|
for x := 0; x <= length+1; x++ {
|
|
out[o] = out[ref]
|
|
ref++
|
|
o++
|
|
}
|
|
}
|
|
}
|
|
return out
|
|
}
|