167 lines
4.1 KiB
Go
167 lines
4.1 KiB
Go
|
package gnmi
|
||
|
|
||
|
import (
|
||
|
"encoding/json"
|
||
|
"fmt"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
|
||
|
"github.com/openconfig/gnmi/proto/gnmi"
|
||
|
"github.com/openconfig/gnmi/value"
|
||
|
)
|
||
|
|
||
|
type keyValuePair struct {
|
||
|
key []string
|
||
|
value interface{}
|
||
|
}
|
||
|
|
||
|
type updateField struct {
|
||
|
path *pathInfo
|
||
|
value interface{}
|
||
|
}
|
||
|
|
||
|
func (h *handler) newFieldsFromUpdate(path *pathInfo, update *gnmi.Update) ([]updateField, error) {
|
||
|
if update.Val == nil || update.Val.Value == nil {
|
||
|
return []updateField{{path: path}}, nil
|
||
|
}
|
||
|
|
||
|
// Apply some special handling for special types
|
||
|
switch v := update.Val.Value.(type) {
|
||
|
case *gnmi.TypedValue_AsciiVal: // not handled in ToScalar
|
||
|
return []updateField{{path, v.AsciiVal}}, nil
|
||
|
case *gnmi.TypedValue_JsonVal: // requires special path handling
|
||
|
return h.processJSON(path, v.JsonVal)
|
||
|
case *gnmi.TypedValue_JsonIetfVal: // requires special path handling
|
||
|
return h.processJSONIETF(path, v.JsonIetfVal)
|
||
|
}
|
||
|
|
||
|
// Convert the protobuf "oneof" data to a Golang type.
|
||
|
nativeType, err := value.ToScalar(update.Val)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return []updateField{{path, nativeType}}, nil
|
||
|
}
|
||
|
|
||
|
func (h *handler) processJSON(path *pathInfo, data []byte) ([]updateField, error) {
|
||
|
var nested interface{}
|
||
|
if err := json.Unmarshal(data, &nested); err != nil {
|
||
|
return nil, fmt.Errorf("failed to parse JSON value: %w", err)
|
||
|
}
|
||
|
|
||
|
// Flatten the JSON data to get a key-value map
|
||
|
entries := flatten(nested)
|
||
|
|
||
|
// Create an update-field with the complete path for all entries
|
||
|
fields := make([]updateField, 0, len(entries))
|
||
|
for _, entry := range entries {
|
||
|
p := path.appendSegments(entry.key...)
|
||
|
if h.enforceFirstNamespaceAsOrigin {
|
||
|
p.enforceFirstNamespaceAsOrigin()
|
||
|
}
|
||
|
|
||
|
fields = append(fields, updateField{
|
||
|
path: p,
|
||
|
value: entry.value,
|
||
|
})
|
||
|
}
|
||
|
|
||
|
return fields, nil
|
||
|
}
|
||
|
|
||
|
func (h *handler) processJSONIETF(path *pathInfo, data []byte) ([]updateField, error) {
|
||
|
var nested interface{}
|
||
|
if err := json.Unmarshal(data, &nested); err != nil {
|
||
|
return nil, fmt.Errorf("failed to parse JSON value: %w", err)
|
||
|
}
|
||
|
|
||
|
// Flatten the JSON data to get a key-value map
|
||
|
entries := flatten(nested)
|
||
|
|
||
|
// Lookup the data in the YANG model if any
|
||
|
if h.decoder != nil {
|
||
|
for i, e := range entries {
|
||
|
var namespace, identifier string
|
||
|
for _, k := range e.key {
|
||
|
if n, _, found := strings.Cut(k, ":"); found {
|
||
|
namespace = n
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// IETF nodes referencing YANG entries require a namespace
|
||
|
if namespace == "" {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
if a, b, found := strings.Cut(e.key[len(e.key)-1], ":"); !found {
|
||
|
identifier = a
|
||
|
} else {
|
||
|
identifier = b
|
||
|
}
|
||
|
|
||
|
if decoded, err := h.decoder.DecodeLeafElement(namespace, identifier, e.value); err != nil {
|
||
|
h.log.Debugf("Decoding %s:%s failed: %v", namespace, identifier, err)
|
||
|
} else {
|
||
|
entries[i].value = decoded
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fields := make([]updateField, 0, len(entries))
|
||
|
for _, entry := range entries {
|
||
|
p := path.appendSegments(entry.key...)
|
||
|
if h.enforceFirstNamespaceAsOrigin {
|
||
|
p.enforceFirstNamespaceAsOrigin()
|
||
|
}
|
||
|
|
||
|
// Try to lookup the full path to decode the field according to the
|
||
|
// YANG model if any
|
||
|
if h.decoder != nil {
|
||
|
origin, fieldPath := p.path()
|
||
|
if decoded, err := h.decoder.DecodePathElement(origin, fieldPath, entry.value); err != nil {
|
||
|
h.log.Debugf("Decoding %s failed: %v", p, err)
|
||
|
} else {
|
||
|
entry.value = decoded
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Create an update-field with the complete path for all entries
|
||
|
fields = append(fields, updateField{
|
||
|
path: p,
|
||
|
value: entry.value,
|
||
|
})
|
||
|
}
|
||
|
|
||
|
return fields, nil
|
||
|
}
|
||
|
|
||
|
func flatten(nested interface{}) []keyValuePair {
|
||
|
var values []keyValuePair
|
||
|
|
||
|
switch n := nested.(type) {
|
||
|
case map[string]interface{}:
|
||
|
for k, child := range n {
|
||
|
for _, c := range flatten(child) {
|
||
|
values = append(values, keyValuePair{
|
||
|
key: append([]string{k}, c.key...),
|
||
|
value: c.value,
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
case []interface{}:
|
||
|
for i, child := range n {
|
||
|
k := strconv.Itoa(i)
|
||
|
for _, c := range flatten(child) {
|
||
|
values = append(values, keyValuePair{
|
||
|
key: append([]string{k}, c.key...),
|
||
|
value: c.value,
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
default:
|
||
|
values = append(values, keyValuePair{value: n})
|
||
|
}
|
||
|
|
||
|
return values
|
||
|
}
|