From cd716f0e793b6018cd408d73e387a4cf06d94183 Mon Sep 17 00:00:00 2001 From: Alexey Potapenko Date: Tue, 18 Nov 2025 10:14:27 +0300 Subject: [PATCH 1/3] api: implement namer, generator, validator Closes TNTP-4190 --- hasher/hasher_test.go | 8 +- marshaller/marshaller.go | 8 +- namer/generatorvalidator.go | 145 +++++++++++++++++++++++++++++++ namer/generatorvalidator_test.go | 95 ++++++++++++++++++++ namer/namer.go | 80 +++++++++++++++-- namer/namer_test.go | 68 +++++++++++++++ 6 files changed, 388 insertions(+), 16 deletions(-) create mode 100644 namer/generatorvalidator.go create mode 100644 namer/generatorvalidator_test.go create mode 100644 namer/namer_test.go diff --git a/hasher/hasher_test.go b/hasher/hasher_test.go index a334dff..97ccab5 100644 --- a/hasher/hasher_test.go +++ b/hasher/hasher_test.go @@ -61,9 +61,9 @@ func TestSHA256Hasher(t *testing.T) { t.Parallel() tests := []struct { - name string - in []byte - out string + name string + in []byte + expected string }{ {"empty", []byte(""), "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, {"abc", []byte("abc"), "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"}, @@ -77,7 +77,7 @@ func TestSHA256Hasher(t *testing.T) { result, _ := h.Hash(test.in) - assert.Equal(t, test.out, hex.EncodeToString(result)) + assert.Equal(t, test.expected, hex.EncodeToString(result)) }) } } diff --git a/marshaller/marshaller.go b/marshaller/marshaller.go index 83702b4..9ec6f14 100644 --- a/marshaller/marshaller.go +++ b/marshaller/marshaller.go @@ -17,7 +17,7 @@ var ErrUnmarshall = errors.New("failed to unmarshal") // implements one time for all objects. // Required for `integrity.Storage` to set marshalling format for any type object // and as recommendation for developers of `Storage` wrappers. -type DefaultMarshaller interface { +type DefaultMarshaller interface { //nolint:iface Marshal(data any) ([]byte, error) Unmarshal(data []byte, out any) error } @@ -25,8 +25,8 @@ type DefaultMarshaller interface { // Marshallable - custom object serialization, implements for each object. // Required for `integrity.Storage` type to set marshalling format to specific object // and as recommendation for developers of `Storage` wrappers. -type Marshallable interface { - Marshal() ([]byte, error) +type Marshallable interface { //nolint:iface + Marshal(data any) ([]byte, error) Unmarshal(data []byte, out any) error } @@ -34,7 +34,7 @@ type Marshallable interface { type YAMLMarshaller struct{} // NewYamlMarshaller creates new NewYamlMarshaller object. -func NewYamlMarshaller() YAMLMarshaller { +func NewYamlMarshaller() Marshallable { return YAMLMarshaller{} } diff --git a/namer/generatorvalidator.go b/namer/generatorvalidator.go new file mode 100644 index 0000000..cbc42a5 --- /dev/null +++ b/namer/generatorvalidator.go @@ -0,0 +1,145 @@ +package namer + +import ( + "bytes" + "fmt" + "strings" + + "github.com/tarantool/go-storage/crypto" + "github.com/tarantool/go-storage/hasher" + "github.com/tarantool/go-storage/kv" + "github.com/tarantool/go-storage/marshaller" +) + +// Generator generates signer K/V pairs. +// Implementation should use `generic` and will used for strong typing of the solution. +type Generator[T any] interface { + Generate(name string, value T) ([]kv.KeyValue, error) +} + +// Validator validates and build the object from K/V. +type Validator[T any] interface { + Validate(pairs []kv.KeyValue) (T, error) +} + +// DefaultGeneratorValidator represent default generator-validator. +type DefaultGeneratorValidator[T any] struct { + Namer Namer + Hasher hasher.Hasher + SignerVerifier crypto.SignerVerifier + Marshaller marshaller.Marshallable +} + +// NewDefaultGeneratorValidator returns new object. +func NewDefaultGeneratorValidator[T any]( + namer Namer, + hasher hasher.Hasher, + signverifier crypto.SignerVerifier, + marshaller marshaller.Marshallable, +) DefaultGeneratorValidator[T] { + return DefaultGeneratorValidator[T]{ + Namer: namer, + Hasher: hasher, + SignerVerifier: signverifier, + Marshaller: marshaller, + } +} + +// Generate create KV pairs with value, hash and signature. +func (gv DefaultGeneratorValidator[T]) Generate(name string, value T) ([]kv.KeyValue, error) { + if name == "" { + return []kv.KeyValue{}, ErrInvalidInput + } + + var kvList []kv.KeyValue + + blob, err := gv.Marshaller.Marshal(value) + if err != nil { + return nil, fmt.Errorf("failed to marshal: %w", err) + } + + hash, err := gv.Hasher.Hash(blob) + if err != nil { + return nil, fmt.Errorf("failed to hash: %w", err) + } + + signature, err := gv.SignerVerifier.Sign(hash) + if err != nil { + return nil, fmt.Errorf("failed to sign: %w", err) + } + + names := gv.Namer.GenerateNames(name) + keys := gv.Namer.ParseNames(names) + + for _, key := range keys { + switch key.Type { + case KeyTypeValue: + { + kvList = append(kvList, kv.KeyValue{ + Key: []byte(key.Name), + Value: blob, + ModRevision: 1, + }) + } + case KeyTypeHash: + { + kvList = append(kvList, kv.KeyValue{ + Key: []byte(key.Name), + Value: hash, + ModRevision: 1, + }) + } + case KeyTypeSignature: + { + kvList = append(kvList, kv.KeyValue{ + Key: []byte(key.Name), + Value: signature, + ModRevision: 1, + }) + } + } + } + + return kvList, nil +} + +// Validate checks hash match, verify signature, unmarshall object and return it. +func (gv DefaultGeneratorValidator[T]) Validate(pairs []kv.KeyValue) (T, error) { + var value T + + var blob []byte + + var hash, signature []byte + + for _, keyvalue := range pairs { + switch { + case strings.Contains(string(keyvalue.Key), hashName): + hash = keyvalue.Value + case strings.Contains(string(keyvalue.Key), sigName): + signature = keyvalue.Value + default: + blob = keyvalue.Value + } + } + + if blob == nil || hash == nil || signature == nil { + return value, ErrInvalidKey + } + + err := gv.SignerVerifier.Verify(hash, signature) + if err != nil { + return value, fmt.Errorf("signature verification failed: %w", err) + } + + computedHash, err := gv.Hasher.Hash(blob) + if !bytes.Equal(computedHash, hash) || err != nil { + return value, ErrHashMismatch + } + + err = gv.Marshaller.Unmarshal(blob, &value) + if err != nil { + return value, fmt.Errorf("failed to unmarshal: %w", err) + } + + return value, nil +} diff --git a/namer/generatorvalidator_test.go b/namer/generatorvalidator_test.go new file mode 100644 index 0000000..db477ab --- /dev/null +++ b/namer/generatorvalidator_test.go @@ -0,0 +1,95 @@ +package namer_test + +import ( + "crypto/rand" + "crypto/rsa" + "testing" + + "github.com/stretchr/testify/require" + "github.com/tarantool/go-storage/hasher" + "github.com/tarantool/go-storage/kv" + "github.com/tarantool/go-storage/marshaller" + "github.com/tarantool/go-storage/namer" + + gscrypto "github.com/tarantool/go-storage/crypto" +) + +func TestGeneratorValidator(t *testing.T) { + t.Parallel() + + tests := []struct { + prefix string + name string + expected []kv.KeyValue + }{ + { + "/tt/config", + "all", + []kv.KeyValue{ + { + Key: []byte("/tt/config/all"), + Value: []byte("value"), + ModRevision: 1, + }, + { + Key: []byte("/tt/config/hash/all"), + Value: []byte{ + 0x1E, 0x1F, 0x2C, 0x88, 0x1A, 0xE0, 0x60, 0x8E, 0xC7, 0x7E, 0xBF, 0x88, 0xA7, 0x5C, 0x66, 0xD3, + 0x09, 0x91, 0x13, 0xA7, 0x34, 0x32, 0x38, 0xF2, 0xF7, 0xA0, 0xEB, 0xB9, 0x1A, 0x4E, 0xD3, 0x35, + }, + ModRevision: 1, + }, + { + Key: []byte("/tt/config/sig/all"), + Value: []byte{ + 0x2c, 0xd0, 0x10, 0x8d, 0x3b, 0x43, 0x3b, 0x8e, 0x60, 0x22, 0x09, 0x78, 0x86, 0xf2, 0xe1, 0xd1, + 0xcd, 0xea, 0xa0, 0x9b, 0xd0, 0x92, 0xb1, 0xc5, 0x90, 0x3a, 0x85, 0xeb, 0xd2, 0xe8, 0x3f, 0x43, + 0x6d, 0xe2, 0x08, 0x1d, 0xfb, 0xc6, 0xd9, 0x04, 0x6e, 0x2c, 0x11, 0xa7, 0x6f, 0xcb, 0x27, 0x66, + 0x0e, 0x6b, 0xc8, 0xbb, 0xe1, 0x77, 0x33, 0xa3, 0x65, 0x49, 0xbf, 0xc7, 0x6e, 0x18, 0x0e, 0x4d, + 0x0f, 0x20, 0x0b, 0x51, 0xf9, 0x50, 0xdf, 0x76, 0x2d, 0x22, 0x0e, 0xa2, 0x1c, 0xa5, 0xdf, 0x49, + 0xbd, 0x2b, 0xbd, 0xc7, 0xa6, 0x99, 0x8d, 0x55, 0x3b, 0xf8, 0xf4, 0xa2, 0xbf, 0xda, 0xf8, 0xe9, + 0xf7, 0x41, 0xa8, 0xc4, 0xa9, 0x34, 0xef, 0x0b, 0xa4, 0xb5, 0x96, 0x08, 0x60, 0x8c, 0xa1, 0xdb, + 0x4e, 0xd0, 0x5a, 0xed, 0x78, 0x3f, 0x0f, 0x9e, 0x74, 0x09, 0xf9, 0x75, 0x07, 0xbb, 0x5b, 0xe8, + 0xbe, 0x44, 0xbf, 0x07, 0xf7, 0x9f, 0x26, 0x12, 0xb2, 0x02, 0xbf, 0x96, 0xdf, 0x18, 0x35, 0x46, + 0x67, 0x9f, 0x7b, 0xe8, 0xdc, 0xfe, 0x7f, 0xac, 0xad, 0x5b, 0x94, 0x63, 0x0f, 0x1b, 0x03, 0xe6, + 0x3f, 0x80, 0xb8, 0x9a, 0x7f, 0x5a, 0x4e, 0x1c, 0x7b, 0x51, 0x9b, 0x76, 0x3b, 0x50, 0xcd, 0x22, + 0x95, 0xca, 0xf0, 0x0a, 0xac, 0xf8, 0x61, 0xbb, 0x9f, 0xe6, 0x36, 0xe5, 0xde, 0x6c, 0xb8, 0xfb, + 0x3e, 0xb7, 0x94, 0x53, 0x20, 0x1e, 0xbc, 0xfa, 0x0a, 0x6e, 0x29, 0xcd, 0x91, 0xe5, 0xf3, 0xa9, + 0x1a, 0xb2, 0x87, 0x97, 0x6f, 0x93, 0x77, 0x48, 0x08, 0xe3, 0xf9, 0x9d, 0xd3, 0x44, 0x18, 0x82, + 0x58, 0x66, 0xf7, 0x41, 0x64, 0x41, 0x1d, 0xae, 0xef, 0x30, 0x3e, 0x6e, 0xf9, 0xeb, 0x0d, 0xbe, + 0xb8, 0x90, 0x76, 0x79, 0x36, 0x44, 0x60, 0x0d, 0xa2, 0x15, 0xcc, 0xc8, 0x28, 0x0f, 0x5d, 0x36, + }, + ModRevision: 1, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + defnamer := namer.NewDefaultNamer(tt.prefix) + + hasher := hasher.NewSHA256Hasher() + + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + require.NoError(t, err) + + sv := gscrypto.NewRSAPSS(*privateKey, privateKey.PublicKey) + + mr := marshaller.NewYamlMarshaller() + + dgv := namer.NewDefaultGeneratorValidator[string](defnamer, hasher, &sv, mr) + + resultKV, err := dgv.Generate(tt.name, "value") + require.NoError(t, err) + + require.Len(t, tt.expected, len(resultKV)) + + for i := range resultKV { + require.Equal(t, tt.expected[i].Key, resultKV[i].Key) + } + }) + } +} diff --git a/namer/namer.go b/namer/namer.go index f272a00..0785d18 100644 --- a/namer/namer.go +++ b/namer/namer.go @@ -2,7 +2,8 @@ package namer import ( - "github.com/tarantool/go-storage/kv" + "errors" + "strings" ) // KeyType represents key types. @@ -17,6 +18,21 @@ const ( KeyTypeSignature ) +const ( + hashName = "hash" + sigName = "sig" + namesNumber = 3 +) + +var ( + // ErrInvalidKey is returned when missing key, hash or signature. + ErrInvalidKey = errors.New("missing key, hash or signature") + // ErrHashMismatch is returned when hash mismatch. + ErrHashMismatch = errors.New("hash mismatch") + // ErrInvalidInput is returned when input data is invalid. + ErrInvalidInput = errors.New("failed to generate: invalid input data") +) + // Key implements internal realization. type Key struct { Name string // Object identificator. @@ -30,13 +46,61 @@ type Namer interface { ParseNames(names []string) []Key // Convert names into keys. } -// Generator generates signer K/V pairs. -// Implementation should use `generic` and will used for strong typing of the solution. -type Generator[T any] interface { - Generate(name string, value T) ([]kv.KeyValue, error) +// DefaultNamer represents default namer. +type DefaultNamer struct { + prefix string +} + +// NewDefaultNamer returns new DefaultNamer object. +func NewDefaultNamer(prefix string) *DefaultNamer { + return &DefaultNamer{ + prefix: prefix, + } +} + +// GenerateNames generates set of names from basic name. +func (n *DefaultNamer) GenerateNames(name string) []string { + return []string{ + n.prefix + "/" + name, + n.prefix + "/" + hashName + "/" + name, + n.prefix + "/" + sigName + "/" + name, + } } -// Validator validates and build the object from K/V. -type Validator[T any] interface { - Validate(pairs []kv.KeyValue) (T, error) +// ParseNames returns set of Keys with different types. +func (n *DefaultNamer) ParseNames(names []string) []Key { + keys := make([]Key, 0, namesNumber) + + for _, name := range names { + var key Key + + // Remove prefix. + result, _ := strings.CutPrefix(name, n.prefix) + + parts := strings.Split(result, "/") + + key.Name = name + + switch parts[1] { + case hashName: + { + key.Property = "" + key.Type = KeyTypeHash + } + case sigName: + { + key.Property = "" + key.Type = KeyTypeSignature + } + default: + { + key.Property = "" + key.Type = KeyTypeValue + } + } + + keys = append(keys, key) + } + + return keys } diff --git a/namer/namer_test.go b/namer/namer_test.go new file mode 100644 index 0000000..41fb58d --- /dev/null +++ b/namer/namer_test.go @@ -0,0 +1,68 @@ +package namer_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/tarantool/go-storage/namer" +) + +func TestGenerateNames(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + prefix string + expected []string + }{ + { + "all", + "/tt/config", + []string{"/tt/config/all", "/tt/config/hash/all", "/tt/config/sig/all"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + dn := namer.NewDefaultNamer(tt.prefix) + + result := dn.GenerateNames(tt.name) + require.Equal(t, tt.expected, result) + }) + } +} + +func TestParseNames(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + prefix string + names []string + expected []namer.Key + }{ + { + "all", + "/tt/config", + []string{"/tt/config/all", "/tt/config/hash/all", "/tt/config/sig/all"}, + []namer.Key{ + {Name: "/tt/config/all", Type: namer.KeyTypeValue, Property: ""}, + {Name: "/tt/config/hash/all", Type: namer.KeyTypeHash, Property: ""}, + {Name: "/tt/config/sig/all", Type: namer.KeyTypeSignature, Property: ""}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + dn := namer.NewDefaultNamer(tt.prefix) + + result := dn.ParseNames(tt.names) + require.Equal(t, tt.expected, result) + }) + } +} From 93d8afdb60dbda54c2dc804651c037a6f9262319 Mon Sep 17 00:00:00 2001 From: Alexey Potapenko Date: Tue, 9 Dec 2025 09:35:32 +0300 Subject: [PATCH 2/3] refactoring: first iteration without tests --- kv/kv.go | 1 - namer/generatorvalidator.go | 145 ------------- namer/generatorvalidator_test.go | 95 --------- namer/key.go | 70 ++++++ namer/namer.go | 285 ++++++++++++++++++++----- namer/namer_test.go | 78 ++++++- {crypto => verification}/interfaces.go | 4 +- {crypto => verification}/rsa.go | 8 +- {crypto => verification}/rsa_test.go | 15 +- 9 files changed, 382 insertions(+), 319 deletions(-) delete mode 100644 namer/generatorvalidator.go delete mode 100644 namer/generatorvalidator_test.go create mode 100644 namer/key.go rename {crypto => verification}/interfaces.go (88%) rename {crypto => verification}/rsa.go (89%) rename {crypto => verification}/rsa_test.go (81%) diff --git a/kv/kv.go b/kv/kv.go index 178d9a2..6a5e5cb 100644 --- a/kv/kv.go +++ b/kv/kv.go @@ -9,7 +9,6 @@ type KeyValue struct { Key []byte // Value is the serialized representation of the value. Value []byte - // ModRevision is the revision number of the last modification to this key. ModRevision int64 } diff --git a/namer/generatorvalidator.go b/namer/generatorvalidator.go deleted file mode 100644 index cbc42a5..0000000 --- a/namer/generatorvalidator.go +++ /dev/null @@ -1,145 +0,0 @@ -package namer - -import ( - "bytes" - "fmt" - "strings" - - "github.com/tarantool/go-storage/crypto" - "github.com/tarantool/go-storage/hasher" - "github.com/tarantool/go-storage/kv" - "github.com/tarantool/go-storage/marshaller" -) - -// Generator generates signer K/V pairs. -// Implementation should use `generic` and will used for strong typing of the solution. -type Generator[T any] interface { - Generate(name string, value T) ([]kv.KeyValue, error) -} - -// Validator validates and build the object from K/V. -type Validator[T any] interface { - Validate(pairs []kv.KeyValue) (T, error) -} - -// DefaultGeneratorValidator represent default generator-validator. -type DefaultGeneratorValidator[T any] struct { - Namer Namer - Hasher hasher.Hasher - SignerVerifier crypto.SignerVerifier - Marshaller marshaller.Marshallable -} - -// NewDefaultGeneratorValidator returns new object. -func NewDefaultGeneratorValidator[T any]( - namer Namer, - hasher hasher.Hasher, - signverifier crypto.SignerVerifier, - marshaller marshaller.Marshallable, -) DefaultGeneratorValidator[T] { - return DefaultGeneratorValidator[T]{ - Namer: namer, - Hasher: hasher, - SignerVerifier: signverifier, - Marshaller: marshaller, - } -} - -// Generate create KV pairs with value, hash and signature. -func (gv DefaultGeneratorValidator[T]) Generate(name string, value T) ([]kv.KeyValue, error) { - if name == "" { - return []kv.KeyValue{}, ErrInvalidInput - } - - var kvList []kv.KeyValue - - blob, err := gv.Marshaller.Marshal(value) - if err != nil { - return nil, fmt.Errorf("failed to marshal: %w", err) - } - - hash, err := gv.Hasher.Hash(blob) - if err != nil { - return nil, fmt.Errorf("failed to hash: %w", err) - } - - signature, err := gv.SignerVerifier.Sign(hash) - if err != nil { - return nil, fmt.Errorf("failed to sign: %w", err) - } - - names := gv.Namer.GenerateNames(name) - keys := gv.Namer.ParseNames(names) - - for _, key := range keys { - switch key.Type { - case KeyTypeValue: - { - kvList = append(kvList, kv.KeyValue{ - Key: []byte(key.Name), - Value: blob, - ModRevision: 1, - }) - } - case KeyTypeHash: - { - kvList = append(kvList, kv.KeyValue{ - Key: []byte(key.Name), - Value: hash, - ModRevision: 1, - }) - } - case KeyTypeSignature: - { - kvList = append(kvList, kv.KeyValue{ - Key: []byte(key.Name), - Value: signature, - ModRevision: 1, - }) - } - } - } - - return kvList, nil -} - -// Validate checks hash match, verify signature, unmarshall object and return it. -func (gv DefaultGeneratorValidator[T]) Validate(pairs []kv.KeyValue) (T, error) { - var value T - - var blob []byte - - var hash, signature []byte - - for _, keyvalue := range pairs { - switch { - case strings.Contains(string(keyvalue.Key), hashName): - hash = keyvalue.Value - case strings.Contains(string(keyvalue.Key), sigName): - signature = keyvalue.Value - default: - blob = keyvalue.Value - } - } - - if blob == nil || hash == nil || signature == nil { - return value, ErrInvalidKey - } - - err := gv.SignerVerifier.Verify(hash, signature) - if err != nil { - return value, fmt.Errorf("signature verification failed: %w", err) - } - - computedHash, err := gv.Hasher.Hash(blob) - if !bytes.Equal(computedHash, hash) || err != nil { - return value, ErrHashMismatch - } - - err = gv.Marshaller.Unmarshal(blob, &value) - if err != nil { - return value, fmt.Errorf("failed to unmarshal: %w", err) - } - - return value, nil -} diff --git a/namer/generatorvalidator_test.go b/namer/generatorvalidator_test.go deleted file mode 100644 index db477ab..0000000 --- a/namer/generatorvalidator_test.go +++ /dev/null @@ -1,95 +0,0 @@ -package namer_test - -import ( - "crypto/rand" - "crypto/rsa" - "testing" - - "github.com/stretchr/testify/require" - "github.com/tarantool/go-storage/hasher" - "github.com/tarantool/go-storage/kv" - "github.com/tarantool/go-storage/marshaller" - "github.com/tarantool/go-storage/namer" - - gscrypto "github.com/tarantool/go-storage/crypto" -) - -func TestGeneratorValidator(t *testing.T) { - t.Parallel() - - tests := []struct { - prefix string - name string - expected []kv.KeyValue - }{ - { - "/tt/config", - "all", - []kv.KeyValue{ - { - Key: []byte("/tt/config/all"), - Value: []byte("value"), - ModRevision: 1, - }, - { - Key: []byte("/tt/config/hash/all"), - Value: []byte{ - 0x1E, 0x1F, 0x2C, 0x88, 0x1A, 0xE0, 0x60, 0x8E, 0xC7, 0x7E, 0xBF, 0x88, 0xA7, 0x5C, 0x66, 0xD3, - 0x09, 0x91, 0x13, 0xA7, 0x34, 0x32, 0x38, 0xF2, 0xF7, 0xA0, 0xEB, 0xB9, 0x1A, 0x4E, 0xD3, 0x35, - }, - ModRevision: 1, - }, - { - Key: []byte("/tt/config/sig/all"), - Value: []byte{ - 0x2c, 0xd0, 0x10, 0x8d, 0x3b, 0x43, 0x3b, 0x8e, 0x60, 0x22, 0x09, 0x78, 0x86, 0xf2, 0xe1, 0xd1, - 0xcd, 0xea, 0xa0, 0x9b, 0xd0, 0x92, 0xb1, 0xc5, 0x90, 0x3a, 0x85, 0xeb, 0xd2, 0xe8, 0x3f, 0x43, - 0x6d, 0xe2, 0x08, 0x1d, 0xfb, 0xc6, 0xd9, 0x04, 0x6e, 0x2c, 0x11, 0xa7, 0x6f, 0xcb, 0x27, 0x66, - 0x0e, 0x6b, 0xc8, 0xbb, 0xe1, 0x77, 0x33, 0xa3, 0x65, 0x49, 0xbf, 0xc7, 0x6e, 0x18, 0x0e, 0x4d, - 0x0f, 0x20, 0x0b, 0x51, 0xf9, 0x50, 0xdf, 0x76, 0x2d, 0x22, 0x0e, 0xa2, 0x1c, 0xa5, 0xdf, 0x49, - 0xbd, 0x2b, 0xbd, 0xc7, 0xa6, 0x99, 0x8d, 0x55, 0x3b, 0xf8, 0xf4, 0xa2, 0xbf, 0xda, 0xf8, 0xe9, - 0xf7, 0x41, 0xa8, 0xc4, 0xa9, 0x34, 0xef, 0x0b, 0xa4, 0xb5, 0x96, 0x08, 0x60, 0x8c, 0xa1, 0xdb, - 0x4e, 0xd0, 0x5a, 0xed, 0x78, 0x3f, 0x0f, 0x9e, 0x74, 0x09, 0xf9, 0x75, 0x07, 0xbb, 0x5b, 0xe8, - 0xbe, 0x44, 0xbf, 0x07, 0xf7, 0x9f, 0x26, 0x12, 0xb2, 0x02, 0xbf, 0x96, 0xdf, 0x18, 0x35, 0x46, - 0x67, 0x9f, 0x7b, 0xe8, 0xdc, 0xfe, 0x7f, 0xac, 0xad, 0x5b, 0x94, 0x63, 0x0f, 0x1b, 0x03, 0xe6, - 0x3f, 0x80, 0xb8, 0x9a, 0x7f, 0x5a, 0x4e, 0x1c, 0x7b, 0x51, 0x9b, 0x76, 0x3b, 0x50, 0xcd, 0x22, - 0x95, 0xca, 0xf0, 0x0a, 0xac, 0xf8, 0x61, 0xbb, 0x9f, 0xe6, 0x36, 0xe5, 0xde, 0x6c, 0xb8, 0xfb, - 0x3e, 0xb7, 0x94, 0x53, 0x20, 0x1e, 0xbc, 0xfa, 0x0a, 0x6e, 0x29, 0xcd, 0x91, 0xe5, 0xf3, 0xa9, - 0x1a, 0xb2, 0x87, 0x97, 0x6f, 0x93, 0x77, 0x48, 0x08, 0xe3, 0xf9, 0x9d, 0xd3, 0x44, 0x18, 0x82, - 0x58, 0x66, 0xf7, 0x41, 0x64, 0x41, 0x1d, 0xae, 0xef, 0x30, 0x3e, 0x6e, 0xf9, 0xeb, 0x0d, 0xbe, - 0xb8, 0x90, 0x76, 0x79, 0x36, 0x44, 0x60, 0x0d, 0xa2, 0x15, 0xcc, 0xc8, 0x28, 0x0f, 0x5d, 0x36, - }, - ModRevision: 1, - }, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - defnamer := namer.NewDefaultNamer(tt.prefix) - - hasher := hasher.NewSHA256Hasher() - - privateKey, err := rsa.GenerateKey(rand.Reader, 2048) - require.NoError(t, err) - - sv := gscrypto.NewRSAPSS(*privateKey, privateKey.PublicKey) - - mr := marshaller.NewYamlMarshaller() - - dgv := namer.NewDefaultGeneratorValidator[string](defnamer, hasher, &sv, mr) - - resultKV, err := dgv.Generate(tt.name, "value") - require.NoError(t, err) - - require.Len(t, tt.expected, len(resultKV)) - - for i := range resultKV { - require.Equal(t, tt.expected[i].Key, resultKV[i].Key) - } - }) - } -} diff --git a/namer/key.go b/namer/key.go new file mode 100644 index 0000000..ab9750e --- /dev/null +++ b/namer/key.go @@ -0,0 +1,70 @@ +package namer + +// KeyType represents key types. +type KeyType int + +const ( + // KeyTypeValue represents data type. + KeyTypeValue KeyType = iota + 1 + // KeyTypeHash represents hash of the data type. + KeyTypeHash + // KeyTypeSignature represents signature of the data type. + KeyTypeSignature +) + +// Key defines the minimal interface required by keys. +type Key interface { + Name() string // Get object name. + Type() KeyType // Get key type. + Property() string // Get metadata (e.g., algorithm version). + Raw() []byte // Get raw data. + Build() string // Reconstruct raw key string. +} + +// DefaultKey implements default realization. +type DefaultKey struct { + name string // Object identifier. + keytype KeyType // Type of object (hash/signature/value). + property string // Additional metadata (version/algorithm). + raw []byte // Raw key string. +} + +// NewDefaultKey returns new Key object. +func NewDefaultKey(n string, k KeyType, p string, r []byte) DefaultKey { + return DefaultKey{ + name: n, + keytype: k, + property: p, + raw: r, + } +} + +// Name returns name of the key. +func (k DefaultKey) Name() string { + return k.name +} + +// Type returns type of the key. +func (k DefaultKey) Type() KeyType { + return k.keytype +} + +// Property returns property of the key. +func (k DefaultKey) Property() string { + return k.property +} + +// Raw returns raw of the key. +func (k DefaultKey) Raw() []byte { + return k.raw +} + +// Build should reconstruct key from signature and digest or not? +func (k DefaultKey) Build() string { + return string(k.raw) +} + +// String returns string representation of the `raw` field. +func (k DefaultKey) String() string { + return string(k.raw) +} diff --git a/namer/namer.go b/namer/namer.go index 0785d18..21992e7 100644 --- a/namer/namer.go +++ b/namer/namer.go @@ -2,26 +2,23 @@ package namer import ( + "bytes" "errors" + "fmt" + "iter" "strings" -) -// KeyType represents key types. -type KeyType int + "github.com/tarantool/go-storage/kv" + "github.com/tarantool/go-storage/verification" -const ( - // KeyTypeValue represents data type. - KeyTypeValue KeyType = iota + 1 - // KeyTypeHash represents hash of the data type. - KeyTypeHash - // KeyTypeSignature represents signature of the data type. - KeyTypeSignature + "github.com/tarantool/go-storage/hasher" + "github.com/tarantool/go-storage/marshaller" ) const ( - hashName = "hash" - sigName = "sig" - namesNumber = 3 + hashName = "hash" + signatureName = "sig" + keysPerName = 3 ) var ( @@ -31,76 +28,250 @@ var ( ErrHashMismatch = errors.New("hash mismatch") // ErrInvalidInput is returned when input data is invalid. ErrInvalidInput = errors.New("failed to generate: invalid input data") + // ErrInvalidPrefix is returned when prefix didn't match. + ErrInvalidPrefix = errors.New("invalid prefix") ) -// Key implements internal realization. -type Key struct { - Name string // Object identificator. - Type KeyType // Type of the object. - Property string // Additional information (version/algorithm). +//----------------------------------------------------------------------------- + +// Results represents Namer working result. +type Results struct { + isSingle bool // True if result contains only one object name. + isSingleName string // Cached name when isSingle=true. + result map[string][]Key // Grouped keys: object name -> key list. +} + +// SelectSingle gets keys for single-name case (if applicable). +func (r *Results) SelectSingle() ([]Key, bool) { + if r.isSingle { + return r.result[r.isSingleName], true + } + + return nil, false +} + +// Items return iterator over all name->keys groups. +func (r *Results) Items() iter.Seq2[string, []Key] { + return func(yield func(str string, res []Key) bool) { + for i, v := range r.result { + if !yield(i, v) { + return + } + } + } +} + +// Select gets keys for a specific object name. +func (r *Results) Select(name string) ([]Key, bool) { + if i, ok := r.result[name]; ok { + return i, true + } + + return nil, false } -// Namer represents keys naming strategy. -type Namer interface { - GenerateNames(name string) []string // Object's keys generation. - ParseNames(names []string) []Key // Convert names into keys. +// Len returns the number of unique object names. +func (r *Results) Len() int { + return len(r.result) } +//----------------------------------------------------------------------------- + // DefaultNamer represents default namer. type DefaultNamer struct { - prefix string + prefix string + hasher hasher.Hasher + signerverifier verification.SignerVerifier + marshaller marshaller.Marshallable } // NewDefaultNamer returns new DefaultNamer object. -func NewDefaultNamer(prefix string) *DefaultNamer { +func NewDefaultNamer(prefix string, hasher hasher.Hasher, signerverifier verification.SignerVerifier, + marshaller marshaller.Marshallable, +) *DefaultNamer { + prefix = strings.TrimPrefix(prefix, "/") + return &DefaultNamer{ - prefix: prefix, + prefix: prefix, + hasher: hasher, + signerverifier: signerverifier, + marshaller: marshaller, } } -// GenerateNames generates set of names from basic name. -func (n *DefaultNamer) GenerateNames(name string) []string { - return []string{ - n.prefix + "/" + name, - n.prefix + "/" + hashName + "/" + name, - n.prefix + "/" + sigName + "/" + name, +// GenerateMulti generates all keys for an object names. +func (n *DefaultNamer) GenerateMulti(kvs []kv.KeyValue) Results { + var out Results + + out.result = make(map[string][]Key) + + for _, keyvalue := range kvs { + keys, err := n.Generate(keyvalue) + if err != nil { + continue + } + + out.result[string(keyvalue.Key)] = keys + } + + if len(kvs) == 1 { + out.isSingle = true + out.isSingleName = string(kvs[0].Key) } + + return out } -// ParseNames returns set of Keys with different types. -func (n *DefaultNamer) ParseNames(names []string) []Key { - keys := make([]Key, 0, namesNumber) +// Generate generates all required keys for an object name. +func (n *DefaultNamer) Generate(keyvalue kv.KeyValue) ([]Key, error) { + name := string(keyvalue.Key) + if name == "" { + return nil, ErrInvalidInput + } - for _, name := range names { - var key Key + marshalled, err := n.marshaller.Marshal(keyvalue.Value) + if err != nil { + return nil, fmt.Errorf("failed to marshal: %w", err) + } - // Remove prefix. - result, _ := strings.CutPrefix(name, n.prefix) + hash, err := n.hasher.Hash(marshalled) + if err != nil { + return nil, fmt.Errorf("failed to hash: %w", err) + } - parts := strings.Split(result, "/") + signature, err := n.signerverifier.Sign(hash) + if err != nil { + return nil, fmt.Errorf("failed to sign: %w", err) + } - key.Name = name + valK := NewDefaultKey( + "/"+n.prefix+"/"+name, + KeyTypeValue, + "", + marshalled, + ) + hashK := NewDefaultKey( + "/"+n.prefix+"/"+hashName+"/"+n.hasher.Name()+"/"+name, + KeyTypeHash, + n.hasher.Name(), + hash, + ) + sigK := NewDefaultKey( + "/"+n.prefix+"/"+signatureName+"/"+name, + KeyTypeSignature, + "", + signature, + ) - switch parts[1] { - case hashName: - { - key.Property = "" - key.Type = KeyTypeHash - } - case sigName: - { - key.Property = "" - key.Type = KeyTypeSignature - } + return []Key{valK, hashK, sigK}, nil +} + +// ParseKVMulti convert set of raw KVs to set of KVs with original key/values. +func (n *DefaultNamer) ParseKVMulti(kvs []kv.KeyValue) ([]kv.KeyValue, error) { + var results Results + + out := make([]kv.KeyValue, 0, len(kvs)/keysPerName) + + // Combine keys in threes to get complete set of data to decode the original value. + for _, keyvalue := range kvs { + key, err := n.ParseKV(keyvalue) + if err != nil { + return nil, err + } + + results.result[key.Name()] = append(results.result[key.Name()], key) + } + + // Decoding. + for _, keyset := range results.Items() { + keyvalue, err := n.DecodeKey(keyset) + if err != nil { + return nil, err + } + + out = append(out, keyvalue) + } + + return out, nil +} + +// ParseKV converts KV to Key to combine it by `name`. +func (n *DefaultNamer) ParseKV(keyvalue kv.KeyValue) (Key, error) { + var out Key + + result, cut := strings.CutPrefix(string(keyvalue.Key), n.prefix) + if !cut { + return DefaultKey{}, ErrInvalidPrefix + } + + result, _ = strings.CutPrefix(result, "/") + + parts := strings.Split(result, "/") + partsLen := len(parts) + + switch parts[0] { + case hashName + "/": + out = NewDefaultKey( + parts[partsLen-1], + KeyTypeHash, + parts[partsLen-2], + keyvalue.Value, + ) + case signatureName + "/": + out = NewDefaultKey( + parts[partsLen-1], + KeyTypeSignature, + "", + keyvalue.Value, + ) + default: + out = NewDefaultKey( + parts[partsLen-1], + KeyTypeValue, + "", + keyvalue.Value, + ) + } + + return out, nil +} + +// DecodeKey converts sets of three Keys into original KV. +func (n *DefaultNamer) DecodeKey(keyset []Key) (kv.KeyValue, error) { + var out kv.KeyValue + + var marshalledValue, hash, signature []byte + + for _, key := range keyset { + switch { + case strings.Contains(key.Name(), hashName): + hash = key.Raw() + case strings.Contains(key.Name(), signatureName): + signature = key.Raw() default: - { - key.Property = "" - key.Type = KeyTypeValue - } + out.Key = []byte(key.Name()) + marshalledValue = key.Raw() } + } + + if out.Key == nil || hash == nil || signature == nil { + return out, ErrInvalidKey + } + + err := n.marshaller.Unmarshal(marshalledValue, &out.Value) + if err != nil { + return out, fmt.Errorf("failed to unmarshal: %w", err) + } + + computedHash, err := n.hasher.Hash(out.Key) + if !bytes.Equal(computedHash, hash) || err != nil { + return out, ErrHashMismatch + } - keys = append(keys, key) + err = n.signerverifier.Verify(out.Value, signature) + if err != nil { + return out, fmt.Errorf("signature verification failed: %w", err) } - return keys + return out, nil } diff --git a/namer/namer_test.go b/namer/namer_test.go index 41fb58d..5aaf007 100644 --- a/namer/namer_test.go +++ b/namer/namer_test.go @@ -1,24 +1,69 @@ package namer_test import ( + "crypto/rand" + "crypto/rsa" "testing" "github.com/stretchr/testify/require" + "github.com/tarantool/go-storage/hasher" + "github.com/tarantool/go-storage/kv" + "github.com/tarantool/go-storage/marshaller" "github.com/tarantool/go-storage/namer" + + "github.com/tarantool/go-storage/verification" ) -func TestGenerateNames(t *testing.T) { +func TestGenerate(t *testing.T) { t.Parallel() tests := []struct { - name string prefix string - expected []string + name string + value string + expected []kv.KeyValue }{ { - "all", "/tt/config", - []string{"/tt/config/all", "/tt/config/hash/all", "/tt/config/sig/all"}, + "all", + "value", + []kv.KeyValue{ + { + Key: []byte("/tt/config/all"), + Value: []byte("value"), + ModRevision: 1, + }, + { + Key: []byte("/tt/config/hash/sha256/all"), + Value: []byte{ + 0xa3, 0xc7, 0xb9, 0x70, 0xc5, 0x1b, 0x9b, 0xee, 0x4e, 0xdc, 0x82, 0xfe, 0x2d, 0x79, 0xf8, 0x15, + 0x24, 0x8d, 0xbb, 0x2e, 0x3a, 0x9b, 0x26, 0x63, 0x6e, 0xf2, 0xa3, 0xbe, 0xed, 0xbf, 0xc7, 0x58, + }, + ModRevision: 1, + }, + { + Key: []byte("/tt/config/sig/all"), + Value: []byte{ + 0x2c, 0xd0, 0x10, 0x8d, 0x3b, 0x43, 0x3b, 0x8e, 0x60, 0x22, 0x09, 0x78, 0x86, 0xf2, 0xe1, 0xd1, + 0xcd, 0xea, 0xa0, 0x9b, 0xd0, 0x92, 0xb1, 0xc5, 0x90, 0x3a, 0x85, 0xeb, 0xd2, 0xe8, 0x3f, 0x43, + 0x6d, 0xe2, 0x08, 0x1d, 0xfb, 0xc6, 0xd9, 0x04, 0x6e, 0x2c, 0x11, 0xa7, 0x6f, 0xcb, 0x27, 0x66, + 0x0e, 0x6b, 0xc8, 0xbb, 0xe1, 0x77, 0x33, 0xa3, 0x65, 0x49, 0xbf, 0xc7, 0x6e, 0x18, 0x0e, 0x4d, + 0x0f, 0x20, 0x0b, 0x51, 0xf9, 0x50, 0xdf, 0x76, 0x2d, 0x22, 0x0e, 0xa2, 0x1c, 0xa5, 0xdf, 0x49, + 0xbd, 0x2b, 0xbd, 0xc7, 0xa6, 0x99, 0x8d, 0x55, 0x3b, 0xf8, 0xf4, 0xa2, 0xbf, 0xda, 0xf8, 0xe9, + 0xf7, 0x41, 0xa8, 0xc4, 0xa9, 0x34, 0xef, 0x0b, 0xa4, 0xb5, 0x96, 0x08, 0x60, 0x8c, 0xa1, 0xdb, + 0x4e, 0xd0, 0x5a, 0xed, 0x78, 0x3f, 0x0f, 0x9e, 0x74, 0x09, 0xf9, 0x75, 0x07, 0xbb, 0x5b, 0xe8, + 0xbe, 0x44, 0xbf, 0x07, 0xf7, 0x9f, 0x26, 0x12, 0xb2, 0x02, 0xbf, 0x96, 0xdf, 0x18, 0x35, 0x46, + 0x67, 0x9f, 0x7b, 0xe8, 0xdc, 0xfe, 0x7f, 0xac, 0xad, 0x5b, 0x94, 0x63, 0x0f, 0x1b, 0x03, 0xe6, + 0x3f, 0x80, 0xb8, 0x9a, 0x7f, 0x5a, 0x4e, 0x1c, 0x7b, 0x51, 0x9b, 0x76, 0x3b, 0x50, 0xcd, 0x22, + 0x95, 0xca, 0xf0, 0x0a, 0xac, 0xf8, 0x61, 0xbb, 0x9f, 0xe6, 0x36, 0xe5, 0xde, 0x6c, 0xb8, 0xfb, + 0x3e, 0xb7, 0x94, 0x53, 0x20, 0x1e, 0xbc, 0xfa, 0x0a, 0x6e, 0x29, 0xcd, 0x91, 0xe5, 0xf3, 0xa9, + 0x1a, 0xb2, 0x87, 0x97, 0x6f, 0x93, 0x77, 0x48, 0x08, 0xe3, 0xf9, 0x9d, 0xd3, 0x44, 0x18, 0x82, + 0x58, 0x66, 0xf7, 0x41, 0x64, 0x41, 0x1d, 0xae, 0xef, 0x30, 0x3e, 0x6e, 0xf9, 0xeb, 0x0d, 0xbe, + 0xb8, 0x90, 0x76, 0x79, 0x36, 0x44, 0x60, 0x0d, 0xa2, 0x15, 0xcc, 0xc8, 0x28, 0x0f, 0x5d, 0x36, + }, + ModRevision: 1, + }, + }, }, } @@ -26,14 +71,30 @@ func TestGenerateNames(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - dn := namer.NewDefaultNamer(tt.prefix) + hasher := hasher.NewSHA256Hasher() + + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + require.NoError(t, err) + + sv := verification.NewRSAPSS(*privateKey, privateKey.PublicKey) - result := dn.GenerateNames(tt.name) + mr := marshaller.NewYamlMarshaller() + + dn := namer.NewDefaultNamer(tt.prefix, hasher, sv, mr) + + result := dn.GenerateMulti([]kv.KeyValue{ + { + Key: []byte(tt.name), + Value: []byte(tt.value), + ModRevision: 1, + }}, + ) require.Equal(t, tt.expected, result) }) } } +/* func TestParseNames(t *testing.T) { t.Parallel() @@ -65,4 +126,5 @@ func TestParseNames(t *testing.T) { require.Equal(t, tt.expected, result) }) } -} +}. +*/ diff --git a/crypto/interfaces.go b/verification/interfaces.go similarity index 88% rename from crypto/interfaces.go rename to verification/interfaces.go index 0dcbab3..f9acfe8 100644 --- a/crypto/interfaces.go +++ b/verification/interfaces.go @@ -1,5 +1,5 @@ -// Package crypto implements crypto interfaces. -package crypto +// Package verification implements verification interfaces. +package verification // Signer implements high-level API for package signing. type Signer interface { diff --git a/crypto/rsa.go b/verification/rsa.go similarity index 89% rename from crypto/rsa.go rename to verification/rsa.go index 0f2234e..bdcc3dd 100644 --- a/crypto/rsa.go +++ b/verification/rsa.go @@ -1,4 +1,4 @@ -package crypto +package verification import ( "crypto" @@ -29,12 +29,12 @@ func NewRSAPSS(privKey rsa.PrivateKey, pubKey rsa.PublicKey) RSAPSS { } // Name implements SignerVerifier interface. -func (r *RSAPSS) Name() string { +func (r RSAPSS) Name() string { return "RSASSA-PSS" } // Sign generates SHA-256 digest and signs it using RSASSA-PSS. -func (r *RSAPSS) Sign(data []byte) ([]byte, error) { +func (r RSAPSS) Sign(data []byte) ([]byte, error) { digest, err := r.hasher.Hash(data) if err != nil { return []byte{}, fmt.Errorf("failed to get hash: %w", err) @@ -52,7 +52,7 @@ func (r *RSAPSS) Sign(data []byte) ([]byte, error) { } // Verify compares data with signature. -func (r *RSAPSS) Verify(data []byte, signature []byte) error { +func (r RSAPSS) Verify(data []byte, signature []byte) error { digest, err := r.hasher.Hash(data) if err != nil { return fmt.Errorf("failed to get hash: %w", err) diff --git a/crypto/rsa_test.go b/verification/rsa_test.go similarity index 81% rename from crypto/rsa_test.go rename to verification/rsa_test.go index 3645f35..091f305 100644 --- a/crypto/rsa_test.go +++ b/verification/rsa_test.go @@ -1,4 +1,4 @@ -package crypto_test +package verification_test import ( "crypto/rand" @@ -6,13 +6,14 @@ import ( "testing" "github.com/stretchr/testify/require" - "github.com/tarantool/go-storage/crypto" + + "github.com/tarantool/go-storage/verification" ) func TestRsaWithoutKeys(t *testing.T) { t.Parallel() - rsapss := crypto.NewRSAPSS(rsa.PrivateKey{}, rsa.PublicKey{}) //nolint:exhaustruct + rsapss := verification.NewRSAPSS(rsa.PrivateKey{}, rsa.PublicKey{}) //nolint:exhaustruct require.NotNil(t, rsapss, "rsapss must be returned") data := []byte("abc") @@ -31,7 +32,7 @@ func TestRsaOnlyPrivateKey(t *testing.T) { privateKey, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err) - rsapss := crypto.NewRSAPSS(*privateKey, rsa.PublicKey{}) //nolint:exhaustruct + rsapss := verification.NewRSAPSS(*privateKey, rsa.PublicKey{}) //nolint:exhaustruct require.NotNil(t, rsapss, "rsapss must be returned") data := []byte("abc") @@ -50,7 +51,7 @@ func TestRsaOnlyPublicKey(t *testing.T) { privateKey, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err) - rsapss := crypto.NewRSAPSS(rsa.PrivateKey{}, privateKey.PublicKey) //nolint:exhaustruct + rsapss := verification.NewRSAPSS(rsa.PrivateKey{}, privateKey.PublicKey) //nolint:exhaustruct require.NotNil(t, rsapss, "rsapss must be returned") data := []byte("abc") @@ -60,7 +61,7 @@ func TestRsaOnlyPublicKey(t *testing.T) { require.Nil(t, sig, "signature must be nil") // Re-create to have a valid sign. - rsapss = crypto.NewRSAPSS(*privateKey, privateKey.PublicKey) + rsapss = verification.NewRSAPSS(*privateKey, privateKey.PublicKey) require.NotNil(t, rsapss, "rsapss must be returned") sign, err := rsapss.Sign(data) @@ -77,7 +78,7 @@ func TestRsaSignVerify(t *testing.T) { privateKey, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err) - rsapss := crypto.NewRSAPSS(*privateKey, privateKey.PublicKey) + rsapss := verification.NewRSAPSS(*privateKey, privateKey.PublicKey) require.NotNil(t, rsapss, "rsapss must be returned") data := []byte("abc") From 6f22bf64ccf90f615e37dbb126360643f9afc118 Mon Sep 17 00:00:00 2001 From: Alexey Potapenko Date: Wed, 10 Dec 2025 10:33:45 +0300 Subject: [PATCH 3/3] tests --- namer/namer.go | 32 +++++++++--------- namer/namer_test.go | 82 ++++++++++++++++++++++++++------------------- 2 files changed, 63 insertions(+), 51 deletions(-) diff --git a/namer/namer.go b/namer/namer.go index 21992e7..ab2e551 100644 --- a/namer/namer.go +++ b/namer/namer.go @@ -36,15 +36,15 @@ var ( // Results represents Namer working result. type Results struct { - isSingle bool // True if result contains only one object name. - isSingleName string // Cached name when isSingle=true. - result map[string][]Key // Grouped keys: object name -> key list. + IsSingle bool // True if result contains only one object name. + IsSingleName string // Cached name when isSingle=true. + Result map[string][]Key // Grouped keys: object name -> key list. } // SelectSingle gets keys for single-name case (if applicable). func (r *Results) SelectSingle() ([]Key, bool) { - if r.isSingle { - return r.result[r.isSingleName], true + if r.IsSingle { + return r.Result[r.IsSingleName], true } return nil, false @@ -53,7 +53,7 @@ func (r *Results) SelectSingle() ([]Key, bool) { // Items return iterator over all name->keys groups. func (r *Results) Items() iter.Seq2[string, []Key] { return func(yield func(str string, res []Key) bool) { - for i, v := range r.result { + for i, v := range r.Result { if !yield(i, v) { return } @@ -63,7 +63,7 @@ func (r *Results) Items() iter.Seq2[string, []Key] { // Select gets keys for a specific object name. func (r *Results) Select(name string) ([]Key, bool) { - if i, ok := r.result[name]; ok { + if i, ok := r.Result[name]; ok { return i, true } @@ -72,7 +72,7 @@ func (r *Results) Select(name string) ([]Key, bool) { // Len returns the number of unique object names. func (r *Results) Len() int { - return len(r.result) + return len(r.Result) } //----------------------------------------------------------------------------- @@ -103,7 +103,7 @@ func NewDefaultNamer(prefix string, hasher hasher.Hasher, signerverifier verific func (n *DefaultNamer) GenerateMulti(kvs []kv.KeyValue) Results { var out Results - out.result = make(map[string][]Key) + out.Result = make(map[string][]Key) for _, keyvalue := range kvs { keys, err := n.Generate(keyvalue) @@ -111,12 +111,12 @@ func (n *DefaultNamer) GenerateMulti(kvs []kv.KeyValue) Results { continue } - out.result[string(keyvalue.Key)] = keys + out.Result[string(keyvalue.Key)] = keys } if len(kvs) == 1 { - out.isSingle = true - out.isSingleName = string(kvs[0].Key) + out.IsSingle = true + out.IsSingleName = string(kvs[0].Key) } return out @@ -168,10 +168,10 @@ func (n *DefaultNamer) Generate(keyvalue kv.KeyValue) ([]Key, error) { // ParseKVMulti convert set of raw KVs to set of KVs with original key/values. func (n *DefaultNamer) ParseKVMulti(kvs []kv.KeyValue) ([]kv.KeyValue, error) { - var results Results - out := make([]kv.KeyValue, 0, len(kvs)/keysPerName) + var temp_results Results + // Combine keys in threes to get complete set of data to decode the original value. for _, keyvalue := range kvs { key, err := n.ParseKV(keyvalue) @@ -179,11 +179,11 @@ func (n *DefaultNamer) ParseKVMulti(kvs []kv.KeyValue) ([]kv.KeyValue, error) { return nil, err } - results.result[key.Name()] = append(results.result[key.Name()], key) + temp_results.Result[key.Name()] = append(temp_results.Result[key.Name()], key) } // Decoding. - for _, keyset := range results.Items() { + for _, keyset := range temp_results.Items() { keyvalue, err := n.DecodeKey(keyset) if err != nil { return nil, err diff --git a/namer/namer_test.go b/namer/namer_test.go index 5aaf007..e8ea606 100644 --- a/namer/namer_test.go +++ b/namer/namer_test.go @@ -21,47 +21,59 @@ func TestGenerate(t *testing.T) { prefix string name string value string - expected []kv.KeyValue + expected namer.Results }{ { "/tt/config", "all", "value", - []kv.KeyValue{ - { - Key: []byte("/tt/config/all"), - Value: []byte("value"), - ModRevision: 1, - }, - { - Key: []byte("/tt/config/hash/sha256/all"), - Value: []byte{ - 0xa3, 0xc7, 0xb9, 0x70, 0xc5, 0x1b, 0x9b, 0xee, 0x4e, 0xdc, 0x82, 0xfe, 0x2d, 0x79, 0xf8, 0x15, - 0x24, 0x8d, 0xbb, 0x2e, 0x3a, 0x9b, 0x26, 0x63, 0x6e, 0xf2, 0xa3, 0xbe, 0xed, 0xbf, 0xc7, 0x58, + namer.Results{ + IsSingle: true, + IsSingleName: "all", + Result: map[string][]namer.Key{ + "all": []namer.Key{ + namer.DefaultKey{ + name: "/tt/config/all", + keytype: 1, + property: "", + raw: []uint8{ + 0x2d, 0x20, 0x31, 0x31, 0x38, 0xa, 0x2d, 0x20, 0x39, 0x37, 0xa, 0x2d, 0x20, 0x31, 0x30, 0x38, + 0xa, 0x2d, 0x20, 0x31, 0x31, 0x37, 0xa, 0x2d, 0x20, 0x31, 0x30, 0x31, 0xa, + }, + }, + namer.DefaultKey{ + name: "/tt/config/hash/sha256/all", + keytype: 2, + property: "sha256", + raw: []uint8{ + 0xa3, 0xc7, 0xb9, 0x70, 0xc5, 0x1b, 0x9b, 0xee, 0x4e, 0xdc, 0x82, 0xfe, 0x2d, 0x79, 0xf8, 0x15, + 0x24, 0x8d, 0xbb, 0x2e, 0x3a, 0x9b, 0x26, 0x63, 0x6e, 0xf2, 0xa3, 0xbe, 0xed, 0xbf, 0xc7, 0x58, + }, + }, + namer.DefaultKey{ + name: "/tt/config/sig/all", + keytype: 3, + property: "", + raw: []uint8{ + 0x2d, 0x38, 0x3b, 0xa1, 0x4a, 0xd3, 0x57, 0x18, 0x21, 0x63, 0x80, 0x2b, 0x99, 0xcc, 0xf1, 0x0f, + 0x4f, 0x7d, 0x7e, 0xb2, 0x23, 0x89, 0xc9, 0x8c, 0xb6, 0x73, 0xd0, 0x8e, 0x37, 0x98, 0x7f, 0xef, + 0xfb, 0x70, 0x1c, 0x25, 0x7d, 0x68, 0x03, 0x44, 0x73, 0x14, 0xe8, 0x3e, 0x33, 0x3d, 0x9d, 0x79, + 0xec, 0x83, 0x95, 0x63, 0x68, 0x79, 0xa3, 0xc1, 0x23, 0x32, 0x31, 0x18, 0x94, 0xb3, 0xad, 0x9a, + 0x16, 0x67, 0xdb, 0x58, 0x28, 0x6e, 0x95, 0x4c, 0x7e, 0x7b, 0xe8, 0xbf, 0x2e, 0x44, 0x31, 0xd8, + 0x4a, 0x62, 0x26, 0x5d, 0x6c, 0xb6, 0x67, 0x05, 0xcf, 0xaf, 0x22, 0x44, 0xef, 0x0c, 0x32, 0x45, + 0xf4, 0xcd, 0xf0, 0x31, 0xb9, 0xa3, 0x90, 0xb1, 0x0a, 0x4e, 0xe3, 0x16, 0x58, 0x81, 0xf5, 0x69, + 0x58, 0x3c, 0x1d, 0x67, 0x0b, 0x65, 0x0d, 0x38, 0x06, 0x87, 0xb5, 0x5c, 0x0a, 0x74, 0x84, 0xf0, + 0x6d, 0xa1, 0x20, 0xb1, 0x12, 0x81, 0xb4, 0xcb, 0xd3, 0x65, 0x2a, 0x3d, 0x4c, 0xf3, 0x8d, 0x9b, + 0x51, 0x68, 0x18, 0x3e, 0xbf, 0x78, 0x22, 0x33, 0xca, 0x20, 0x55, 0x24, 0xba, 0x57, 0x2b, 0xcf, + 0x43, 0xd0, 0x38, 0xbd, 0x51, 0x42, 0xef, 0x46, 0x45, 0x94, 0xde, 0xca, 0xda, 0x04, 0x2e, 0xc2, + 0x61, 0x54, 0xf7, 0xb3, 0xfd, 0x33, 0xd6, 0x1b, 0x48, 0xd3, 0x9a, 0x08, 0xfe, 0xad, 0xfb, 0x0a, + 0x7b, 0x1b, 0x8d, 0xd5, 0x9b, 0xe9, 0xfe, 0xe1, 0x69, 0x1c, 0xad, 0xf4, 0xa0, 0x7b, 0xad, 0xe6, + 0x53, 0x32, 0xc2, 0x37, 0x62, 0x3d, 0xab, 0xcd, 0x18, 0x82, 0x3a, 0xc6, 0x8b, 0x12, 0xd1, 0x28, + 0xe1, 0x72, 0xb1, 0xf9, 0x5a, 0x31, 0xb8, 0xa1, 0x72, 0x54, 0xbf, 0x4d, 0xc8, 0xa7, 0x07, 0xf5, + 0x05, 0xf0, 0x67, 0xeb, 0x32, 0xda, 0x41, 0x82, 0xeb, 0xdb, 0xcb, 0x96, 0xfe, 0x95, 0x19, 0x8e, + }, + }, }, - ModRevision: 1, - }, - { - Key: []byte("/tt/config/sig/all"), - Value: []byte{ - 0x2c, 0xd0, 0x10, 0x8d, 0x3b, 0x43, 0x3b, 0x8e, 0x60, 0x22, 0x09, 0x78, 0x86, 0xf2, 0xe1, 0xd1, - 0xcd, 0xea, 0xa0, 0x9b, 0xd0, 0x92, 0xb1, 0xc5, 0x90, 0x3a, 0x85, 0xeb, 0xd2, 0xe8, 0x3f, 0x43, - 0x6d, 0xe2, 0x08, 0x1d, 0xfb, 0xc6, 0xd9, 0x04, 0x6e, 0x2c, 0x11, 0xa7, 0x6f, 0xcb, 0x27, 0x66, - 0x0e, 0x6b, 0xc8, 0xbb, 0xe1, 0x77, 0x33, 0xa3, 0x65, 0x49, 0xbf, 0xc7, 0x6e, 0x18, 0x0e, 0x4d, - 0x0f, 0x20, 0x0b, 0x51, 0xf9, 0x50, 0xdf, 0x76, 0x2d, 0x22, 0x0e, 0xa2, 0x1c, 0xa5, 0xdf, 0x49, - 0xbd, 0x2b, 0xbd, 0xc7, 0xa6, 0x99, 0x8d, 0x55, 0x3b, 0xf8, 0xf4, 0xa2, 0xbf, 0xda, 0xf8, 0xe9, - 0xf7, 0x41, 0xa8, 0xc4, 0xa9, 0x34, 0xef, 0x0b, 0xa4, 0xb5, 0x96, 0x08, 0x60, 0x8c, 0xa1, 0xdb, - 0x4e, 0xd0, 0x5a, 0xed, 0x78, 0x3f, 0x0f, 0x9e, 0x74, 0x09, 0xf9, 0x75, 0x07, 0xbb, 0x5b, 0xe8, - 0xbe, 0x44, 0xbf, 0x07, 0xf7, 0x9f, 0x26, 0x12, 0xb2, 0x02, 0xbf, 0x96, 0xdf, 0x18, 0x35, 0x46, - 0x67, 0x9f, 0x7b, 0xe8, 0xdc, 0xfe, 0x7f, 0xac, 0xad, 0x5b, 0x94, 0x63, 0x0f, 0x1b, 0x03, 0xe6, - 0x3f, 0x80, 0xb8, 0x9a, 0x7f, 0x5a, 0x4e, 0x1c, 0x7b, 0x51, 0x9b, 0x76, 0x3b, 0x50, 0xcd, 0x22, - 0x95, 0xca, 0xf0, 0x0a, 0xac, 0xf8, 0x61, 0xbb, 0x9f, 0xe6, 0x36, 0xe5, 0xde, 0x6c, 0xb8, 0xfb, - 0x3e, 0xb7, 0x94, 0x53, 0x20, 0x1e, 0xbc, 0xfa, 0x0a, 0x6e, 0x29, 0xcd, 0x91, 0xe5, 0xf3, 0xa9, - 0x1a, 0xb2, 0x87, 0x97, 0x6f, 0x93, 0x77, 0x48, 0x08, 0xe3, 0xf9, 0x9d, 0xd3, 0x44, 0x18, 0x82, - 0x58, 0x66, 0xf7, 0x41, 0x64, 0x41, 0x1d, 0xae, 0xef, 0x30, 0x3e, 0x6e, 0xf9, 0xeb, 0x0d, 0xbe, - 0xb8, 0x90, 0x76, 0x79, 0x36, 0x44, 0x60, 0x0d, 0xa2, 0x15, 0xcc, 0xc8, 0x28, 0x0f, 0x5d, 0x36, - }, - ModRevision: 1, }, }, },