From ba0cfb20bcebc10ea431e2956047af183dfe95a9 Mon Sep 17 00:00:00 2001 From: Yawning Angel Date: Thu, 25 Jun 2020 11:30:20 +0000 Subject: [PATCH] go/common/cbor: Add support for generic versioned blobs --- .changelog/3052.internal.md | 1 + go/common/cbor/versioned.go | 51 ++++++++++++++++++++++++++++++++ go/common/cbor/versioned_test.go | 39 ++++++++++++++++++++++++ 3 files changed, 91 insertions(+) create mode 100644 .changelog/3052.internal.md create mode 100644 go/common/cbor/versioned.go create mode 100644 go/common/cbor/versioned_test.go diff --git a/.changelog/3052.internal.md b/.changelog/3052.internal.md new file mode 100644 index 00000000000..45db96f11ef --- /dev/null +++ b/.changelog/3052.internal.md @@ -0,0 +1 @@ +go/common/cbor: Add support for generic versioned blobs diff --git a/go/common/cbor/versioned.go b/go/common/cbor/versioned.go new file mode 100644 index 00000000000..0461af73a73 --- /dev/null +++ b/go/common/cbor/versioned.go @@ -0,0 +1,51 @@ +package cbor + +import ( + "errors" + "math" + + "github.com/fxamacker/cbor/v2" +) + +const invalidVersion = math.MaxUint16 + +var ( + // ErrInvalidVersion is the error returned when a versioned + // serialized blob is either missing, or has an invalid version. + ErrInvalidVersion = errors.New("cbor: missing or invalid version") + + decOptionsVersioned = decOptions + + decModeVersioned cbor.DecMode +) + +// Versioned is a generic versioned serializable data structure. +type Versioned struct { + V uint16 `json:"v"` +} + +// GetVersion returns the version of a versioned serializable data +// structure, if any. +func GetVersion(data []byte) (uint16, error) { + vblob := Versioned{ + V: invalidVersion, + } + if err := decModeVersioned.Unmarshal(data, &vblob); err != nil { + return 0, err + } + if vblob.V == invalidVersion { + return 0, ErrInvalidVersion + } + return vblob.V, nil +} + +func init() { + // Use the untrusted decode options, but ignore unknown fields. + // FIXME: https://github.com/fxamacker/cbor/issues/240 + decOptionsVersioned.ExtraReturnErrors = int(cbor.ExtraDecErrorNone) + + var err error + if decModeVersioned, err = decOptionsVersioned.DecMode(); err != nil { + panic(err) + } +} diff --git a/go/common/cbor/versioned_test.go b/go/common/cbor/versioned_test.go new file mode 100644 index 00000000000..2d0884b26f2 --- /dev/null +++ b/go/common/cbor/versioned_test.go @@ -0,0 +1,39 @@ +package cbor + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestVersioned(t *testing.T) { + require := require.New(t) + + type a struct { + A string + } + + raw := Marshal(&a{ + A: "Open still remaineth the earth for great souls.", + }) + _, err := GetVersion(raw) + require.Equal(ErrInvalidVersion, err, "missing version should error") + + type b struct { + Versioned + a + } + + const testVersion uint16 = 451 + raw = Marshal(&b{ + Versioned: Versioned{ + V: testVersion, + }, + a: a{ + A: "Empty are still many sites for lone ones and twain ones", + }, + }) + version, err := GetVersion(raw) + require.NoError(err, "versioned blobs should deserialize") + require.Equal(testVersion, version, "the version should be correct") +}