Skip to content

Commit

Permalink
Extracted CRC calculations into a separate Table type for easier mult…
Browse files Browse the repository at this point in the history
…ithreaded use
  • Loading branch information
snksoft committed Jun 16, 2018
1 parent c29f141 commit 03404db
Show file tree
Hide file tree
Showing 3 changed files with 178 additions and 56 deletions.
53 changes: 51 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ To install, simply execute:
go get github.com/snksoft/crc
```

or:

```
go get gopkg.in/snksoft/crc.v1
```

## Usage

Using `crc` is easy. Here is an example of calculating a CCITT crc.
Expand Down Expand Up @@ -54,12 +60,55 @@ func main() {
hash.Reset() // Discard crc data accumulated so far
hash.Update([]byte("123456789")) // feed first chunk
hash.Update([]byte("01234567890")) // feed next chunk
xmodemCrc2 := hash.CRC() // gets CRC of whole data ("12345678901234567890")
xmodemCrc2 := hash.CRC() // gets CRC of whole data "12345678901234567890"
fmt.Printf("CRC is 0x%04X\n", xmodemCrc2) // prints "CRC is 0x2C89"
}
```

## New in version 1.1

In this version I have separated actual CRC caclulations and Hash interface implementation. New `Table` type incorporates table based implementation which can be used without creating a `Hash` instance. The main difference is that `Table` instances are essentially immutable once initialized. This greatly simplifies concurrent use as `Table` instances can be safely used in concurrent applications without tricky copying or synchronization. The downside is, however, that feeding data in multiple chunks becomes a bit more verbose (as you essentially maintain intermediate crc in your code and keep feeding it back to subsequent calls). So, you might prefer one or the other depending on situation at hand and personal preferences. You even can ask a `Hash` instance for a `Table` instance it uses internally and then use both in parallel without recalculating the crc table.

Anyway, here is how to use a `Table` directly.

```go
package main

import (
"fmt"
"github.com/snksoft/crc"
)

func main() {
data := []byte("123456789")

// create a Table
crcTable := crc.NewTable(crc.XMODEM)

// Simple calculation all in one go
xmodemCrc := crcTable.CalculateCRC(data)
fmt.Printf("CRC is 0x%04X\n", xmodemCrc) // prints "CRC is 0x31C3"

// You can also reuse same Table for another crc calculation
// or even calculate multiple crc in parallel using same Table
crc1 := crcTable.InitCrc()
crc1 = crcTable.UpdateCrc(crc1, []byte("1234567890")) // feed first chunk to first crc
crc2 := crcTable.InitCrc()
crc2 = crcTable.UpdateCrc(crc2, data) // feed first chunk to second crc
crc1 = crcTable.UpdateCrc(crc1, []byte("1234567890")) // feed second chunk to first crc

// Now finish calcuation for both
crc1 = crcTable.CRC(crc1)
crc2 = crcTable.CRC(crc2)

fmt.Printf("CRC is 0x%04X\n", crc1) // prints "CRC is 0x2C89"
fmt.Printf("CRC is 0x%04X\n", crc2) // prints "CRC is 0x31C3"
}
```


