递归定义SNMP消息

I am messing around with an SNMP library in Go and came up with a type Field that defines an SNMP BER encoded field according to this document. Each field consists of a type, length, and value, where the type is an ASN.1 type, length is the length of the field's value, and the value can be another field, a sequence of fields, or a sequence of bytes. This made me think about the possibility of recursively defining an SNMP message. Here is some code I came up with, but I'm stuck trying to translate it into a recursive structure:

package main

import "fmt"

// ASN.1 BER encoded types.
type ASN1BER byte

const (
    Integer          ASN1BER = 0x02
    BitString                = 0x03
    OctetString              = 0x04
    Null                     = 0x05
    ObjectIdentifier         = 0x06
    Sequence                 = 0x30
    GetRequest               = 0xa0
)

// SNMP versions.
type Version byte

const (
    Version1  Version = 0x00
    Version2c         = 0x01
)

// SNMP message field.
type Field struct {
    Type  ASN1BER
    Len   int
    Value interface{}
}

func main() {
    // SNMP Message
    msg := &Field{Type: Sequence, Len: 44, Value: []*Field{
        // SNMP Version
        {Type: Integer, Len: 1, Value: Version2c},
        // Community String
        {Type: OctetString, Len: 7, Value: []byte("private")},
        // Get-Request PDU
        {Type: GetRequest, Len: 30, Value: []*Field{
            // Request ID
            {Type: Integer, Len: 1, Value: []byte{0x01}},
            // Error Status
            {Type: Integer, Len: 1, Value: []byte{0x00}},
            // Error Index
            {Type: Integer, Len: 1, Value: []byte{0x00}},
            // Varbind List
            {Type: Sequence, Len: 19, Value: []*Field{
                // Varbind
                {Type: Sequence, Len: 17, Value: []*Field{
                    // OID 1.3.6.1.4.1.2680.1.2.7.3.2.0
                    {Type: ObjectIdentifier, Len: 13, Value: []byte{0x2b, 0x06, 0x01, 0x04, 0x01, 0x94, 0x78, 0x01, 0x02, 0x07, 0x03, 0x02, 0x00}},
                    // Value
                    {Type: Null, Len: 0},
                }}},
            }},
        }},
    }
    fmt.Println(msg)
}

Is it possible to represent the SNMP message recursively? If so, what would be the base case(s) and the recursive case(s)? The goal is to be able to recursively print, encode, and decode SNMP messages.

The solution is to have a type switch which would branch into different code paths depending on the actual type of Value—possibly resursively.

package main

import (
    "fmt"
    "log"
    "reflect"
)

// ASN.1 BER encoded types.
type ASN1BER byte

const (
    Integer          ASN1BER = 0x02
    BitString                = 0x03
    OctetString              = 0x04
    Null                     = 0x05
    ObjectIdentifier         = 0x06
    Sequence                 = 0x30
    GetRequest               = 0xa0
)

// SNMP versions.
type Version byte

const (
    Version1  Version = 0x00
    Version2c         = 0x01
)

// SNMP message field.
type Field struct {
    Type  ASN1BER
    Len   int
    Value interface{}
}

func walk(f *Field, indent string) error {
    switch f.Type {
    case GetRequest, Sequence:
        indent += "\t"
        switch v := f.Value.(type) {
        case *Field:
            return walk(v, indent)
        case []*Field:
            for _, f := range v {
                err := walk(f, indent)
                if err != nil {
                    return err
                }
            }
        default:
            return &ValueTypeError{
                ASNType:   f.Type,
                ValueType: reflect.TypeOf(v),
            }
        }
    default:
        fmt.Printf("%sType: %d; value: %v
", indent, f.Type, f.Value)
    }
    return nil
}

type ValueTypeError struct {
    ASNType   ASN1BER
    ValueType reflect.Type
}

func (e *ValueTypeError) Error() string {
    return fmt.Sprintf("invalid Go type (%s) for ASN1BER type %d",
        e.ValueType.Name(), e.ASNType)
}

func main() {
    // SNMP Message
    msg := Field{Type: Sequence, Len: 44, Value: []*Field{
        // SNMP Version
        {Type: Integer, Len: 1, Value: Version2c},
        // Community String
        {Type: OctetString, Len: 7, Value: []byte("private")},
        // Get-Request PDU
        {Type: GetRequest, Len: 30, Value: []*Field{
            // Request ID
            {Type: Integer, Len: 1, Value: []byte{0x01}},
            // Error Status
            {Type: Integer, Len: 1, Value: []byte{0x00}},
            // Error Index
            {Type: Integer, Len: 1, Value: []byte{0x00}},
            // Varbind List
            {Type: Sequence, Len: 19, Value: []*Field{
                // Varbind
                {Type: Sequence, Len: 17, Value: []*Field{
                    // OID 1.3.6.1.4.1.2680.1.2.7.3.2.0
                    {Type: ObjectIdentifier, Len: 13, Value: []byte{0x2b, 0x06, 0x01, 0x04, 0x01, 0x94, 0x78, 0x01, 0x02, 0x07, 0x03, 0x02, 0x00}},
                    // Value
                    {Type: Null, Len: 0},
                }}},
            }},
        }},
    }

    bad := Field{
        Type:  Sequence,
        Value: 42,
    }

    for i, f := range [...]*Field{&msg, &bad} {
        log.Println("walking:", i)
        err := walk(f, "")
        if err != nil {
            log.Println("error:", err)
        }
    }
}

Playground link.