-
Notifications
You must be signed in to change notification settings - Fork 672
/
manager.go
160 lines (131 loc) · 4.34 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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
// Copyright (C) 2019-2023, 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/units"
"github.com/ava-labs/avalanchego/utils/wrappers"
)
const (
// default max size, in bytes, of something being marshalled by Marshal()
defaultMaxSize = 256 * units.KiB
// 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 (
ErrUnknownVersion = errors.New("unknown codec version")
errMarshalNil = errors.New("can't marshal nil pointer or interface")
errUnmarshalNil = errors.New("can't unmarshal nil")
errUnmarshalTooBig = errors.New("byte array exceeds maximum length")
errCantPackVersion = errors.New("couldn't pack codec version")
errCantUnpackVersion = errors.New("couldn't unpack codec version")
errDuplicatedVersion = errors.New("duplicated codec version")
)
var _ Manager = (*manager)(nil)
// 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
// Size returns the size, in bytes, of [value] when it's marshaled
// using the codec with the given version.
// RegisterCodec must have been called with that version.
// If [value] is nil, returns [errMarshalNil]
Size(version uint16, value interface{}) (int, error)
// 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
}
func (m *manager) Size(version uint16, value interface{}) (int, error) {
if value == nil {
return 0, errMarshalNil // can't marshal nil
}
m.lock.RLock()
c, exists := m.codecs[version]
m.lock.RUnlock()
if !exists {
return 0, ErrUnknownVersion
}
res, err := c.Size(value)
// Add [wrappers.ShortLen] for the codec version
return wrappers.ShortLen + res, err
}
// 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
}
if byteLen := len(bytes); byteLen > m.maxSize {
return 0, fmt.Errorf("%w: %d > %d", errUnmarshalTooBig, byteLen, m.maxSize)
}
p := wrappers.Packer{
Bytes: bytes,
}
version := p.UnpackShort()
if p.Errored() { // Make sure the codec version is correct
return 0, errCantUnpackVersion
}
m.lock.RLock()
c, exists := m.codecs[version]
m.lock.RUnlock()
if !exists {
return version, ErrUnknownVersion
}
return version, c.Unmarshal(p.Bytes[p.Offset:], dest)
}