-
Notifications
You must be signed in to change notification settings - Fork 0
/
model.go
192 lines (167 loc) · 5.01 KB
/
model.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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
package crcutil
// Model defines how a concrete CRC calculation should be
// performed, based on a polynomial, and parameters.
// To calculate a crc value, an instance needs to be created
// using New, followed by calls to methods Write or Update,
// and if appropriate, Reset.
//
// A Model may also be used to create a [hash.Hash] using
// packages [github.com/knieriem/hash/crc8] or
// packages [github.com/knieriem/hash/crc16].
type Model[T Word] struct {
Poly *Poly[T]
// Table may be assigned an alternative table to be used for
// calculation. On default, MakeTable derives a table from Poly.
Table []T
// Initial is the start value to be used for crc calculation.
// If left empty, zero will be used.
Initial T
// InitialInvert may be set to true, if the initial value shall
// be inverted. If Initial is left empty, setting InitialInvert
// has the effect of an initial value with all bits set.
InitialInvert bool
// FinalInvert may be set to true, if a crc value shall
// be inverted before being returned as checksum.
// Alternatively, FinalXOR could be set to a value with all bits set
// for the same effect.
FinalInvert bool
// FinalXOR may contain a value to be XORed into the crc value
// before returning the checksum. In most cases leaving FinalXOR
// empty, and setting FinalInvert instead will suffice, as cases
// where the final step isn't either a no-op or an inversion,
// but an XOR-operation with a specific value seem rare.
FinalXOR T
}
// MakeTable returns the lookup table stored in the Table field.
// If this field is nil, a table generated by Poly.MakeTable() will
// be returned.
func (m *Model[T]) MakeTable() []T {
if m.Table != nil {
return m.Table
}
return m.Poly.MakeTable()
}
func (m *Model[T]) initVal() T {
crc := m.Initial
if m.InitialInvert {
crc = ^crc & m.Poly.mask()
}
return crc
}
func (m *Model[T]) finalize(crc T) T {
if m.FinalInvert {
return ^crc & m.Poly.mask()
}
if x := m.FinalXOR; x != 0 {
return crc ^ x
}
return crc
}
// Checksum returns the CRC checksum calculated
// over the bytes in the data slice.
func (m *Model[T]) Checksum(data []byte) T {
inst := m.New()
inst.Update(data)
return inst.Sum()
}
// Inst is an instance of a Model;
// it contains the current state of the crc calculation.
// current state of
type Inst[T Word] struct {
crc T
tab []T
model *Model[T]
impl Impl[T]
conf *instConf
adjustCRC func(crc T) T
}
// NewInst returns a new instance of the Model.
func (m *Model[T]) New(opts ...InstOption) *Inst[T] {
var conf instConf
for _, o := range opts {
o(&conf)
}
tab := m.Table
if tab == nil {
tab = m.Poly.MakeTable(conf.tabOpts()...)
}
adjustCRC := func(crc T) T {
return crc
}
if conf.compSwapInputNibbles {
adjustCRC = func(crc T) T {
return swapNibbles(crc)
}
}
return &Inst[T]{
crc: adjustCRC(m.initVal()),
tab: tab,
model: m,
impl: m.Poly.Impl(),
conf: &conf,
adjustCRC: adjustCRC,
}
}
type InstOption func(*instConf)
type instConf struct {
appendSumSkipFinalXOR bool
compSwapInputNibbles bool
}
func (c *instConf) tabOpts() []TableOption {
var opts []TableOption
if c.compSwapInputNibbles {
opts = append(opts, withSwappedInputNibbles())
}
return opts
}
// AppendSumSkipFinalXOR ensures that when calling
// AppendSum the final inversion or xor-ing will be skipped.
func AppendSumSkipFinalXOR() InstOption {
return func(c *instConf) {
c.appendSumSkipFinalXOR = true
}
}
// WithSwappedInputNibbles ensures that bytes are
// processed with upper and lower nibbles swapped.
// This option is useful for 4-bit CRCs, when an
// LSBit-first implementation is used, and the input
// bytes shall be processed with high nibble first.
func WithSwappedInputNibbles() InstOption {
return func(c *instConf) {
c.compSwapInputNibbles = true
}
}
// Reset sets the instance back to its initial state.
func (inst *Inst[T]) Reset() {
inst.crc = inst.adjustCRC(inst.model.initVal())
}
// Write implements an io.Writer to add bytes to the crc.
func (inst *Inst[T]) Write(p []byte) (n int, err error) {
inst.crc = inst.impl.Update(inst.crc, inst.tab, p)
return len(p), nil
}
// Update adds bytes in p to the crc
func (inst *Inst[T]) Update(p []byte) {
inst.crc = inst.impl.Update(inst.crc, inst.tab, p)
}
// Sum returns the crc checksum; it does not change the current state.
func (inst *Inst[T]) Sum() T {
crc := inst.adjustCRC(inst.crc)
return inst.model.finalize(crc)
}
// AppendSum appends the crc checksum to the provided slice and returns
// the resulting slice. If AppendSumXORed is set, it will apply,
// if specified, the final inversion or final xor value before appending the checksum,
// otherwise this step will be skipped.
// Calling AppendSum does not change the instance's current state.
func (inst *Inst[T]) AppendSum(in []byte) []byte {
crc := inst.adjustCRC(inst.crc)
if !inst.conf.appendSumSkipFinalXOR {
crc = inst.model.finalize(crc)
}
return inst.impl.Append(in, crc)
}
// Table exposes the lookup table used by the instance.
func (inst *Inst[T]) Table() []T {
return inst.tab
}