264 lines
6.8 KiB
Go
264 lines
6.8 KiB
Go
|
package yangmodel
|
||
|
|
||
|
import (
|
||
|
"encoding/base64"
|
||
|
"encoding/binary"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"math"
|
||
|
"os"
|
||
|
"path/filepath"
|
||
|
"strconv"
|
||
|
|
||
|
"github.com/openconfig/goyang/pkg/yang"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
ErrInsufficientData = errors.New("insufficient data")
|
||
|
ErrNotFound = errors.New("no such node")
|
||
|
)
|
||
|
|
||
|
type Decoder struct {
|
||
|
modules map[string]*yang.Module
|
||
|
rootNodes map[string][]yang.Node
|
||
|
}
|
||
|
|
||
|
func NewDecoder(paths ...string) (*Decoder, error) {
|
||
|
modules := yang.NewModules()
|
||
|
modules.ParseOptions.IgnoreSubmoduleCircularDependencies = true
|
||
|
|
||
|
var moduleFiles []string
|
||
|
modulePaths := paths
|
||
|
unresolved := paths
|
||
|
for {
|
||
|
var newlyfound []string
|
||
|
for _, path := range unresolved {
|
||
|
entries, err := os.ReadDir(path)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("reading directory %q failed: %w", path, err)
|
||
|
}
|
||
|
for _, entry := range entries {
|
||
|
info, err := entry.Info()
|
||
|
if err != nil {
|
||
|
fmt.Printf("Couldn't get info for %q: %v", entry.Name(), err)
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
if info.Mode()&os.ModeSymlink != 0 {
|
||
|
target, err := filepath.EvalSymlinks(entry.Name())
|
||
|
if err != nil {
|
||
|
fmt.Printf("Couldn't evaluate symbolic links for %q: %v", entry.Name(), err)
|
||
|
continue
|
||
|
}
|
||
|
info, err = os.Lstat(target)
|
||
|
if err != nil {
|
||
|
fmt.Printf("Couldn't stat target %v: %v", target, err)
|
||
|
continue
|
||
|
}
|
||
|
}
|
||
|
|
||
|
newPath := filepath.Join(path, info.Name())
|
||
|
if info.IsDir() {
|
||
|
newlyfound = append(newlyfound, newPath)
|
||
|
continue
|
||
|
}
|
||
|
if info.Mode().IsRegular() && filepath.Ext(info.Name()) == ".yang" {
|
||
|
moduleFiles = append(moduleFiles, info.Name())
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if len(newlyfound) == 0 {
|
||
|
break
|
||
|
}
|
||
|
|
||
|
modulePaths = append(modulePaths, newlyfound...)
|
||
|
unresolved = newlyfound
|
||
|
}
|
||
|
|
||
|
// Add the module paths
|
||
|
modules.AddPath(modulePaths...)
|
||
|
for _, fn := range moduleFiles {
|
||
|
if err := modules.Read(fn); err != nil {
|
||
|
fmt.Printf("reading file %q failed: %v\n", fn, err)
|
||
|
}
|
||
|
}
|
||
|
if errs := modules.Process(); len(errs) > 0 {
|
||
|
return nil, errors.Join(errs...)
|
||
|
}
|
||
|
|
||
|
// Get all root nodes defined in models with their origin. We require
|
||
|
// those nodes to later resolve paths to YANG model leaf nodes...
|
||
|
moduleLUT := make(map[string]*yang.Module)
|
||
|
moduleRootNodes := make(map[string][]yang.Node)
|
||
|
for _, m := range modules.Modules {
|
||
|
// Check if we processed the module already
|
||
|
if _, found := moduleLUT[m.Name]; found {
|
||
|
continue
|
||
|
}
|
||
|
// Create a module mapping for easily finding modules by name
|
||
|
moduleLUT[m.Name] = m
|
||
|
|
||
|
// Determine the origin defined in the module
|
||
|
var prefix string
|
||
|
for _, imp := range m.Import {
|
||
|
if imp.Name == "openconfig-extensions" {
|
||
|
prefix = imp.Name
|
||
|
if imp.Prefix != nil {
|
||
|
prefix = imp.Prefix.Name
|
||
|
}
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var moduleOrigin string
|
||
|
if prefix != "" {
|
||
|
for _, e := range m.Extensions {
|
||
|
if e.Keyword == prefix+":origin" || e.Keyword == "origin" {
|
||
|
moduleOrigin = e.Argument
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
for _, u := range m.Uses {
|
||
|
root, err := yang.FindNode(m, u.Name)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
moduleRootNodes[moduleOrigin] = append(moduleRootNodes[moduleOrigin], root)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return &Decoder{modules: moduleLUT, rootNodes: moduleRootNodes}, nil
|
||
|
}
|
||
|
|
||
|
func (d *Decoder) FindLeaf(name, identifier string) (*yang.Leaf, error) {
|
||
|
// Get module name from the element
|
||
|
module, found := d.modules[name]
|
||
|
if !found {
|
||
|
return nil, fmt.Errorf("cannot find module %q", name)
|
||
|
}
|
||
|
|
||
|
for _, grp := range module.Grouping {
|
||
|
for _, leaf := range grp.Leaf {
|
||
|
if leaf.Name == identifier {
|
||
|
return leaf, nil
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return nil, ErrNotFound
|
||
|
}
|
||
|
|
||
|
func DecodeLeafValue(leaf *yang.Leaf, value interface{}) (interface{}, error) {
|
||
|
schema := leaf.Type.YangType
|
||
|
|
||
|
// Ignore all non-string values as the types seem already converted...
|
||
|
s, ok := value.(string)
|
||
|
if !ok {
|
||
|
return value, nil
|
||
|
}
|
||
|
|
||
|
switch schema.Kind {
|
||
|
case yang.Ybinary:
|
||
|
// Binary values are encodes as base64 string, so decode the string
|
||
|
raw, err := base64.StdEncoding.DecodeString(s)
|
||
|
if err != nil {
|
||
|
return value, err
|
||
|
}
|
||
|
|
||
|
switch schema.Name {
|
||
|
case "ieeefloat32":
|
||
|
if len(raw) != 4 {
|
||
|
return raw, fmt.Errorf("%w, expected 4 but got %d bytes", ErrInsufficientData, len(raw))
|
||
|
}
|
||
|
return math.Float32frombits(binary.BigEndian.Uint32(raw)), nil
|
||
|
default:
|
||
|
return raw, nil
|
||
|
}
|
||
|
case yang.Yint8:
|
||
|
v, err := strconv.ParseInt(s, 10, 8)
|
||
|
if err != nil {
|
||
|
return value, fmt.Errorf("parsing %s %q failed: %w", yang.TypeKindToName[schema.Kind], s, err)
|
||
|
}
|
||
|
return int8(v), nil
|
||
|
case yang.Yint16:
|
||
|
v, err := strconv.ParseInt(s, 10, 16)
|
||
|
if err != nil {
|
||
|
return value, fmt.Errorf("parsing %s %q failed: %w", yang.TypeKindToName[schema.Kind], s, err)
|
||
|
}
|
||
|
return int16(v), nil
|
||
|
case yang.Yint32:
|
||
|
v, err := strconv.ParseInt(s, 10, 32)
|
||
|
if err != nil {
|
||
|
return value, fmt.Errorf("parsing %s %q failed: %w", yang.TypeKindToName[schema.Kind], s, err)
|
||
|
}
|
||
|
return int32(v), nil
|
||
|
case yang.Yint64:
|
||
|
v, err := strconv.ParseInt(s, 10, 64)
|
||
|
if err != nil {
|
||
|
return value, fmt.Errorf("parsing %s %q failed: %w", yang.TypeKindToName[schema.Kind], s, err)
|
||
|
}
|
||
|
return v, nil
|
||
|
case yang.Yuint8:
|
||
|
v, err := strconv.ParseUint(s, 10, 8)
|
||
|
if err != nil {
|
||
|
return value, fmt.Errorf("parsing %s %q failed: %w", yang.TypeKindToName[schema.Kind], s, err)
|
||
|
}
|
||
|
return uint8(v), nil
|
||
|
case yang.Yuint16:
|
||
|
v, err := strconv.ParseUint(s, 10, 16)
|
||
|
if err != nil {
|
||
|
return value, fmt.Errorf("parsing %s %q failed: %w", yang.TypeKindToName[schema.Kind], s, err)
|
||
|
}
|
||
|
return uint16(v), nil
|
||
|
case yang.Yuint32:
|
||
|
v, err := strconv.ParseUint(s, 10, 32)
|
||
|
if err != nil {
|
||
|
return value, fmt.Errorf("parsing %s %q failed: %w", yang.TypeKindToName[schema.Kind], s, err)
|
||
|
}
|
||
|
return uint32(v), nil
|
||
|
case yang.Yuint64:
|
||
|
v, err := strconv.ParseUint(s, 10, 64)
|
||
|
if err != nil {
|
||
|
return value, fmt.Errorf("parsing %s %q failed: %w", yang.TypeKindToName[schema.Kind], s, err)
|
||
|
}
|
||
|
return v, nil
|
||
|
case yang.Ydecimal64:
|
||
|
v, err := strconv.ParseFloat(s, 64)
|
||
|
if err != nil {
|
||
|
return value, fmt.Errorf("parsing %s %q failed: %w", yang.TypeKindToName[schema.Kind], s, err)
|
||
|
}
|
||
|
return v, nil
|
||
|
}
|
||
|
return value, nil
|
||
|
}
|
||
|
|
||
|
func (d *Decoder) DecodeLeafElement(namespace, identifier string, value interface{}) (interface{}, error) {
|
||
|
leaf, err := d.FindLeaf(namespace, identifier)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("finding %s failed: %w", identifier, err)
|
||
|
}
|
||
|
|
||
|
return DecodeLeafValue(leaf, value)
|
||
|
}
|
||
|
|
||
|
func (d *Decoder) DecodePathElement(origin, path string, value interface{}) (interface{}, error) {
|
||
|
rootNodes, found := d.rootNodes[origin]
|
||
|
if !found || len(rootNodes) == 0 {
|
||
|
return value, nil
|
||
|
}
|
||
|
|
||
|
for _, root := range rootNodes {
|
||
|
node, err := yang.FindNode(root, path)
|
||
|
if node == nil || err != nil {
|
||
|
// The path does not exist in this root node
|
||
|
continue
|
||
|
}
|
||
|
// We do expect a leaf node...
|
||
|
if leaf, ok := node.(*yang.Leaf); ok {
|
||
|
return DecodeLeafValue(leaf, value)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return value, nil
|
||
|
}
|