-
Notifications
You must be signed in to change notification settings - Fork 672
/
manager.go
143 lines (118 loc) · 3.8 KB
/
manager.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package codec
import (
"errors"
"fmt"
"sync"
"github.com/ava-labs/avalanchego/utils/wrappers"
)
const (
// default max size, in bytes, of something being marshalled by Marshal()
defaultMaxSize = 1 << 18
// initial capacity of byte slice that values are marshaled into.
// Larger value --> need less memory allocations but possibly have allocated but unused memory
// Smaller value --> need more memory allocations but more efficient use of allocated memory
initialSliceCap = 128
)
var (
errMarshalNil = errors.New("can't marshal nil pointer or interface")
errUnmarshalNil = errors.New("can't unmarshal nil")
errCantPackVersion = errors.New("couldn't pack codec version")
errCantUnpackVersion = errors.New("couldn't unpack codec version")
errUnknownVersion = errors.New("unknown codec version")
errDuplicatedVersion = errors.New("duplicated codec version")
)
// Manager describes the functionality for managing codec versions.
type Manager interface {
// Associate the given codec with the given version ID
RegisterCodec(version uint16, codec Codec) error
// Define the maximum size, in bytes, of something serialized/deserialized
// by this codec manager
SetMaxSize(int)
// Marshal the given value using the codec with the given version.
// RegisterCodec must have been called with that version.
Marshal(version uint16, source interface{}) (destination []byte, err error)
// Unmarshal the given bytes into the given destination. [destination] must
// be a pointer or an interface. Returns the version of the codec that
// produces the given bytes.
Unmarshal(source []byte, destination interface{}) (version uint16, err error)
}
// NewManager returns a new codec manager.
func NewManager(maxSize int) Manager {
return &manager{
maxSize: maxSize,
codecs: map[uint16]Codec{},
}
}
// NewDefaultManager returns a new codec manager.
func NewDefaultManager() Manager { return NewManager(defaultMaxSize) }
type manager struct {
lock sync.RWMutex
maxSize int
codecs map[uint16]Codec
}
// RegisterCodec is used to register a new codec version that can be used to
// (un)marshal with.
func (m *manager) RegisterCodec(version uint16, codec Codec) error {
m.lock.Lock()
defer m.lock.Unlock()
if _, exists := m.codecs[version]; exists {
return errDuplicatedVersion
}
m.codecs[version] = codec
return nil
}
// SetMaxSize of bytes allowed
func (m *manager) SetMaxSize(size int) {
m.lock.Lock()
m.maxSize = size
m.lock.Unlock()
}
// To marshal an interface, [value] must be a pointer to the interface.
func (m *manager) Marshal(version uint16, value interface{}) ([]byte, error) {
if value == nil {
return nil, errMarshalNil // can't marshal nil
}
m.lock.RLock()
c, exists := m.codecs[version]
m.lock.RUnlock()
if !exists {
return nil, errUnknownVersion
}
p := wrappers.Packer{
MaxSize: m.maxSize,
Bytes: make([]byte, 0, initialSliceCap),
}
p.PackShort(version)
if p.Errored() {
return nil, errCantPackVersion // Should never happen
}
return p.Bytes, c.MarshalInto(value, &p)
}
// Unmarshal unmarshals [bytes] into [dest], where [dest] must be a pointer or
// interface.
func (m *manager) Unmarshal(bytes []byte, dest interface{}) (uint16, error) {
if dest == nil {
return 0, errUnmarshalNil
}
m.lock.RLock()
if len(bytes) > m.maxSize {
m.lock.RUnlock()
return 0, fmt.Errorf("byte array exceeds maximum length, %d", m.maxSize)
}
p := wrappers.Packer{
Bytes: bytes,
}
version := p.UnpackShort()
if p.Errored() { // Make sure the codec version is correct
m.lock.RUnlock()
return 0, errCantUnpackVersion
}
c, exists := m.codecs[version]
m.lock.RUnlock()
if !exists {
return version, errUnknownVersion
}
return version, c.Unmarshal(p.Bytes[p.Offset:], dest)
}