@ -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) | |||
} | |||
} |