From 89e51a3e968dbc425cca44f9d1631a6da5c42194 Mon Sep 17 00:00:00 2001 From: Faye Amacker <33205765+fxamacker@users.noreply.github.com> Date: Wed, 14 Aug 2024 16:08:38 -0500 Subject: [PATCH] Add funcs to check availablility of CBOR tag nums This commit: * Adds IsCBORTagNumberRangeAvailable() * Adds ReservedCBORTagNumberRange() * Reserves CBOR tag numbers [240, 255] for atree internal use IsCBORTagNumberRangeAvailable() checks if specified range of CBOR tag numbers can be used to encode elements managed by atree containers. ReservedCBORTagNumberRange() returns minTagNum and maxTagNum of the range of CBOR tag numbers reserved by atree. Currently, Atree and Cadence uses CBOR tag numbers: * Atree: 246 to 255 * Cadence: 128 to 230 to encode internal Cadence values This commit reserves CBOR tag numbers [240, 255] for atree internal use. Applications must use unreserved CBOR tag numbers to encode elements managed by atree containers. When a new tag number is needed, Atree will use higher tag number first from its reserved range. By contrast, Cadence will use lower tag numbers first from its own (different) reserved range. This allows Atree and Cadence more flexibility in case we need to revisit the allocation of adjacent unused ranges for Atree and Cadence. --- storable.go | 37 ++++++++++++++++++++++++++++- storable_test.go | 60 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 1 deletion(-) diff --git a/storable.go b/storable.go index 59e41ce..bd49ef3 100644 --- a/storable.go +++ b/storable.go @@ -73,9 +73,28 @@ func hasPointer(storable Storable) bool { const ( // WARNING: tag numbers defined in here in github.com/onflow/atree // MUST not overlap with tag numbers used by Cadence internal value encoding. - // As of Oct. 2, 2023, Cadence uses tag numbers from 128 to 224. + // As of Aug. 14, 2024, Cadence uses tag numbers from 128 to 230. // See runtime/interpreter/encode.go at github.com/onflow/cadence. + // Atree reserves CBOR tag numbers [240, 255] for internal use. + // Applications must use non-overlapping CBOR tag numbers to encode + // elements managed by atree containers. + minInternalCBORTagNumber = 240 + maxInternalCBORTagNumber = 255 + + // Reserved CBOR tag numbers for atree internal use. + + // Replace _ when new tag number is needed (use higher tag numbers first). + // Atree will use higher tag numbers first because Cadence will use lower tag numbers first. + // This approach allows more flexibility in case we need to revisit ranges used by Atree and Cadence. + + _ = 240 + _ = 241 + _ = 242 + _ = 243 + _ = 244 + _ = 245 + CBORTagTypeInfoRef = 246 CBORTagInlinedArrayExtraData = 247 @@ -92,6 +111,22 @@ const ( CBORTagSlabID = 255 ) +// IsCBORTagNumberRangeAvailable returns true if the specified range is not reserved for internal use by atree. +// Applications must only use available (unreserved) CBOR tag numbers to encode elements in atree managed containers. +func IsCBORTagNumberRangeAvailable(minTagNum, maxTagNum uint64) (bool, error) { + if minTagNum > maxTagNum { + return false, NewUserError(fmt.Errorf("min CBOR tag number %d must be <= max CBOR tag number %d", minTagNum, maxTagNum)) + } + + return maxTagNum < minInternalCBORTagNumber || minTagNum > maxInternalCBORTagNumber, nil +} + +// ReservedCBORTagNumberRange returns minTagNum and maxTagNum of the range of CBOR tag numbers +// reserved for internal use by atree. +func ReservedCBORTagNumberRange() (minTagNum, maxTagNum uint64) { + return minInternalCBORTagNumber, maxInternalCBORTagNumber +} + type SlabIDStorable SlabID var _ ContainerStorable = SlabIDStorable{} diff --git a/storable_test.go b/storable_test.go index 6072e83..f1db065 100644 --- a/storable_test.go +++ b/storable_test.go @@ -22,8 +22,10 @@ import ( "encoding/binary" "fmt" "math" + "testing" "github.com/fxamacker/cbor/v2" + "github.com/stretchr/testify/require" ) // This file contains value implementations for testing purposes @@ -37,6 +39,64 @@ const ( cborTagHashableMap = 166 ) +func TestIsCBORTagNumberRangeAvailable(t *testing.T) { + t.Run("error", func(t *testing.T) { + _, err := IsCBORTagNumberRangeAvailable(maxInternalCBORTagNumber, minInternalCBORTagNumber) + var userError *UserError + require.ErrorAs(t, err, &userError) + }) + + t.Run("identical", func(t *testing.T) { + available, err := IsCBORTagNumberRangeAvailable(minInternalCBORTagNumber, maxInternalCBORTagNumber) + require.NoError(t, err) + require.False(t, available) + }) + + t.Run("subrange", func(t *testing.T) { + available, err := IsCBORTagNumberRangeAvailable(minInternalCBORTagNumber, maxInternalCBORTagNumber-1) + require.NoError(t, err) + require.False(t, available) + + available, err = IsCBORTagNumberRangeAvailable(minInternalCBORTagNumber+1, maxInternalCBORTagNumber) + require.NoError(t, err) + require.False(t, available) + }) + + t.Run("partial overlap", func(t *testing.T) { + available, err := IsCBORTagNumberRangeAvailable(minInternalCBORTagNumber-1, maxInternalCBORTagNumber-1) + require.NoError(t, err) + require.False(t, available) + + available, err = IsCBORTagNumberRangeAvailable(minInternalCBORTagNumber+1, maxInternalCBORTagNumber+1) + require.NoError(t, err) + require.False(t, available) + }) + + t.Run("non-overlap", func(t *testing.T) { + available, err := IsCBORTagNumberRangeAvailable(minInternalCBORTagNumber-10, minInternalCBORTagNumber-1) + require.NoError(t, err) + require.True(t, available) + + available, err = IsCBORTagNumberRangeAvailable(minInternalCBORTagNumber-1, minInternalCBORTagNumber-1) + require.NoError(t, err) + require.True(t, available) + + available, err = IsCBORTagNumberRangeAvailable(maxInternalCBORTagNumber+1, maxInternalCBORTagNumber+10) + require.NoError(t, err) + require.True(t, available) + + available, err = IsCBORTagNumberRangeAvailable(maxInternalCBORTagNumber+10, maxInternalCBORTagNumber+10) + require.NoError(t, err) + require.True(t, available) + }) +} + +func TestReservedCBORTagNumberRange(t *testing.T) { + minTagNum, maxTagNum := ReservedCBORTagNumberRange() + require.Equal(t, uint64(minInternalCBORTagNumber), minTagNum) + require.Equal(t, uint64(maxInternalCBORTagNumber), maxTagNum) +} + type HashableValue interface { Value HashInput(scratch []byte) ([]byte, error)