frostfs-node/pkg/util/attributes/parser.go
Alex Vanin 2d7e9f0c40 [#787] util/attributes: Return consistent list of attributes
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>
2021-09-01 14:29:26 +03:00

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
}