• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

HDT3213 / rdb / 20613456442

30 Dec 2025 03:37PM UTC coverage: 64.632% (+0.2%) from 64.422%
20613456442

push

github

HDT3213
support opCodeFunction 245

19 of 23 new or added lines in 2 files covered. (82.61%)

15 existing lines in 1 file now uncovered.

1842 of 2850 relevant lines covered (64.63%)

0.71 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

68.78
/core/decoder.go
1
// Package core is RDB core core
2
package core
3

4
import (
5
        "bufio"
6
        "bytes"
7
        "encoding/binary"
8
        "errors"
9
        "fmt"
10
        "io"
11
        "strconv"
12
        "time"
13

14
        "github.com/hdt3213/rdb/memprofiler"
15
        "github.com/hdt3213/rdb/model"
16
)
17

18
// Decoder is an instance of rdb parsing process
19
type Decoder struct {
20
        input     *bufio.Reader
21
        readCount int
22
        buffer    []byte
23

24
        withSpecialOpCode bool
25
        withSpecialTypes  map[string]ModuleTypeHandleFunc
26
}
27

28
// NewDecoder creates a new RDB decoder
29
func NewDecoder(reader io.Reader) *Decoder {
1✔
30
        parser := new(Decoder)
1✔
31
        parser.input = bufio.NewReader(reader)
1✔
32
        parser.buffer = make([]byte, 8)
1✔
33
        parser.withSpecialTypes = make(map[string]ModuleTypeHandleFunc)
1✔
34
        return parser
1✔
35
}
1✔
36

37
// WithSpecialOpCode enables returning model.AuxObject to callback
38
func (dec *Decoder) WithSpecialOpCode() *Decoder {
1✔
39
        dec.withSpecialOpCode = true
1✔
40
        return dec
1✔
41
}
1✔
42

43
// WithSpecialType enables returning redis module data structure to callback
44
func (dec *Decoder) WithSpecialType(moduleType string, f ModuleTypeHandleFunc) *Decoder {
1✔
45
        dec.withSpecialTypes[moduleType] = f
1✔
46
        return dec
1✔
47
}
1✔
48

49
var magicNumber = []byte("REDIS")
50

51
const (
52
        minVersion = 1
53
        maxVersion = 12
54
)
55

56
const (
57
        opCodeFunction     = 245
58
        opCodeModuleAux    = 247 /* Module auxiliary data. */
59
        opCodeIdle         = 248 /* LRU idle time. */
60
        opCodeFreq         = 249 /* LFU frequency. */
61
        opCodeAux          = 250 /* RDB aux field. */
62
        opCodeResizeDB     = 251 /* Hash table resize hint. */
63
        opCodeExpireTimeMs = 252 /* Expire time in milliseconds. */
64
        opCodeExpireTime   = 253 /* Old expire time in seconds. */
65
        opCodeSelectDB     = 254 /* DB number of the following keys. */
66
        opCodeEOF          = 255
67
)
68

69
const (
70
        typeString = iota
71
        typeList
72
        typeSet
73
        typeZset
74
        typeHash
75
        typeZset2 /* ZSET version 2 with doubles stored in binary. */
76
        typeModule
77
        typeModule2 // Module value parser should be registered with Decoder.WithSpecialType
78
        _
79
        typeHashZipMap
80
        typeListZipList
81
        typeSetIntSet
82
        typeZsetZipList
83
        typeHashZipList
84
        typeListQuickList
85
        typeStreamListPacks
86
        typeHashListPack
87
        typeZsetListPack
88
        typeListQuickList2
89
        typeStreamListPacks2
90
        typeSetListPack
91
        typeStreamListPacks3
92
        typeHashWithHfeRc         // rdb 12 (only redis 7.4 rc)
93
        typeHashListPackWithHfeRc // rdb 12 (only redis 7.4 rc)
94
        typeHashWithHfe           // since rdb 12 (redis 7.4)
95
        typeHashListPackWithHfe   // since rdb 12 (redis 7.4)
96
)
97

98
const (
99
        EB_EXPIRE_TIME_MAX     int64 = 0x0000FFFFFFFFFFFF
100
        EB_EXPIRE_TIME_INVALID int64 = EB_EXPIRE_TIME_MAX + 1
101
        HFE_MAX_ABS_TIME_MSEC  int64 = EB_EXPIRE_TIME_MAX >> 2
102
)
103

