forked from TrueCloudLab/frostfs-node
2d7e9f0c40
Attributes are linked to each other through parents, so they can be returned in any order. However, it will be better to return the list in consistent order to reduce entropy. Signed-off-by: Alex Vanin <alexey@nspcc.ru>
121 lines
2.8 KiB
Go
121 lines
2.8 KiB
Go
package attributes
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/nspcc-dev/neofs-api-go/pkg/netmap"
|
|
)
|
|
|
|
const (
|
|
pairSeparator = "/"
|
|
keyValueSeparator = ":"
|
|
)
|
|
|
|
var (
|
|
errEmptyChain = errors.New("empty attribute chain")
|
|
errNonUniqueBucket = errors.New("attributes must contain unique keys")
|
|
errUnexpectedKey = errors.New("attributes contain unexpected key")
|
|
)
|
|
|
|
// ParseV2Attributes parses strings like "K1:V1/K2:V2/K3:V3" into netmap
|
|
// attributes. Supports escaped symbols "\:", "\/" and "\\".
|
|
func ParseV2Attributes(attrs []string, excl []string) ([]*netmap.NodeAttribute, error) {
|
|
restricted := make(map[string]struct{}, len(excl))
|
|
for i := range excl {
|
|
restricted[excl[i]] = struct{}{}
|
|
}
|
|
|
|
cache := make(map[string]*netmap.NodeAttribute, len(attrs))
|
|
result := make([]*netmap.NodeAttribute, 0, len(attrs))
|
|
|
|
for i := range attrs {
|
|
line := strings.Trim(attrs[i], pairSeparator)
|
|
line = replaceEscaping(line, false) // replaced escaped symbols with non-printable symbols
|
|
chain := strings.Split(line, pairSeparator)
|
|
if len(chain) == 0 {
|
|
return nil, errEmptyChain
|
|
}
|
|
|
|
var parentKey string // backtrack parents in next pairs
|
|
|
|
for j := range chain {
|
|
pair := strings.Split(chain[j], keyValueSeparator)
|
|
if len(pair) != 2 {
|
|
return nil, fmt.Errorf("incorrect attribute pair %s", chain[j])
|
|
}
|
|
|
|
key := pair[0]
|
|
value := pair[1]
|
|
|
|
attribute, present := cache[key]
|
|
if present && attribute.Value() != value {
|
|
return nil, errNonUniqueBucket
|
|
}
|
|
|
|
if _, ok := restricted[key]; ok {
|
|
return nil, errUnexpectedKey
|
|
}
|
|
|
|
if !present {
|
|
// replace non-printable symbols with escaped symbols without escape character
|
|
key = replaceEscaping(key, true)
|
|
value = replaceEscaping(value, true)
|
|
|
|
attribute = netmap.NewNodeAttribute()
|
|
attribute.SetKey(key)
|
|
attribute.SetValue(value)
|
|
|
|
cache[key] = attribute
|
|
result = append(result, attribute)
|
|
}
|
|
|
|
if parentKey != "" {
|
|
parentKeys := attribute.ParentKeys()
|
|
if !hasString(parentKeys, parentKey) {
|
|
attribute.SetParentKeys(append(parentKeys, parentKey)...)
|
|
}
|
|
}
|
|
|
|
parentKey = key
|
|
}
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func replaceEscaping(target string, rollback bool) (s string) {
|
|
const escChar = `\`
|
|
|
|
var (
|
|
oldPairSep = escChar + pairSeparator
|
|
oldKVSep = escChar + keyValueSeparator
|
|
oldEsc = escChar + escChar
|
|
newPairSep = string(uint8(1))
|
|
newKVSep = string(uint8(2))
|
|
newEsc = string(uint8(3))
|
|
)
|
|
|
|
if rollback {
|
|
oldPairSep, oldKVSep, oldEsc = newPairSep, newKVSep, newEsc
|
|
newPairSep = pairSeparator
|
|
newKVSep = keyValueSeparator
|
|
newEsc = escChar
|
|
}
|
|
|
|
s = strings.ReplaceAll(target, oldEsc, newEsc)
|
|
s = strings.ReplaceAll(s, oldPairSep, newPairSep)
|
|
s = strings.ReplaceAll(s, oldKVSep, newKVSep)
|
|
|
|
return
|
|
}
|
|
|
|
func hasString(lst []string, v string) bool {
|
|
for i := range lst {
|
|
if lst[i] == v {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|