func (mib *MIB) Symbol(oid OID) (*Symbol, OID) {
	sym := mib.Root
	var prev *Symbol
	for i := 0; ; {
		if sym != nil && sym.ID == oid[i] {
			i++
		} else {
			return prev, oid[i:]
		}
		if i < len(oid) {
			child := sym.ChildByID[oid[i]]
			prev = sym
			sym = child
		} else {
			return sym, OID{}
		}
	}
}
Symbol returns the Symbol and an OID index for the specified OID.
package smi
import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
)
type MIB struct {
	Modules   map[string]*Module
	Root      *Symbol
	Symbols   map[string]*Symbol
	Debug     bool
	dirs      []string
	loadOrder []string
}
type parentRef struct {
	Label string
	Child *Symbol
}
var replacementModule = map[string]string{
	"RFC1155-SMI": "SNMPv2-SMI",
	"RFC-1212":    "SNMPv2-SMI",
	"RFC1212":     "SNMPv2-SMI",
	"RFC-1213":    "SNMPv2-MIB",
	"RFC1213-MIB": "SNMPv2-MIB",
	"RFC1271-MIB": "RMON-MIB",
	"RFC1316-MIB": "CHARACTER-MIB",
}
func NewMIB(dirs ...string) *MIB {
	mib := &MIB{
		dirs:    dirs,
		Modules: make(map[string]*Module),
		Symbols: make(map[string]*Symbol),
	}
	root := Symbol{
		Name:         "iso",
		ID:           1,
		Module:       nil,
		Parent:       nil,
		ChildByLabel: make(map[string]*Symbol),
		ChildByID:    make(map[int]*Symbol),
	}
	mib.Root = &root
	mib.Symbols[root.Name] = &root
	return mib
}
func (mib *MIB) LoadModules(modNames ...string) error {
	err := mib.scanDirs()
	if err != nil {
		return err
	}
	if len(modNames) == 0 {
		modNames = make([]string, 0, len(mib.Modules))
		for name := range mib.Modules {
			modNames = append(modNames, name)
		}
		sort.SliceStable(modNames, func(i, j int) bool {
			return modNames[i] < modNames[j]
		})
	}
	for _, modName := range modNames {
		err := mib.loadModule(modName)
		if err != nil {
			return err
		}
	}
	return mib.indexModules()
}
func (mib *MIB) addSymbol(sym *Symbol) bool {
	if oldSym, ok := mib.Symbols[sym.Name]; ok {
		if mib.Debug {
			log.Printf("imported symbol %v duplicates name of %v, ignoring", sym, oldSym)
		}
		return false
	}
	mib.Symbols[sym.Name] = sym
	return true
}
func (mib *MIB) indexModules() error {
	for _, modName := range mib.loadOrder {
		mod, ok := mib.Modules[modName]
		if !ok {
			return fmt.Errorf("indexing: module not found: %s", modName)
		}
		if !mod.IsLoaded {
			return fmt.Errorf("indexing: module not loaded: %s", modName)
		}
		var unresolved []parentRef
		for _, n := range mod.Nodes {
			if len(n.IDs) < 2 {
				return fmt.Errorf("%s: unknown IDs format: %v", modName, n.IDs)
			}
			parentLabel := n.IDs[0].Label
			if parentLabel == "" {
				if len(n.IDs) == 2 && n.IDs[0].ID == 0 && n.IDs[1].ID == 0 {
					continue
				}
				return fmt.Errorf("%s: expected parent symbol: %v", modName, n.IDs)
			}
			parent := mib.findSymbol(mod, parentLabel)
			for i := 1; i < len(n.IDs); i++ {
				id := n.IDs[i].ID
				if id == -1 {
					return fmt.Errorf("%s: expected numeric index: %v", modName, n.IDs)
				}
				var label string
				if i < len(n.IDs)-1 {
					label = ""
				} else {
					label = n.Label
				}
				sym := &Symbol{
					Name:         label,
					ID:           id,
					Module:       mod,
					Parent:       parent,
					ChildByLabel: make(map[string]*Symbol),
					ChildByID:    make(map[int]*Symbol),
				}
				if sym.Name != "" {
					mod.Symbols[sym.Name] = sym
					if !mib.addSymbol(sym) {
						continue
					}
				}
				if parent == nil {
					unresolved = append(unresolved, parentRef{Label: parentLabel, Child: sym})
				} else {
					parent.ChildByLabel[sym.Name] = sym
					parent.ChildByID[sym.ID] = sym
				}
				parent = sym
			}
		}
		for _, ref := range unresolved {
			parent := mib.findSymbol(mod, ref.Label)
			sym := ref.Child
			if parent == nil {
				return fmt.Errorf("%s: cannot resolve symbol %v, parent of %s", modName, ref.Label, sym.Name)
			}
			parent.ChildByLabel[sym.Name] = sym
			parent.ChildByID[sym.ID] = sym
		}
	}
	return nil
}
func (mib *MIB) findSymbol(mod *Module, label string) *Symbol {
	if sym, ok := mod.Symbols[label]; ok {
		return sym
	}
	for _, imp := range mod.Imports {
		for _, impLabel := range imp.Symbols {
			if label == impLabel {
				importName := imp.From
				if newName, ok := replacementModule[importName]; ok {
					importName = newName
				}
				impMod := mib.Modules[importName]
				if impMod == nil {
					if mib.Debug {
						log.Printf("imported module not found: %s", imp.From)
					}
					return nil
				}
				return mib.findSymbol(impMod, label)
			}
		}
	}
	if sym, ok := mib.Symbols[label]; ok {
		return sym
	}
	return nil
}
func (mib *MIB) loadModule(modName string) error {
	if newName, ok := replacementModule[modName]; ok {
		modName = newName
	}
	mod := mib.Modules[modName]
	if mod == nil {
		return fmt.Errorf("loading: module not found: %s", modName)
	}
	if mod.IsLoaded {
		return nil
	}
	parsedMod, err := ParseModule(mod.File)
	if err != nil {
		return err
	}
	if mod.Name != parsedMod.Name {
		return fmt.Errorf("found module %s in file %s, expected %s", parsedMod.Name, mod.File, mod.Name)
	}
	mod.Nodes = parsedMod.Nodes
	mod.Imports = parsedMod.Imports
	mod.IsLoaded = true
	mod.Symbols = make(map[string]*Symbol)
	err = mib.loadImports(mod.Imports)
	if err != nil {
		return fmt.Errorf("loading imports for %s: %v", modName, err)
	}
	mib.loadOrder = append(mib.loadOrder, modName)
	return nil
}
func (mib *MIB) loadImports(imports []Import) error {
	for _, imp := range imports {
		if len(imp.Symbols) > 0 {
			err := mib.loadModule(imp.From)
			if err != nil {
				return err
			}
		}
	}
	return nil
}
func (mib *MIB) scanDirs() error {
	scanMods := make(map[string]*Module)
	for _, dirname := range mib.dirs {
		if fi, err := os.Stat(dirname); !os.IsNotExist(err) {
			if fi.IsDir() {
				err = scanDir(dirname, &scanMods)
				if err != nil {
					return err
				}
			}
		}
	}
	for modName, mod := range mib.Modules {
		if newMod, ok := scanMods[modName]; ok {
			if mod.File != newMod.File {
				if mib.Debug {
					log.Printf("module %s: replacing %s with %s", modName, mod.File, newMod.File)
				}
				mib.Modules[modName] = newMod
			}
			delete(scanMods, modName)
		} else {
			delete(mib.Modules, modName)
		}
	}
	for modName, mod := range scanMods {
		mib.Modules[modName] = mod
	}
	return nil
}
func scanDir(dirname string, scanMods *map[string]*Module) error {
	files, err := ioutil.ReadDir(dirname)
	if err != nil {
		return err
	}
	for _, fi := range files {
		absPath, err := filepath.Abs(filepath.Join(dirname, fi.Name()))
		if err != nil {
			return err
		}
		if fi.IsDir() {
			continue
		}
		if moduleName, err := ModuleName(absPath); err == nil {
			(*scanMods)[moduleName] = &Module{Name: moduleName, File: absPath}
		} else {
			if _, ok := err.(NotAModuleError); !ok {
				return err
			}
		}
	}
	return nil
}