From 421114a17a380b7687b4e31d63f35ebf56c9eff2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20=C5=9Awi=C4=99cki?= Date: Fri, 31 Oct 2025 14:01:20 +0100 Subject: [PATCH] Add blob-related data wrappers - Name - identification of the specific blob instance - Type - blob type, associated with blob's name - Key - encryption keys for encrypting and decryption blob's data - AuthInfo - data allowing blob update after it's created (for dynamic blobs) --- .golangci.yml | 7 ++ .vscode/settings.json | 4 +- README.md | 10 ++- base58/base58.go | 104 +++++++++++++++++++++++++ base58/base58_encode_decode.json | 23 ++++++ base58/base58_test.go | 128 +++++++++++++++++++++++++++++++ blob/auth_info.go | 32 ++++++++ blob/auth_info_test.go | 31 ++++++++ blob/keys.go | 36 +++++++++ blob/keys_test.go | 39 ++++++++++ blob/name.go | 102 ++++++++++++++++++++++++ blob/name_test.go | 73 ++++++++++++++++++ blob/type.go | 29 +++++++ blob/type_test.go | 29 +++++++ 14 files changed, 644 insertions(+), 3 deletions(-) create mode 100644 base58/base58.go create mode 100644 base58/base58_encode_decode.json create mode 100644 base58/base58_test.go create mode 100644 blob/auth_info.go create mode 100644 blob/auth_info_test.go create mode 100644 blob/keys.go create mode 100644 blob/keys_test.go create mode 100644 blob/name.go create mode 100644 blob/name_test.go create mode 100644 blob/type.go create mode 100644 blob/type_test.go diff --git a/.golangci.yml b/.golangci.yml index 5a86215..19075fc 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -33,8 +33,15 @@ linters: allow: - golang.org/x/exp/constraints$ - github.com/cinode/go-common/ + - bytes$ + - crypto/subtle$ + - crypto/sha256$ + - embed$ - errors$ + - encoding/hex$ + - encoding/json$ - fmt$ + - math/big$ - reflect$ - regexp$ - strings$ diff --git a/.vscode/settings.json b/.vscode/settings.json index 0d26dce..656e7d9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,8 @@ { "go.coverOnSave": true, "cSpell.words": [ - "cinode" + "cinode", + "cutl", + "picotestify" ] } \ No newline at end of file diff --git a/README.md b/README.md index f355f03..6dd90c8 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,13 @@ It has the following goals over the original testify one: The interface of picotestify is a subset of the original testify one and thus switching to the original testify library should be easily achievable through go.mod's rewrite option. - -## curl - Cinode Utilities +## cutl - Cinode Utilities A set of small utilities that are shared across other modules. + +## blob - type-safe wrappers around various blob-related data + +- Name - identification of the specific blob instance +- Type - blob type, associated with blob's name +- Key - encryption keys for encrypting and decryption blob's data +- AuthInfo - data allowing blob update after it's created (for dynamic blobs) diff --git a/base58/base58.go b/base58/base58.go new file mode 100644 index 0000000..ae04f76 --- /dev/null +++ b/base58/base58.go @@ -0,0 +1,104 @@ +/* +Copyright © 2025 Bartłomiej Święcki (byo) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package base58 + +import ( + "errors" + "fmt" + "math/big" +) + +var ErrInvalidBase58Character = errors.New("invalid base58 character") + +var bn2btc, btc2bn = func() (bn2btc, btc2bn [256]byte) { + const btcAlphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" + const bitNumDigits = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUV" + + for i := range btcAlphabet { + bn2btc[bitNumDigits[i]] = btcAlphabet[i] + btc2bn[btcAlphabet[i]] = bitNumDigits[i] + } + + return bn2btc, btc2bn +}() + +func Encode(data []byte) string { + // Count leading zero bytes, those are treated especially + leadingZeros := 0 + for _, b := range data { + if b == 0 { + leadingZeros++ + } else { + break + } + } + + var bi big.Int + bi.SetBytes(data[leadingZeros:]) + + txt := bi.Text(58) + + if txt == "0" { + txt = "" + } + + res := make([]byte, leadingZeros+len(txt)) + + // Add '1' characters for leading zeros + for i := 0; i < leadingZeros; i++ { + res[i] = '1' + } + + // Convert the rest + for i, b := range txt { + res[leadingZeros+i] = bn2btc[b] + } + + return string(res) +} + +func Decode(s string) ([]byte, error) { + // Split into leading bytes and bit.Int-compatible representation + leadingZeros := 0 + leadingZerosDone := false + bnText := make([]byte, 0, len(s)) + for _, b := range s { + switch { + case !leadingZerosDone && b == '1': + leadingZeros++ + case btc2bn[b] != 0: + leadingZerosDone = true + bnText = append(bnText, btc2bn[b]) + default: + return nil, fmt.Errorf("%w: '%v'", ErrInvalidBase58Character, b) + } + } + + if len(bnText) == 0 { + return make([]byte, leadingZeros), nil + } + + var bn big.Int + bn.SetString(string(bnText), 58) + + bnBytes := bn.Bytes() + + result := make([]byte, leadingZeros+len(bnBytes)) + copy(result[leadingZeros:], bnBytes) + + return result, nil +} diff --git a/base58/base58_encode_decode.json b/base58/base58_encode_decode.json new file mode 100644 index 0000000..7a94c63 --- /dev/null +++ b/base58/base58_encode_decode.json @@ -0,0 +1,23 @@ +[ + ["", ""], + ["61", "2g"], + ["626262", "a3gV"], + ["636363", "aPEr"], + ["73696d706c792061206c6f6e6720737472696e67", "2cFupjhnEsSn59qHXstmK2ffpLv2"], + ["00eb15231dfceb60925886b67d065299925915aeb172c06647", "1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L"], + ["516b6fcd0f", "ABnLTmg"], + ["bf4f89001e670274dd", "3SEo3LWLoPntC"], + ["572e4794", "3EFU7m"], + ["ecac89cad93923c02321", "EJDM8drfXA6uyA"], + ["10c8511e", "Rt5zm"], + ["00000000000000000000", "1111111111"], + ["00000000000000000000000000000000000000000000000000000000000000000000000000000000", "1111111111111111111111111111111111111111"], + ["00000000000000000000000000000000000000000000000000000000000000000000000000000001", "1111111111111111111111111111111111111112"], + ["0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ec39d04c37e71e5d591881f6", "111111111111111111111111111111111111111111111111111111111111111111111111111111111111115TYzLYH1udmLdzCLM"], + ["000111d38e5fc9071ffcd20b4a763cc9ae4f252bb4e48fd66a835e252ada93ff480d6dd43dc62a641155a5", "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"], + ["000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff", "1cWB5HCBdLjAuqGGReWE3R3CguuwSjw6RHn39s2yuDRTS5NsBgNiFpWgAnEx6VQi8csexkgYw3mdYrMHr8x9i7aEwP8kZ7vccXWqKDvGv3u1GxFKPuAkn8JCPPGDMf3vMMnbzm6Nh9zh1gcNsMvH3ZNLmP5fSG6DGbbi2tuwMWPthr4boWwCxf7ewSgNQeacyozhKDDQQ1qL5fQFUW52QKUZDZ5fw3KXNQJMcNTcaB723LchjeKun7MuGW5qyCBZYzA1KjofN1gYBV3NqyhQJ3Ns746GNuf9N2pQPmHz4xpnSrrfCvy6TVVz5d4PdrjeshsWQwpZsZGzvbdAdN8MKV5QsBDY"], + ["271F359E", "zzzzy"], + ["271F359F", "zzzzz"], + ["271F35A0", "211111"], + ["271F35A1", "211112"] + ] \ No newline at end of file diff --git a/base58/base58_test.go b/base58/base58_test.go new file mode 100644 index 0000000..bfb0be6 --- /dev/null +++ b/base58/base58_test.go @@ -0,0 +1,128 @@ +/* +Copyright © 2025 Bartłomiej Święcki (byo) + +Licensed under the Apache License, + Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, + software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package base58_test + +import ( + _ "embed" + "encoding/hex" + "encoding/json" + "fmt" + "testing" + + "github.com/cinode/go-common/base58" + "github.com/cinode/go-common/picotestify/require" +) + +// Test cases from (MIT License): +// +// https://github.com/bitcoin/bitcoin/blob/master/src/test/data/base58_encode_decode.json +// +//go:embed base58_encode_decode.json +var testJSONData string + +type testCase struct { + expected string + data []byte +} + +func validTestCases(t require.TestingT) []testCase { + testCases := []testCase{ + { + data: []byte{}, + expected: "", + }, + { + data: []byte("Hello World!"), + expected: "2NEpo7TZRRrLZSi2U", + }, + { + data: []byte("The quick brown fox jumps over the lazy dog."), + expected: "USm3fpXnKG5EUBx2ndxBDMPVciP5hGey2Jh4NDv6gmeo1LkMeiKrLJUUBk6Z", + }, + { + data: []byte{0x00, 0x00, 0x28, 0x7f, 0xb4, 0xcd}, + expected: "11233QC4", + }, + } + + jsonTests := [][]string{} + require.NoError(t, json.Unmarshal([]byte(testJSONData), &jsonTests)) + + for _, tc := range jsonTests { + require.Len(t, tc, 2) + + data, err := hex.DecodeString(tc[0]) + require.NoError(t, err) + expected := tc[1] + + testCases = append(testCases, testCase{ + data: data, + expected: expected, + }) + } + return testCases +} + +func TestEncodeDecode(t *testing.T) { + for _, test := range validTestCases(t) { + t.Run(fmt.Sprintf("data=%v", test.data), func(t *testing.T) { + encoded := base58.Encode(test.data) + require.Equal(t, test.expected, encoded) + + decodedBack, err := base58.Decode(encoded) + require.NoError(t, err) + + require.Equal(t, test.data, decodedBack) + }) + } +} + +func TestErrorOnInvalidDecode(t *testing.T) { + for _, test := range []string{ + "@", + "3SEo3LWLoPntC@", + "@3SEo3LWLoPntC", + "3SEo3@LWLoPntC", + } { + t.Run(test, func(t *testing.T) { + decoded, err := base58.Decode(test) + require.Nil(t, decoded) + require.ErrorIs(t, err, base58.ErrInvalidBase58Character) + }) + } +} + +func FuzzEncodeDecode(f *testing.F) { + for _, test := range validTestCases(f) { + f.Add(test.data) + } + + f.Fuzz(func(t *testing.T, a []byte) { + if len(a) > 256 { + // No point in testing those + t.SkipNow() + } + + str := base58.Encode(a) + back, err := base58.Decode(str) + require.NoError(t, err) + require.Equal(t, a, back) + }) +} diff --git a/blob/auth_info.go b/blob/auth_info.go new file mode 100644 index 0000000..ef5bce9 --- /dev/null +++ b/blob/auth_info.go @@ -0,0 +1,32 @@ +/* +Copyright © 2025 Bartłomiej Święcki (byo) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package blob + +import ( + "bytes" + "crypto/subtle" +) + +// AuthInfo is an opaque data that is necessary to perform update of an existing blob. +// +// Currently used only for dynamic links, auth info contains all the necessary information +// to update the content of the blob. The representation is specific to the blob type +type AuthInfo struct{ data []byte } + +func AuthInfoFromBytes(ai []byte) *AuthInfo { return &AuthInfo{data: bytes.Clone(ai)} } +func (a *AuthInfo) Bytes() []byte { return bytes.Clone(a.data) } +func (a *AuthInfo) Equal(a2 *AuthInfo) bool { return subtle.ConstantTimeCompare(a.data, a2.data) == 1 } diff --git a/blob/auth_info_test.go b/blob/auth_info_test.go new file mode 100644 index 0000000..c913d07 --- /dev/null +++ b/blob/auth_info_test.go @@ -0,0 +1,31 @@ +/* +Copyright © 2025 Bartłomiej Święcki (byo) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package blob + +import ( + "testing" + + "github.com/cinode/go-common/picotestify/require" +) + +func TestAuthInfo(t *testing.T) { + authInfoBytes := []byte{1, 2, 3} + authInfo := AuthInfoFromBytes(authInfoBytes) + require.Equal(t, authInfoBytes, authInfo.Bytes()) + require.True(t, authInfo.Equal(AuthInfoFromBytes(authInfoBytes))) + require.Nil(t, new(Key).Bytes()) +} diff --git a/blob/keys.go b/blob/keys.go new file mode 100644 index 0000000..89df0e2 --- /dev/null +++ b/blob/keys.go @@ -0,0 +1,36 @@ +/* +Copyright © 2025 Bartłomiej Święcki (byo) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package blob + +import ( + "bytes" + "crypto/subtle" +) + +// Key with cipher type +type Key struct{ key []byte } + +func KeyFromBytes(key []byte) *Key { return &Key{key: bytes.Clone(key)} } +func (k *Key) Bytes() []byte { return bytes.Clone(k.key) } +func (k *Key) Equal(k2 *Key) bool { return subtle.ConstantTimeCompare(k.key, k2.key) == 1 } + +// IV +type IV struct{ iv []byte } + +func IVFromBytes(iv []byte) *IV { return &IV{iv: bytes.Clone(iv)} } +func (i *IV) Bytes() []byte { return bytes.Clone(i.iv) } +func (i *IV) Equal(i2 *IV) bool { return subtle.ConstantTimeCompare(i.iv, i2.iv) == 1 } diff --git a/blob/keys_test.go b/blob/keys_test.go new file mode 100644 index 0000000..df1ffad --- /dev/null +++ b/blob/keys_test.go @@ -0,0 +1,39 @@ +/* +Copyright © 2025 Bartłomiej Święcki (byo) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package blob + +import ( + "testing" + + "github.com/cinode/go-common/picotestify/require" +) + +func TestBlobKey(t *testing.T) { + keyBytes := []byte{1, 2, 3} + key := KeyFromBytes(keyBytes) + require.Equal(t, keyBytes, key.Bytes()) + require.True(t, key.Equal(KeyFromBytes(keyBytes))) + require.Nil(t, new(Key).Bytes()) +} + +func TestBlobIV(t *testing.T) { + ivBytes := []byte{1, 2, 3} + iv := IVFromBytes(ivBytes) + require.Equal(t, ivBytes, iv.Bytes()) + require.True(t, iv.Equal(IVFromBytes(ivBytes))) + require.Nil(t, new(Key).Bytes()) +} diff --git a/blob/name.go b/blob/name.go new file mode 100644 index 0000000..2b1da2f --- /dev/null +++ b/blob/name.go @@ -0,0 +1,102 @@ +/* +Copyright © 2025 Bartłomiej Święcki (byo) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package blob + +import ( + "bytes" + "crypto/subtle" + "errors" + + "github.com/cinode/go-common/base58" +) + +var ( + ErrInvalidBlobName = errors.New("invalid blob name") +) + +// Name is used to identify blobs. +// Internally it is a single array of bytes that represents +// both the type of the blob and internal hash used to create that blob. +// The type of the blob is not stored directly. Instead it is mixed +// with the hash of the blob to make sure that all bytes in the blob name +// are randomly distributed. +type Name struct { + bn []byte +} + +// NameFromHashAndType generates the name of a blob from some hash (e.g. sha256 of blob's content) +// and given blob type +func NameFromHashAndType(hash []byte, t Type) (*Name, error) { + if len(hash) == 0 || len(hash) > 0x7E { + return nil, ErrInvalidBlobName + } + + bn := make([]byte, len(hash)+1) + + copy(bn[1:], hash) + + scrambledTypeByte := t.t + for _, b := range hash { + scrambledTypeByte ^= b + } + bn[0] = scrambledTypeByte + + return &Name{bn: bn}, nil +} + +// NameFromString decodes base58-encoded string into blob name +func NameFromString(s string) (*Name, error) { + decoded, err := base58.Decode(s) + if err != nil { + return nil, ErrInvalidBlobName + } + return NameFromBytes(decoded) +} + +func NameFromBytes(n []byte) (*Name, error) { + if len(n) == 0 || len(n) > 0x7F { + return nil, ErrInvalidBlobName + } + return &Name{bn: bytes.Clone(n)}, nil +} + +// Returns base58-encoded blob name +func (b *Name) String() string { + return base58.Encode(b.bn) +} + +// Extracts hash from blob name +func (b *Name) Hash() []byte { + return b.bn[1:] +} + +// Extracts blob type from the name +func (b *Name) Type() Type { + ret := byte(0) + for _, by := range b.bn { + ret ^= by + } + return Type{t: ret} +} + +func (b *Name) Bytes() []byte { + return bytes.Clone(b.bn) +} + +func (b *Name) Equal(b2 *Name) bool { + return subtle.ConstantTimeCompare(b.bn, b2.bn) == 1 +} diff --git a/blob/name_test.go b/blob/name_test.go new file mode 100644 index 0000000..d6f6470 --- /dev/null +++ b/blob/name_test.go @@ -0,0 +1,73 @@ +/* +Copyright © 2025 Bartłomiej Święcki (byo) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package blob + +import ( + "crypto/sha256" + "fmt" + "testing" + + "github.com/cinode/go-common/picotestify/assert" + "github.com/cinode/go-common/picotestify/require" +) + +func TestBlobName(t *testing.T) { + for _, h := range [][]byte{ + {0}, {1}, {2}, {0xFE}, {0xFF}, + {0, 0, 0, 0}, + {1, 2, 3, 4, 5, 6}, + sha256.New().Sum(nil), + } { + for _, bt := range []Type{ + {t: 0x00}, + {t: 0x01}, + {t: 0x02}, + {t: 0xFE}, + {t: 0xFF}, + } { + t.Run(fmt.Sprintf("%v:%v", bt, h), func(t *testing.T) { + bn, err := NameFromHashAndType(h, bt) + assert.NoError(t, err) + assert.NotEmpty(t, bn) + assert.Greater(t, len(bn.bn), len(h)) + assert.Equal(t, h, bn.Hash()) + assert.Equal(t, bt, bn.Type()) + + s := bn.String() + bn2, err := NameFromString(s) + require.NoError(t, err) + require.Equal(t, bn, bn2) + require.True(t, bn.Equal(bn2)) + + b := bn.Bytes() + bn3, err := NameFromBytes(b) + require.NoError(t, err) + require.Equal(t, bn, bn3) + require.True(t, bn.Equal(bn3)) + }) + } + } + + _, err := NameFromString("!@#") + require.ErrorIs(t, err, ErrInvalidBlobName) + + _, err = NameFromString("") + require.ErrorIs(t, err, ErrInvalidBlobName) + + _, err = NameFromHashAndType(nil, Type{t: 0x00}) + require.ErrorIs(t, err, ErrInvalidBlobName) +} diff --git a/blob/type.go b/blob/type.go new file mode 100644 index 0000000..6f9bb02 --- /dev/null +++ b/blob/type.go @@ -0,0 +1,29 @@ +/* +Copyright © 2025 Bartłomiej Święcki (byo) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package blob + +type Type struct { + t byte +} + +func NewType(t byte) Type { + return Type{t: t} +} + +func (b Type) IDByte() byte { + return b.t +} diff --git a/blob/type_test.go b/blob/type_test.go new file mode 100644 index 0000000..4e4bc04 --- /dev/null +++ b/blob/type_test.go @@ -0,0 +1,29 @@ +/* +Copyright © 2025 Bartłomiej Święcki (byo) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package blob + +import ( + "testing" + + "github.com/cinode/go-common/picotestify/require" +) + +func TestBlobType(t *testing.T) { + tp := NewType(0x77) + require.Equal(t, tp.t, byte(0x77)) + require.Equal(t, tp.IDByte(), byte(0x77)) +}