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)