104
var encodingMap = map[int]string{
105
        typeString:                model.StringEncoding,
106
        typeList:                  model.ListEncoding,
107
        typeSet:                   model.SetEncoding,
108
        typeZset:                  model.ZSetEncoding,
109
        typeHash:                  model.HashEncoding,
110
        typeZset2:                 model.ZSet2Encoding,
111
        typeHashZipMap:            model.ZipMapEncoding,
112
        typeListZipList:           model.ZipListEncoding,
113
        typeSetIntSet:             model.IntSetEncoding,
114
        typeZsetZipList:           model.ZipListEncoding,
115
        typeHashZipList:           model.ZipListEncoding,
116
        typeListQuickList:         model.QuickListEncoding,
117
        typeStreamListPacks:       model.ListPackEncoding,
118
        typeStreamListPacks2:      model.ListPackEncoding,
119
        typeHashListPack:          model.ListPackEncoding,
120
        typeZsetListPack:          model.ListPackEncoding,
121
        typeListQuickList2:        model.QuickList2Encoding,
122
        typeSetListPack:           model.ListPackEncoding,
123
        typeHashWithHfeRc:         model.HashExEncoding,
124
        typeHashListPackWithHfeRc: model.ListPackExEncoding,
125
        typeHashWithHfe:           model.HashExEncoding,
126
        typeHashListPackWithHfe:   model.ListPackExEncoding,
127
}
128

129
// checkHeader checks whether input has valid RDB file header
130
func (dec *Decoder) checkHeader() error {
1✔
131
        header := make([]byte, 9)
1✔
132
        err := dec.readFull(header)
1✔
133
        if err == io.EOF {
1✔
134
                return errors.New("empty file")
×
135
        }
×
136
        if err != nil {
1✔
137
                return fmt.Errorf("io error: %v", err)
×
138
        }
×
139
        if !bytes.Equal(header[0:5], magicNumber) {
1✔
140
                return errors.New("file is not a RDB file")
×
141
        }
×
142
        version, err := strconv.Atoi(string(header[5:]))
1✔
143
        if err != nil {
1✔
144
                return fmt.Errorf("%s is not valid version number", string(header[5:]))
×
145
        }
×
146
        if version < minVersion || version > maxVersion {
1✔
147
                return fmt.Errorf("cannot parse version: %d", version)
×
148
        }
×
149
        return nil
1✔
150
}
151

