@ -0,0 +1,56 @@ | |||||
package keys | |||||
import ( | |||||
"encoding/binary" | |||||
"errors" | |||||
"hash/crc32" | |||||
) | |||||
// ECC is used for anything that calculates an error-correcting code | |||||
type ECC interface { | |||||
// AddECC calculates an error-correcting code for the input | |||||
// returns an output with the code appended | |||||
AddECC([]byte) []byte | |||||
// CheckECC verifies if the ECC is proper on the input and returns | |||||
// the data with the code removed, or an error | |||||
CheckECC([]byte) ([]byte, error) | |||||
} | |||||
// NoECC is a no-op placeholder, kind of useless... except for tests | |||||
type NoECC struct{} | |||||
var _ ECC = NoECC{} | |||||
func (_ NoECC) AddECC(input []byte) []byte { return input } | |||||
func (_ NoECC) CheckECC(input []byte) ([]byte, error) { return input, nil } | |||||
// CRC32 does the ieee crc32 polynomial check | |||||
type CRC32 struct{} | |||||
var _ ECC = CRC32{} | |||||
func (_ CRC32) AddECC(input []byte) []byte { | |||||
// get crc and convert to some bytes... | |||||
crc := crc32.ChecksumIEEE(input) | |||||
check := make([]byte, 4) | |||||
binary.BigEndian.PutUint32(check, crc) | |||||
// append it to the input | |||||
output := append(input, check...) | |||||
return output | |||||
} | |||||
func (_ CRC32) CheckECC(input []byte) ([]byte, error) { | |||||
if len(input) <= 4 { | |||||
return nil, errors.New("input too short, no checksum present") | |||||
} | |||||
cut := len(input) - 4 | |||||
data, check := input[:cut], input[cut:] | |||||
crc := binary.BigEndian.Uint32(check) | |||||
calc := crc32.ChecksumIEEE(data) | |||||
if crc != calc { | |||||
return nil, errors.New("Checksum does not match") | |||||
} | |||||
return data, nil | |||||
} |
@ -0,0 +1,52 @@ | |||||
package keys | |||||
import ( | |||||
"testing" | |||||
"github.com/stretchr/testify/assert" | |||||
cmn "github.com/tendermint/tmlibs/common" | |||||
) | |||||
// TestECCPasses makes sure that the AddECC/CheckECC methods are symetric | |||||
func TestECCPasses(t *testing.T) { | |||||
assert := assert.New(t) | |||||
checks := []ECC{NoECC{}, CRC32{}} | |||||
for _, check := range checks { | |||||
for i := 0; i < 2000; i++ { | |||||
numBytes := cmn.RandInt()%60 + 1 | |||||
data := cmn.RandBytes(numBytes) | |||||
checked := check.AddECC(data) | |||||
res, err := check.CheckECC(checked) | |||||
if assert.Nil(err, "%v: %+v", check, err) { | |||||
assert.Equal(data, res, "%v", check) | |||||
} | |||||
} | |||||
} | |||||
} | |||||
// TestECCFails makes sure random data will (usually) fail the checksum | |||||
func TestECCFails(t *testing.T) { | |||||
assert := assert.New(t) | |||||
checks := []ECC{CRC32{}} | |||||
attempts := 2000 | |||||
for _, check := range checks { | |||||
failed := 0 | |||||
for i := 0; i < attempts; i++ { | |||||
numBytes := cmn.RandInt()%60 + 1 | |||||
data := cmn.RandBytes(numBytes) | |||||
_, err := check.CheckECC(data) | |||||
if err != nil { | |||||
failed += 1 | |||||
} | |||||
} | |||||
// we allow up to 1 falsely accepted checksums, as there are random matches | |||||
assert.InDelta(attempts, failed, 1, "%v", check) | |||||
} | |||||
} |