## Notes
Beware that Hash instance is not thread safe. If you want to do parallel CRC calculations, then either use `NewHash()` to create multiple Hash instances or simply make a copy of Hash whehever you need it. Latter option avoids recalculating CRC table, but keep in mind that `NewHash()` returns a pointer, so simple assignement will point to the same instance.
Beware that `Hash` instance is not thread safe. If you want to do parallel CRC calculations (and actually need it to be `Hash`, not `Table`), then either use `NewHash()` to create multiple Hash instances or simply make a copy of Hash whehever you need it. Latter option avoids recalculating CRC table, but keep in mind that `NewHash()` returns a pointer, so simple assignement will point to the same instance.
Use either
```go
hash2 := &crc.Hash{}
Expand Down
166 changes: 115 additions & 51 deletions crc.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,102 @@ func CalculateCRC(crcParams *Parameters, data []byte) uint64 {
return curValue & mask
}

// Hash represents the partial evaluation of a checksum using table-driven
// implementation. It also implements hash.Hash interface.
type Hash struct {
// Table represents the partial evaluation of a checksum using table-driven
// implementation. It is essentially immutable once initialized and thread safe as a result.
type Table struct {
crcParams Parameters
crctable []uint64
curValue uint64
mask uint64
size uint
initValue uint64
}

// NewTable creates and initializes a new Table for the CRC algorithm specified by the crcParams.
func NewTable(crcParams *Parameters) *Table {
ret := &Table{crcParams: *crcParams}
ret.mask = (uint64(1) << crcParams.Width) - 1
ret.crctable = make([]uint64, 256, 256)
ret.initValue = crcParams.Init
if crcParams.ReflectIn {
ret.initValue = reflect(crcParams.Init, crcParams.Width)
}

tmp := make([]byte, 1, 1)
tableParams := *crcParams
tableParams.Init = 0
tableParams.ReflectOut = tableParams.ReflectIn
tableParams.FinalXor = 0
for i := 0; i < 256; i++ {
tmp[0] = byte(i)
ret.crctable[i] = CalculateCRC(&tableParams, tmp)
}
return ret
}

// InitCrc returns a stating value for a new CRC calculation
func (t *Table) InitCrc() uint64 {
return t.initValue
}

// UpdateCrc process supplied bytes and updates current (partial) CRC accordingly.
// It can be called repetitively to process larger data in chunks.
func (t *Table) UpdateCrc(curValue uint64, p []byte) uint64 {
if t.crcParams.ReflectIn {
for _, v := range p {
curValue = t.crctable[(byte(curValue)^v)&0xFF] ^ (curValue >> 8)
}
} else if t.crcParams.Width < 8 {
for _, v := range p {
curValue = t.crctable[((((byte)(curValue<<(8-t.crcParams.Width)))^v)&0xFF)] ^ (curValue << 8)
}
} else {
for _, v := range p {
curValue = t.crctable[((byte(curValue>>(t.crcParams.Width-8))^v)&0xFF)] ^ (curValue << 8)
}
}
return curValue
}

// CRC returns CRC value for the data processed so far.
func (t *Table) CRC(curValue uint64) uint64 {
ret := curValue

if t.crcParams.ReflectOut != t.crcParams.ReflectIn {
ret = reflect(ret, t.crcParams.Width)
}
return (ret ^ t.crcParams.FinalXor) & t.mask
}

// CRC8 is a convenience method to spare end users from explicit type conversion every time this package is used.
// Underneath, it just calls CRC() method.
func (t *Table) CRC8(curValue uint64) uint8 {
return uint8(t.CRC(curValue))
}

// CRC16 is a convenience method to spare end users from explicit type conversion every time this package is used.
// Underneath, it just calls CRC() method.
func (t *Table) CRC16(curValue uint64) uint16 {
return uint16(t.CRC(curValue))
}

// CRC32 is a convenience method to spare end users from explicit type conversion every time this package is used.
// Underneath, it just calls CRC() method.
func (t *Table) CRC32(curValue uint64) uint32 {
return uint32(t.CRC(curValue))
}

// CalculateCRC is a convenience function allowing to calculate CRC in one call.
func (t *Table) CalculateCRC(data []byte) uint64 {
crc := t.InitCrc()
crc = t.UpdateCrc(crc, data)
return t.CRC(crc)
}

// Hash represents the partial evaluation of a checksum using table-driven
// implementation. It also implements hash.Hash interface.
type Hash struct {
table *Table
curValue uint64
size uint
}

// Size returns the number of bytes Sum will return.
Expand All @@ -130,10 +218,7 @@ func (h *Hash) BlockSize() int { return 1 }
// Reset resets the Hash to its initial state.
// See hash.Hash interface.
func (h *Hash) Reset() {
h.curValue = h.crcParams.Init
if h.crcParams.ReflectIn {
h.curValue = reflect(h.crcParams.Init, h.crcParams.Width)
}
h.curValue = h.table.InitCrc()
}

// Sum appends the current hash to b and returns the resulting slice.
Expand All @@ -156,74 +241,53 @@ func (h *Hash) Write(p []byte) (n int, err error) {

// Update updates process supplied bytes and updates current (partial) CRC accordingly.
func (h *Hash) Update(p []byte) {
if h.crcParams.ReflectIn {
for _, v := range p {
h.curValue = h.crctable[(byte(h.curValue)^v)&0xFF] ^ (h.curValue >> 8)
}
} else if h.crcParams.Width < 8 {
for _, v := range p {
h.curValue = h.crctable[((((byte)(h.curValue<<(8-h.crcParams.Width)))^v)&0xFF)] ^ (h.curValue << 8)
}
} else {
for _, v := range p {
h.curValue = h.crctable[((byte(h.curValue>>(h.crcParams.Width-8))^v)&0xFF)] ^ (h.curValue << 8)
}
}
h.curValue = h.table.UpdateCrc(h.curValue, p)
}

// CRC returns current CRC value for the data processed so far.
func (h *Hash) CRC() uint64 {
ret := h.curValue

if h.crcParams.ReflectOut != h.crcParams.ReflectIn {
ret = reflect(ret, h.crcParams.Width)
}
return (ret ^ h.crcParams.FinalXor) & h.mask
return h.table.CRC(h.curValue)
}

// CalculateCRC is a convenience function allowing to calculate CRC in one call.
func (h *Hash) CalculateCRC(data []byte) uint64 {
h.Reset() // just in case
h.Update(data)
return h.CRC()
return h.table.CalculateCRC(data)
}

// NewHashWithTable creates a new Hash instance configured for table driven
// CRC calculation using a Table instance created elsewhere.
func NewHashWithTable(table *Table) *Hash {
ret := &Hash{table: table}
ret.size = (table.crcParams.Width + 7) / 8 // smalest number of bytes enough to store produced crc
ret.Reset()
return ret
}

// NewHash creates a new Hash instance configured for table driven
// CRC calculation according to parameters specified.
func NewHash(crcParams *Parameters) *Hash {
ret := &Hash{crcParams: *crcParams}
ret.mask = (uint64(1) << crcParams.Width) - 1
ret.size = (crcParams.Width + 7) / 8 // smalest number of bytes enough to store produced crc
ret.crctable = make([]uint64, 256, 256)

tmp := make([]byte, 1, 1)
tableParams := *crcParams
tableParams.Init = 0
tableParams.ReflectOut = tableParams.ReflectIn
tableParams.FinalXor = 0
for i := 0; i < 256; i++ {
tmp[0] = byte(i)
ret.crctable[i] = CalculateCRC(&tableParams, tmp)
}
ret.Reset()

return ret
return NewHashWithTable(NewTable(crcParams))
}

// CRC8 is a convenience method to spare end users from explicit type conversion every time this package is used.
// Underneath, it just calls CRC() method.
func (h *Hash) CRC8() uint8 {
return uint8(h.CRC())
return h.table.CRC8(h.curValue)
}

// CRC16 is a convenience method to spare end users from explicit type conversion every time this package is used.
// Underneath, it just calls CRC() method.
func (h *Hash) CRC16() uint16 {
return uint16(h.CRC())
return h.table.CRC16(h.curValue)
}

// CRC32 is a convenience method to spare end users from explicit type conversion every time this package is used.
// Underneath, it just calls CRC() method.
func (h *Hash) CRC32() uint32 {
return uint32(h.CRC())
return h.table.CRC32(h.curValue)
}

// Table used by this Hash under the hood
func (h *Hash) Table() *Table {
return h.table
}
15 changes: 12 additions & 3 deletions crc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ func TestCRCAlgorithms(t *testing.T) {
}
}

// Test Hash's table directly and see there is no difference
table := tableDriven.Table()
calculated = table.CalculateCRC([]byte(data))
if calculated != crc {
t.Errorf("Incorrect CRC 0x%04x calculated for %s (should be 0x%04x)", calculated, data, crc)
}

}

doTest(X25, "123456789", 0x906E)
Expand Down Expand Up @@ -156,9 +163,11 @@ func TestCRCAlgorithms(t *testing.T) {
doTestWithParameters(15, 0x4599, 0x00, false, false, 0x00, 0x2857, longText)
doTestWithParameters(15, 0x6815, 0x00, false, false, 0x0001, 0x2566, "123456789") // CRC-15/MPT1327

doTestWithParameters(21, 0x102899, 0x000000, false, false, 0x000000, 0x0ed841, "123456789") // CRC-21/CAN-FD
doTestWithParameters(24, 0x864cfb, 0xb704ce, false, false, 0x000000, 0x21cf02, "123456789") // CRC-24
doTestWithParameters(24, 0x5d6dcb, 0xfedcba, false, false, 0x000000, 0x7979bd, "123456789") // CRC-24/FLEXRAY-A
doTestWithParameters(21, 0x102899, 0x000000, false, false, 0x000000, 0x0ed841, "123456789") // CRC-21/CAN-FD
doTestWithParameters(24, 0x864cfb, 0xb704ce, false, false, 0x000000, 0x21cf02, "123456789") // CRC-24
doTestWithParameters(24, 0x5d6dcb, 0xfedcba, false, false, 0x000000, 0x7979bd, "123456789") // CRC-24/FLEXRAY-A
doTestWithParameters(24, 0x00065b, 0x555555, true, true, 0x000000, 0xc25a56, "123456789") // "CRC-24/BLE"

doTestWithParameters(31, 0x04c11db7, 0x7fffffff, false, false, 0x7fffffff, 0x0ce9e46c, "123456789") // CRC-31/PHILIPS
}

Expand Down

0 comments on commit 03404db

Please sign in to comment.