152
func (dec *Decoder) readObject(flag byte, base *model.BaseObject) (model.RedisObject, error) {
1✔
153
        base.Encoding = encodingMap[int(flag)]
1✔
154
        switch flag {
1✔
155
        case typeString:
1✔
156
                bs, err := dec.readString()
1✔
157
                if err != nil {
1✔
158
                        return nil, err
×
159
                }
×
160
                return &model.StringObject{
1✔
161
                        BaseObject: base,
1✔
162
                        Value:      bs,
1✔
163
                }, nil
1✔
164
        case typeList:
1✔
165
                list, err := dec.readList()
1✔
166
                if err != nil {
1✔
167
                        return nil, err
×
168
                }
×
169
                return &model.ListObject{
1✔
170
                        BaseObject: base,
1✔
171
                        Values:     list,
1✔
172
                }, nil
1✔
173
        case typeSet:
1✔
174
                set, err := dec.readSet()
1✔
175
                if err != nil {
1✔
176
                        return nil, err
×
177
                }
×
178
                return &model.SetObject{
1✔
179
                        BaseObject: base,
1✔
180
                        Members:    set,
1✔
181
                }, nil
1✔
182
        case typeSetIntSet:
1✔
183
                set, extra, err := dec.readIntSet()
1✔
184
                if err != nil {
1✔
185
                        return nil, err
×
186
                }
×
187
                base.Extra = extra
1✔
188
                return &model.SetObject{
1✔
189
                        BaseObject: base,
1✔
190
                        Members:    set,
1✔
191
                }, nil
1✔
192
        case typeHash:
1✔
193
                hash, err := dec.readHashMap()
1✔
194
                if err != nil {
1✔
195
                        return nil, err
×
196
                }
×
197
                return &model.HashObject{
1✔
198
                        BaseObject: base,
1✔
199
                        Hash:       hash,
1✔
200
                }, nil
1✔
201
        case typeListZipList:
1✔
202
                list, err := dec.readZipList()
1✔
203
                if err != nil {
1✔
204
                        return nil, err
×
205
                }
×
206
                return &model.ListObject{
1✔
207
                        BaseObject: base,
1✔
208
                        Values:     list,
1✔
209
                }, nil
1✔
210
        case typeListQuickList:
1✔
211
                list, extra, err := dec.readQuickList()
1✔
212
                if err != nil {
1✔
213
                        return nil, err
×
214
                }
×
215
                base.Extra = extra
1✔
216
                return &model.ListObject{
1✔
217
                        BaseObject: base,
1✔
218
                        Values:     list,
1✔
219
                }, nil
1✔
220
        case typeListQuickList2:
1✔
221
                list, extra, err := dec.readQuickList2()
1✔
222
                if err != nil {
1✔
223
                        return nil, err
×
224
                }
×
225
                base.Extra = extra
1✔
226
                return &model.ListObject{
1✔
227
                        BaseObject: base,
1✔
228
                        Values:     list,
1✔
229
                }, nil
1✔
230
        case typeHashZipMap:
1✔
231
                m, err := dec.readZipMapHash()
1✔
232
                if err != nil {
1✔
233
                        return nil, err
×
234
                }
×
235
                return &model.HashObject{
1✔
236
                        BaseObject: base,
1✔
237
                        Hash:       m,
1✔
238
                }, nil
1✔
239
        case typeHashZipList:
1✔
240
                m, extra, err := dec.readZipListHash()
1✔
241
                if err != nil {
1✔
242
                        return nil, err
×
243
                }
×
244
                base.Extra = extra
1✔
245
                return &model.HashObject{
1✔
246
                        BaseObject: base,
1✔
247
                        Hash:       m,
1✔
248
                }, nil
1✔
249
        case typeHashListPack:
1✔
250
                m, extra, err := dec.readListPackHash()
1✔
251
                if err != nil {
1✔
252
                        return nil, err
×
253
                }
×
254
                base.Extra = extra
1✔
255
                return &model.HashObject{
1✔
256
                        BaseObject: base,
1✔
257
                        Hash:       m,
1✔
258
                }, nil
1✔
259
        case typeZset:
1✔
260
                entries, err := dec.readZSet(false)
1✔
261
                if err != nil {
1✔
262
                        return nil, err
×
263
                }
×
264
                return &model.ZSetObject{
1✔
265
                        BaseObject: base,
1✔
266
                        Entries:    entries,
1✔
267
                }, nil
1✔
268
        case typeZset2:
1✔
269
                entries, err := dec.readZSet(true)
1✔
270
                if err != nil {
1✔
271
                        return nil, err
×
272
                }
×
273
                return &model.ZSetObject{
1✔
274
                        BaseObject: base,
1✔
275
                        Entries:    entries,
1✔
276
                }, nil
1✔
277
        case typeZsetZipList:
1✔
278
                entries, extra, err := dec.readZipListZSet()
1✔
279
                if err != nil {
1✔
280
                        return nil, err
×
281
                }
×
282
                base.Extra = extra
1✔
283
                return &model.ZSetObject{
1✔
284
                        BaseObject: base,
1✔
285
                        Entries:    entries,
1✔
286
                }, nil
1✔
287
        case typeZsetListPack:
1✔
288
                entries, extra, err := dec.readListPackZSet()
1✔
289
                if err != nil {
1✔
290
                        return nil, err
×
291
                }
×
292
                base.Extra = extra
1✔
293
                return &model.ZSetObject{
1✔
294
                        BaseObject: base,
1✔
295
                        Entries:    entries,
1✔
296
                }, nil
1✔
297
        case typeStreamListPacks, typeStreamListPacks2, typeStreamListPacks3:
1✔
298
                var version uint = 1
1✔
299
                if flag == typeStreamListPacks2 {
2✔
300
                        version = 2
1✔
301
                } else if flag == typeStreamListPacks3 {
3✔
302
                        version = 3
1✔
303
                }
1✔
304
                stream, err := dec.readStreamListPacks(version)
1✔
305
                if err != nil {
1✔
306
                        return nil, err
×
307
                }
×
308
                stream.BaseObject = base
1✔
309
                return stream, nil
1✔
310
        case typeModule2:
1✔
311
                moduleType, val, err := dec.readModuleType()
1✔
312
                if err != nil {
1✔
313
                        return nil, err
×
314
                }
×
315
                return &model.ModuleTypeObject{
1✔
316
                        BaseObject: base,
1✔
317
                        ModuleType: moduleType,
1✔
318
                        Value:      val,
1✔
319
                }, nil
1✔
320
        case typeSetListPack:
1✔
321
                set, extra, err := dec.readListPackSet()
1✔
322
                if err != nil {
1✔
323
                        return nil, err
×
324
                }
×
325
                base.Extra = extra
1✔
326
                return &model.SetObject{
1✔
327
                        BaseObject: base,
1✔
328
                        Members:    set,
1✔
329
                }, nil
1✔
330
        case typeHashWithHfe, typeHashWithHfeRc:
×
331
                hash, expire, err := dec.readHashMapEx(func() bool { return flag == typeHashWithHfeRc }())
×
332
                if err != nil {
×
333
                        return nil, err
×
334
                }
×
335
                return &model.HashObject{
×
336
                        BaseObject:       base,
×
337
                        Hash:             hash,
×
338
                        FieldExpirations: expire,
×
339
                }, nil
×
340
        case typeHashListPackWithHfe, typeHashListPackWithHfeRc:
×
341
                m, e, extra, err := dec.readListPackHashEx(func() bool { return flag == typeHashListPackWithHfeRc }())
×
342
                if err != nil {
×
343
                        return nil, err
×
344
                }
×
345
                base.Extra = extra
×
346
                return &model.HashObject{
×
347
                        BaseObject:       base,
×
348
                        Hash:             m,
×
349
                        FieldExpirations: e,
×
350
                }, nil
×
351
        }
352
        return nil, fmt.Errorf("unknown type flag: %b", flag)
×
353
}
354

