From 03404db21ad4e7182edf4843b51f6252799f7140 Mon Sep 17 00:00:00 2001 From: Sergiy Sevastyanov Date: Sun, 17 Jun 2018 09:45:49 +1200 Subject: [PATCH] Extracted CRC calculations into a separate Table type for easier multithreaded use --- README.md | 53 ++++++++++++++++- crc.go | 166 ++++++++++++++++++++++++++++++++++++---------------- crc_test.go | 15 ++++- 3 files changed, 178 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index 06f4791..b3386f8 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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{} diff --git a/crc.go b/crc.go index f1a4f46..5771351 100644 --- a/crc.go +++ b/crc.go @@ -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. @@ -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. @@ -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 } diff --git a/crc_test.go b/crc_test.go index fb7350d..aa054d4 100644 --- a/crc_test.go +++ b/crc_test.go @@ -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) @@ -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 }