355
func (dec *Decoder) parse(cb func(object model.RedisObject) bool) error {
1✔
356
        var dbIndex int
1✔
357
        var expireMs int64
1✔
358
        for {
2✔
359
                b, err := dec.readByte()
1✔
360
                if err != nil {
1✔
361
                        return err
×
362
                }
×
363
                if b == opCodeEOF {
2✔
364
                        break
1✔
365
                } else if b == opCodeSelectDB {
2✔
366
                        dbIndex64, _, err := dec.readLength()
1✔
367
                        if err != nil {
1✔
368
                                return err
×
369
                        }
×
370
                        dbIndex = int(dbIndex64)
1✔
371
                        continue
1✔
372
                } else if b == opCodeExpireTime {
1✔
373
                        err = dec.readFull(dec.buffer[:4])
×
374
                        if err != nil {
×
375
                                return err
×
376
                        }
×
377
                        expireMs = int64(binary.LittleEndian.Uint32(dec.buffer)) * 1000
×
378
                        continue
×
379
                } else if b == opCodeExpireTimeMs {
2✔
380
                        err = dec.readFull(dec.buffer)
1✔
381
                        if err != nil {
1✔
382
                                return err
×
383
                        }
×
384
                        expireMs = int64(binary.LittleEndian.Uint64(dec.buffer))
1✔
385
                        continue
1✔
386
                } else if b == opCodeResizeDB {
2✔
387
                        keyCount, _, err := dec.readLength()
1✔
388
                        if err != nil {
1✔
389
                                return err
×
390
                        }
×
391
                        ttlCount, _, err := dec.readLength()
1✔
392
                        if err != nil {
1✔
393
                                err = errors.New("Parse Aux value failed: " + err.Error())
×
394
                                break
×
395
                        }
396
                        if dec.withSpecialOpCode {
2✔
397
                                obj := &model.DBSizeObject{
1✔
398
                                        BaseObject: &model.BaseObject{},
1✔
399
                                }
1✔
400
                                obj.DB = dbIndex
1✔
401
                                obj.KeyCount = keyCount
1✔
402
                                obj.TTLCount = ttlCount
1✔
403
                                tbc := cb(obj)
1✔
404
                                if !tbc {
1✔
405
                                        break
×
406
                                }
407
                        }
408
                        continue
1✔
409
                } else if b == opCodeAux {
2✔
410
                        key, err := dec.readString()
1✔
411
                        if err != nil {
1✔
412
                                return err
×
413
                        }
×
414
                        value, err := dec.readString()
1✔
415
                        if err != nil {
1✔
416
                                err = errors.New("Parse Aux value failed: " + err.Error())
×
417
                                break
×
418
                        }
419
                        if dec.withSpecialOpCode {
2✔
420
                                obj := &model.AuxObject{
1✔
421
                                        BaseObject: &model.BaseObject{},
1✔
422
                                }
1✔
423
                                obj.Type = model.AuxType
1✔
424
                                obj.Key = unsafeBytes2Str(key)
1✔
425
                                obj.Value = unsafeBytes2Str(value)
1✔
426
                                tbc := cb(obj)
1✔
427
                                if !tbc {
1✔
428
                                        break
×
429
                                }
430
                        }
431
                        continue
1✔
432
                } else if b == opCodeFreq {
1✔
433
                        _, err = dec.readByte()
×
434
                        if err != nil {
×
435
                                return err
×
436
                        }
×
437
                        continue
×
438
                } else if b == opCodeIdle {
1✔
439
                        _, _, err = dec.readLength()
×
440
                        if err != nil {
×
441
                                return err
×
442
                        }
×
443
                        continue
×
444
                } else if b == opCodeModuleAux {
1✔
445
                        _, _, err = dec.readModuleType()
×
446
                        if err != nil {
×
447
                                return err
×
448
                        }
×
449
                        continue
×
450
                } else if b == opCodeFunction {
2✔
451
                        functionsLua, err := dec.readString()
1✔
452
                        if err != nil {
1✔
NEW
453
                                return err
×
NEW
454
                        }
×
455
                        if dec.withSpecialOpCode {
2✔
456
                                obj := &model.FunctionsObject{
1✔
457
                                        BaseObject: &model.BaseObject{},
1✔
458
                                }
1✔
459
                                obj.Key = "functions"
1✔
460
                                obj.Type = model.FunctionsType
1✔
461
                                obj.Encoding = "functions"
1✔
462
                                obj.FunctionsLua = unsafeBytes2Str(functionsLua)
1✔
463
                                tbc := cb(obj)
1✔
464
                                if !tbc {
2✔
465
                                        break
1✔
466
                                }
467
                        }
468
                        continue
1✔
469
                }
470
                key, err := dec.readString()
1✔
471
                if err != nil {
1✔
472
                        return err
×
473
                }
×
474
                base := &model.BaseObject{
1✔
475
                        DB:  dbIndex,
1✔
476
                        Key: unsafeBytes2Str(key),
1✔
477
                }
1✔
478
                if expireMs > 0 {
2✔
479
                        expiration := time.Unix(0, expireMs*int64(time.Millisecond))
1✔
480
                        base.Expiration = &expiration
1✔
481
                        expireMs = 0 // reset expire ms
1✔
482
                }
1✔
483
                obj, err := dec.readObject(b, base)
1✔
484
                if err != nil {
1✔
485
                        return err
×
486
                }
×
487
                base.Size = memprofiler.SizeOfObject(obj)
1✔
488
                base.Type = obj.GetType()
1✔
489
                tbc := cb(obj)
1✔
490
                if !tbc {
2✔
491
                        break
1✔
492
                }
493
        }
494
        // read crc64 at the end
495
        _ = dec.readFull(dec.buffer)
1✔
496
        return nil
1✔
497
}
498

499
// Parse parses rdb and callback
500
// cb returns true to continue, returns false to stop the iteration
501
func (dec *Decoder) Parse(cb func(object model.RedisObject) bool) (err error) {
1✔
502
        defer func() {
2✔
503
                if err2 := recover(); err2 != nil {
1✔
504
                        err = fmt.Errorf("panic: %v", err2)
×
505
                }
×
506
        }()
507
        err = dec.checkHeader()
1✔
508
        if err != nil {
1✔
509
                return err
×
510
        }
×
511
        return dec.parse(cb)
1✔
512
}
513

514
func (dec *Decoder) GetReadCount() int {
×
515
        return dec.readCount
×
516
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc