From f93de0494ace1f7df45cde2ecd2adf87455501a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ch=C3=A1bek?= Date: Fri, 15 Oct 2021 17:24:40 +0200 Subject: [PATCH] Add DateAndTime display hint Also improve peformance: - unsafe string conversion to remove allocation - hex formatting is done manually instead by fmt.Sprintf --- .golangci.yml | 3 + cmd/snmp-proxy/snmp-proxy.go | 3 +- snmpproxy/mib/mib.go | 1 + snmpproxy/mib/netsnmp.go | 2 + snmpproxy/requester.go | 45 ++---------- snmpproxy/requester_test.go | 107 +++++++++-------------------- snmpproxy/value.go | 128 +++++++++++++++++++++++++++++++++++ snmpproxy/value_test.go | 82 ++++++++++++++++++++++ 8 files changed, 253 insertions(+), 118 deletions(-) create mode 100644 snmpproxy/value.go create mode 100644 snmpproxy/value_test.go diff --git a/.golangci.yml b/.golangci.yml index 4a9f98a..596fc32 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -42,6 +42,9 @@ linters: - wsl linters-settings: + exhaustive: + default-signifies-exhaustive: true + goimports: local-prefixes: gitlab.cdn77.eu diff --git a/cmd/snmp-proxy/snmp-proxy.go b/cmd/snmp-proxy/snmp-proxy.go index 8cd322f..68788a5 100644 --- a/cmd/snmp-proxy/snmp-proxy.go +++ b/cmd/snmp-proxy/snmp-proxy.go @@ -32,8 +32,7 @@ func main() { config.Logger.Fatalw("mib parser error: ", zap.Error(err)) } - mibDataProvider := mib.NewDataProvider(displayHints) - requester := snmpproxy.NewGosnmpRequester(mibDataProvider) + requester := snmpproxy.NewGosnmpRequester(snmpproxy.NewValueFormatter(mib.NewDataProvider(displayHints))) apiListener := snmpproxy.NewApiListener( validator, diff --git a/snmpproxy/mib/mib.go b/snmpproxy/mib/mib.go index fedba30..2bdb66f 100644 --- a/snmpproxy/mib/mib.go +++ b/snmpproxy/mib/mib.go @@ -10,6 +10,7 @@ const ( DisplayHintUnknown = DisplayHint(iota) DisplayHintString DisplayHintHexadecimal + DisplayHintDateAndTime ) type DisplayHints map[string]DisplayHint diff --git a/snmpproxy/mib/netsnmp.go b/snmpproxy/mib/netsnmp.go index 39010d7..9c991e6 100644 --- a/snmpproxy/mib/netsnmp.go +++ b/snmpproxy/mib/netsnmp.go @@ -109,6 +109,8 @@ func (p *NetsnmpMibParser) findStringTypesDisplayHints(displayHints DisplayHints displayHints[oid] = DisplayHintString case "PhysAddress": displayHints[oid] = DisplayHintHexadecimal + case "DateAndTime": + displayHints[oid] = DisplayHintDateAndTime } return diff --git a/snmpproxy/requester.go b/snmpproxy/requester.go index 18507ac..21d6e07 100644 --- a/snmpproxy/requester.go +++ b/snmpproxy/requester.go @@ -5,11 +5,8 @@ import ( "fmt" "strconv" "strings" - "unicode" - "unicode/utf8" "github.com/gosnmp/gosnmp" - "github.com/grongor/go-snmp-proxy/snmpproxy/mib" ) type requestResult struct { @@ -23,7 +20,7 @@ type Requester interface { } type GosnmpRequester struct { - mibDataProvider *mib.DataProvider + valueFormatter *ValueFormatter } func (r *GosnmpRequester) ExecuteRequest(apiRequest *ApiRequest) ([][]interface{}, error) { @@ -141,7 +138,7 @@ func (r *GosnmpRequester) processGetPacket(packet *gosnmp.SnmpPacket, request Re return result, fmt.Errorf("end of mib: %s", dataUnit.Name) } - result = append(result, dataUnit.Name, r.getPduValue(dataUnit)) + result = append(result, dataUnit.Name, r.valueFormatter.Format(dataUnit)) } return result, nil @@ -178,7 +175,7 @@ func (r *GosnmpRequester) executeWalk(apiRequest *ApiRequest, requestNo int, res oid := request.Oids[0] err = walker(oid, func(dataUnit gosnmp.SnmpPDU) error { - result.result = append(result.result, dataUnit.Name, r.getPduValue(dataUnit)) + result.result = append(result.result, dataUnit.Name, r.valueFormatter.Format(dataUnit)) return nil }) @@ -250,38 +247,6 @@ func (*GosnmpRequester) createSnmpHandler(apiRequest *ApiRequest) (gosnmp.Handle return snmp, nil } -func (r *GosnmpRequester) getPduValue(dataUnit gosnmp.SnmpPDU) interface{} { - switch dataUnit.Type { //nolint:exhaustive // We only care about gosnmp.OctetString, the rest is just passed along - case gosnmp.OctetString: - displayHint := r.mibDataProvider.GetDisplayHint(dataUnit.Name) - if displayHint == mib.DisplayHintString || - // best effort to display octet strings correctly without the MIBs - displayHint == mib.DisplayHintUnknown && r.isStringPrintable(dataUnit.Value.([]byte)) { - return string(dataUnit.Value.([]byte)) - } - - return fmt.Sprintf("% X", dataUnit.Value) - default: - return dataUnit.Value - } -} - -func (*GosnmpRequester) isStringPrintable(value []byte) bool { - if !utf8.Valid(value) { - return false - } - - for _, b := range value { - if unicode.IsPrint(rune(b)) || unicode.IsSpace(rune(b)) { - continue - } - - return false - } - - return true -} - -func NewGosnmpRequester(mibDataProvider *mib.DataProvider) *GosnmpRequester { - return &GosnmpRequester{mibDataProvider: mibDataProvider} +func NewGosnmpRequester(valueFormatter *ValueFormatter) *GosnmpRequester { + return &GosnmpRequester{valueFormatter: valueFormatter} } diff --git a/snmpproxy/requester_test.go b/snmpproxy/requester_test.go index a9160ab..aad8442 100644 --- a/snmpproxy/requester_test.go +++ b/snmpproxy/requester_test.go @@ -9,7 +9,6 @@ import ( "sync" "testing" "time" - "unicode/utf8" "github.com/gosnmp/gosnmp" "github.com/grongor/go-snmp-proxy/snmpproxy" @@ -82,7 +81,7 @@ func TestGet(t *testing.T) { apiRequest := apiRequest(get([]string{".1.3.6.1.2.1.25.2.3.1.2.1", ".1.3.6.1.2.1.25.2.3.1.2.4"})) - requester := snmpproxy.NewGosnmpRequester(mib.NewDataProvider(nil)) + requester := snmpproxy.NewGosnmpRequester(snmpproxy.NewValueFormatter(mib.NewDataProvider(nil))) result, err := requester.ExecuteRequest(apiRequest) assert.NoError(err) @@ -97,59 +96,12 @@ func TestGet(t *testing.T) { ) } -func TestGetStringDisplayHintGuessedString(t *testing.T) { - assert := require.New(t) - - apiRequest := apiRequest(get([]string{".1.3.6.1.2.1.2.2.1.2.48"})) - - requester := snmpproxy.NewGosnmpRequester(mib.NewDataProvider(nil)) - result, err := requester.ExecuteRequest(apiRequest) - assert.NoError(err) - - assert.Equal( - [][]interface{}{{".1.3.6.1.2.1.2.2.1.2.48", "Ethernet48"}}, - result, - ) -} - -func TestGetStringDisplayHintGuessedHexadecimalBecauseNotUtf8Valid(t *testing.T) { - assert := require.New(t) - - apiRequest := apiRequest(get([]string{".1.3.6.1.2.1.4.22.1.2.2000955.185.152.67.97"})) - - requester := snmpproxy.NewGosnmpRequester(mib.NewDataProvider(nil)) - result, err := requester.ExecuteRequest(apiRequest) - assert.NoError(err) - - assert.Equal( - [][]interface{}{{".1.3.6.1.2.1.4.22.1.2.2000955.185.152.67.97", "91 E2 BA E3 5A 61"}}, - result, - ) -} - -func TestGetStringDisplayHintGuessedHexadecimalBecauseNotPrintable(t *testing.T) { - assert := require.New(t) - - apiRequest := apiRequest(get([]string{".1.3.6.1.2.1.4.22.1.2.2000955.185.152.67.100"})) - - requester := snmpproxy.NewGosnmpRequester(mib.NewDataProvider(nil)) - result, err := requester.ExecuteRequest(apiRequest) - assert.NoError(err) - - assert.True(utf8.Valid([]byte{})) - - assert.Equal( - [][]interface{}{{".1.3.6.1.2.1.4.22.1.2.2000955.185.152.67.100", "53 54 00 4C 5A 5D"}}, - result, - ) -} - func TestGetNext(t *testing.T) { assert := require.New(t) apiRequest := apiRequest(getNext([]string{".1.3.6.1.2.1.25.2.3.1.2", ".1.3.6.1.2.1.25.2.3.1.2.3"})) - requester := snmpproxy.NewGosnmpRequester(mib.NewDataProvider(nil)) + requester := snmpproxy.NewGosnmpRequester(snmpproxy.NewValueFormatter(mib.NewDataProvider(nil))) result, err := requester.ExecuteRequest(apiRequest) assert.NoError(err) @@ -169,7 +121,7 @@ func TestWalk(t *testing.T) { apiRequest := apiRequest(walk(".1.3.6.1.2.1.31.1.1.1.15")) - requester := snmpproxy.NewGosnmpRequester(mib.NewDataProvider(nil)) + requester := snmpproxy.NewGosnmpRequester(snmpproxy.NewValueFormatter(mib.NewDataProvider(nil))) result, err := requester.ExecuteRequest(apiRequest) assert.NoError(err) @@ -191,7 +143,7 @@ func TestWalkWithSnmpVersion1(t *testing.T) { apiRequest := apiRequest(walk(".1.3.6.1.2.1.31.1.1.1.15")) apiRequest.Version = snmpproxy.SnmpVersion(gosnmp.Version1) - requester := snmpproxy.NewGosnmpRequester(mib.NewDataProvider(nil)) + requester := snmpproxy.NewGosnmpRequester(snmpproxy.NewValueFormatter(mib.NewDataProvider(nil))) result, err := requester.ExecuteRequest(apiRequest) assert.NoError(err) @@ -212,11 +164,14 @@ func TestWalkWholeTree(t *testing.T) { apiRequest := apiRequest(walk(".1.3")) - mibDataProvider := mib.NewDataProvider(mib.DisplayHints{ - ".1.3.6.1.2.1.2.2.1.2": mib.DisplayHintString, - ".1.3.6.1.2.1.4.22.1.2": mib.DisplayHintHexadecimal, - }) - + mibDataProvider := snmpproxy.NewValueFormatter( + mib.NewDataProvider( + mib.DisplayHints{ + ".1.3.6.1.2.1.2.2.1.2": mib.DisplayHintString, + ".1.3.6.1.2.1.4.22.1.2": mib.DisplayHintHexadecimal, + }, + ), + ) requester := snmpproxy.NewGosnmpRequester(mibDataProvider) result, err := requester.ExecuteRequest(apiRequest) assert.NoError(err) @@ -277,7 +232,7 @@ func TestWalkLastMibElement(t *testing.T) { apiRequest := apiRequest(walk(".1.7")) - requester := snmpproxy.NewGosnmpRequester(mib.NewDataProvider(nil)) + requester := snmpproxy.NewGosnmpRequester(snmpproxy.NewValueFormatter(mib.NewDataProvider(nil))) result, err := requester.ExecuteRequest(apiRequest) assert.NoError(err) @@ -291,7 +246,7 @@ func TestWalkLastMibElementAndSnmpVersion1(t *testing.T) { apiRequest := apiRequest(walk(".1.7")) apiRequest.Version = snmpproxy.SnmpVersion(gosnmp.Version1) - requester := snmpproxy.NewGosnmpRequester(mib.NewDataProvider(nil)) + requester := snmpproxy.NewGosnmpRequester(snmpproxy.NewValueFormatter(mib.NewDataProvider(nil))) result, err := requester.ExecuteRequest(apiRequest) assert.NoError(err) @@ -308,7 +263,7 @@ func TestMultipleRequests(t *testing.T) { getNext([]string{".1.3.6.1.2.1.2.2.1.14.9", ".1.3.6.1.2.1.2.2.1.14.10"}), ) - requester := snmpproxy.NewGosnmpRequester(mib.NewDataProvider(nil)) + requester := snmpproxy.NewGosnmpRequester(snmpproxy.NewValueFormatter(mib.NewDataProvider(nil))) result, err := requester.ExecuteRequest(apiRequest) assert.NoError(err) @@ -335,7 +290,7 @@ func TestMultipleRequestsWithSingleError(t *testing.T) { getNext([]string{".1.3.6.1.2.1.2.2.1.14.9", ".1.7.9"}), ) - requester := snmpproxy.NewGosnmpRequester(mib.NewDataProvider(nil)) + requester := snmpproxy.NewGosnmpRequester(snmpproxy.NewValueFormatter(mib.NewDataProvider(nil))) result, err := requester.ExecuteRequest(apiRequest) assert.Nil(result) @@ -351,7 +306,7 @@ func TestMultipleRequestsAllError(t *testing.T) { getNext([]string{".1.3.6.1.2.1.2.2.1.14.9", ".1.7.9"}), ) - requester := snmpproxy.NewGosnmpRequester(mib.NewDataProvider(nil)) + requester := snmpproxy.NewGosnmpRequester(snmpproxy.NewValueFormatter(mib.NewDataProvider(nil))) result, err := requester.ExecuteRequest(apiRequest) assert.Nil(result) @@ -367,7 +322,7 @@ func TestWalkWithTimeout(t *testing.T) { apiRequest.Version = snmpproxy.SnmpVersion(gosnmp.Version1) apiRequest.Timeout = time.Millisecond - requester := snmpproxy.NewGosnmpRequester(mib.NewDataProvider(nil)) + requester := snmpproxy.NewGosnmpRequester(snmpproxy.NewValueFormatter(mib.NewDataProvider(nil))) result, err := requester.ExecuteRequest(apiRequest) assert.Nil(result) assert.EqualError(err, "timeout: .1.15") @@ -378,7 +333,7 @@ func TestWalkWithNoSuchInstanceError(t *testing.T) { apiRequest := apiRequest(walk(".1.3.5")) - requester := snmpproxy.NewGosnmpRequester(mib.NewDataProvider(nil)) + requester := snmpproxy.NewGosnmpRequester(snmpproxy.NewValueFormatter(mib.NewDataProvider(nil))) result, err := requester.ExecuteRequest(apiRequest) assert.Nil(result) assert.EqualError(err, "no such instance: .1.3.5") @@ -390,7 +345,7 @@ func TestWalkWithSnmpVersion1AndNoSuchInstanceError(t *testing.T) { apiRequest := apiRequest(walk(".1.3.5")) apiRequest.Version = snmpproxy.SnmpVersion(gosnmp.Version1) - requester := snmpproxy.NewGosnmpRequester(mib.NewDataProvider(nil)) + requester := snmpproxy.NewGosnmpRequester(snmpproxy.NewValueFormatter(mib.NewDataProvider(nil))) result, err := requester.ExecuteRequest(apiRequest) assert.Nil(result) assert.EqualError(err, "no such instance: .1.3.5") @@ -401,7 +356,7 @@ func TestWalkWithEndOfMibError(t *testing.T) { apiRequest := apiRequest(walk(".1.15")) - requester := snmpproxy.NewGosnmpRequester(mib.NewDataProvider(nil)) + requester := snmpproxy.NewGosnmpRequester(snmpproxy.NewValueFormatter(mib.NewDataProvider(nil))) result, err := requester.ExecuteRequest(apiRequest) assert.Nil(result) assert.EqualError(err, "end of mib: .1.15") @@ -413,7 +368,7 @@ func TestWalkWithSnmpVersion1AndEndOfMibError(t *testing.T) { apiRequest := apiRequest(walk(".1.15")) apiRequest.Version = snmpproxy.SnmpVersion(gosnmp.Version1) - requester := snmpproxy.NewGosnmpRequester(mib.NewDataProvider(nil)) + requester := snmpproxy.NewGosnmpRequester(snmpproxy.NewValueFormatter(mib.NewDataProvider(nil))) result, err := requester.ExecuteRequest(apiRequest) assert.Nil(result) assert.EqualError(err, "end of mib: .1.15") @@ -427,7 +382,7 @@ func TestGetWithTimeout(t *testing.T) { apiRequest.Version = snmpproxy.SnmpVersion(gosnmp.Version1) apiRequest.Timeout = time.Millisecond - requester := snmpproxy.NewGosnmpRequester(mib.NewDataProvider(nil)) + requester := snmpproxy.NewGosnmpRequester(snmpproxy.NewValueFormatter(mib.NewDataProvider(nil))) result, err := requester.ExecuteRequest(apiRequest) assert.Nil(result) assert.EqualError(err, "timeout: .1.15") @@ -438,7 +393,7 @@ func TestGetWithNoSuchInstanceError(t *testing.T) { apiRequest := apiRequest(get([]string{".1.3.5"})) - requester := snmpproxy.NewGosnmpRequester(mib.NewDataProvider(nil)) + requester := snmpproxy.NewGosnmpRequester(snmpproxy.NewValueFormatter(mib.NewDataProvider(nil))) result, err := requester.ExecuteRequest(apiRequest) assert.Nil(result) assert.EqualError(err, "no such instance: .1.3.5") @@ -450,7 +405,7 @@ func TestGetWithSnmpVersion1AndNoSuchInstanceError(t *testing.T) { apiRequest := apiRequest(get([]string{".1.3.5"})) apiRequest.Version = snmpproxy.SnmpVersion(gosnmp.Version1) - requester := snmpproxy.NewGosnmpRequester(mib.NewDataProvider(nil)) + requester := snmpproxy.NewGosnmpRequester(snmpproxy.NewValueFormatter(mib.NewDataProvider(nil))) result, err := requester.ExecuteRequest(apiRequest) assert.Nil(result) assert.EqualError(err, "no such instance: .1.3.5") @@ -462,7 +417,7 @@ func TestGetWithMultipleOidsAndSnmpVersion1AndNoSuchInstanceError(t *testing.T) apiRequest := apiRequest(get([]string{".1.3.5", "1.3.2"})) apiRequest.Version = snmpproxy.SnmpVersion(gosnmp.Version1) - requester := snmpproxy.NewGosnmpRequester(mib.NewDataProvider(nil)) + requester := snmpproxy.NewGosnmpRequester(snmpproxy.NewValueFormatter(mib.NewDataProvider(nil))) result, err := requester.ExecuteRequest(apiRequest) assert.Nil(result) assert.EqualError(err, "no such instance: one of .1.3.5 1.3.2") @@ -473,7 +428,7 @@ func TestGetNextWithEndOfMib(t *testing.T) { apiRequest := apiRequest(getNext([]string{".1.15"})) - requester := snmpproxy.NewGosnmpRequester(mib.NewDataProvider(nil)) + requester := snmpproxy.NewGosnmpRequester(snmpproxy.NewValueFormatter(mib.NewDataProvider(nil))) result, err := requester.ExecuteRequest(apiRequest) assert.Nil(result) assert.EqualError(err, "end of mib: .1.15") @@ -485,7 +440,7 @@ func TestGetNextWithSnmpVersion1AndEndOfMib(t *testing.T) { apiRequest := apiRequest(getNext([]string{".1.15"})) apiRequest.Version = snmpproxy.SnmpVersion(gosnmp.Version1) - requester := snmpproxy.NewGosnmpRequester(mib.NewDataProvider(nil)) + requester := snmpproxy.NewGosnmpRequester(snmpproxy.NewValueFormatter(mib.NewDataProvider(nil))) result, err := requester.ExecuteRequest(apiRequest) assert.Nil(result) assert.EqualError(err, "end of mib: .1.15") @@ -497,7 +452,7 @@ func TestGetWithSnmpError(t *testing.T) { apiRequest := apiRequest(get([]string{".1.1"})) apiRequest.Host = "localhost" - requester := snmpproxy.NewGosnmpRequester(mib.NewDataProvider(nil)) + requester := snmpproxy.NewGosnmpRequester(snmpproxy.NewValueFormatter(mib.NewDataProvider(nil))) result, err := requester.ExecuteRequest(apiRequest) assert.Nil(result) assert.Error(err) @@ -510,7 +465,7 @@ func TestWalkWithSnmpError(t *testing.T) { apiRequest := apiRequest(walk("")) apiRequest.Host = "localhost" - requester := snmpproxy.NewGosnmpRequester(mib.NewDataProvider(nil)) + requester := snmpproxy.NewGosnmpRequester(snmpproxy.NewValueFormatter(mib.NewDataProvider(nil))) result, err := requester.ExecuteRequest(apiRequest) assert.Nil(result) assert.Error(err) @@ -547,7 +502,7 @@ func TestInvalidHost(t *testing.T) { apiRequest := apiRequest(get([]string{}), walk("")) apiRequest.Host = test.host - requester := snmpproxy.NewGosnmpRequester(mib.NewDataProvider(nil)) + requester := snmpproxy.NewGosnmpRequester(snmpproxy.NewValueFormatter(mib.NewDataProvider(nil))) result, err := requester.ExecuteRequest(apiRequest) assert.Nil(result) assert.Error(err) diff --git a/snmpproxy/value.go b/snmpproxy/value.go new file mode 100644 index 0000000..e3f2459 --- /dev/null +++ b/snmpproxy/value.go @@ -0,0 +1,128 @@ +package snmpproxy + +import ( + "encoding/binary" + "strconv" + "strings" + "unicode" + "unicode/utf8" + "unsafe" + + "github.com/gosnmp/gosnmp" + "github.com/grongor/go-snmp-proxy/snmpproxy/mib" +) + +type ValueFormatter struct { + mibDataProvider *mib.DataProvider +} + +func (f *ValueFormatter) Format(dataUnit gosnmp.SnmpPDU) interface{} { + if dataUnit.Type != gosnmp.OctetString { + return dataUnit.Value + } + + switch f.mibDataProvider.GetDisplayHint(dataUnit.Name) { + case mib.DisplayHintString: + return f.getValueAsString(dataUnit.Value.([]byte)) + case mib.DisplayHintDateAndTime: + return f.formatDateAndTime(dataUnit.Value.([]byte)) + case mib.DisplayHintUnknown: + if f.isStringPrintable(dataUnit.Value.([]byte)) { + return f.getValueAsString(dataUnit.Value.([]byte)) + } + + fallthrough + default: + value := dataUnit.Value.([]byte) + result := make([]byte, len(value)*3-1) // 2 chars per byte + space separator between each (hence -1) + + const hexTable = "0123456789ABCDEF" + + var j int + + for i, v := range value { + if i != 0 { + result[j] = ' ' + j++ + } + + result[j] = hexTable[v>>4] + result[j+1] = hexTable[v&0x0f] + j += 2 + } + + return f.getValueAsString(result) + } +} + +func (*ValueFormatter) formatDateAndTime(value []byte) string { + valueSize := len(value) + withoutTimezone := valueSize == 8 + + buf := strings.Builder{} + if withoutTimezone { + buf.Grow(valueSize*2 + 6) // approx 2 chars per byte + separators + } else { + buf.Grow(valueSize*2 + 6 - 1 + 2) // same as above (except timezone sign) + timezone separators + } + + // year + buf.WriteString(strconv.FormatUint(uint64(binary.BigEndian.Uint16(value[0:2])), 10)) + buf.WriteByte('-') + // month + buf.WriteString(strconv.FormatUint(uint64(value[2]), 10)) + buf.WriteByte('-') + // day + buf.WriteString(strconv.FormatUint(uint64(value[3]), 10)) + buf.WriteByte(',') + // hours + buf.WriteString(strconv.FormatUint(uint64(value[4]), 10)) + buf.WriteByte(':') + // minutes + buf.WriteString(strconv.FormatUint(uint64(value[5]), 10)) + buf.WriteByte(':') + // seconds + buf.WriteString(strconv.FormatUint(uint64(value[6]), 10)) + buf.WriteByte('.') + // deci-seconds + buf.WriteString(strconv.FormatUint(uint64(value[7]), 10)) + + if withoutTimezone { + return buf.String() + } + + buf.WriteByte(',') + // direction from UTC: + or - + buf.WriteByte(value[8]) + // hours from UTC + buf.WriteString(strconv.FormatUint(uint64(value[9]), 10)) + buf.WriteByte(':') + // minutes from UTC + buf.WriteString(strconv.FormatUint(uint64(value[10]), 10)) + + return buf.String() +} + +func (*ValueFormatter) getValueAsString(value []byte) string { + return *(*string)(unsafe.Pointer(&value)) +} + +func (*ValueFormatter) isStringPrintable(value []byte) bool { + if !utf8.Valid(value) { + return false + } + + for _, b := range value { + if unicode.IsPrint(rune(b)) || unicode.IsSpace(rune(b)) { + continue + } + + return false + } + + return true +} + +func NewValueFormatter(mibDataProvider *mib.DataProvider) *ValueFormatter { + return &ValueFormatter{mibDataProvider: mibDataProvider} +} diff --git a/snmpproxy/value_test.go b/snmpproxy/value_test.go new file mode 100644 index 0000000..9e37b49 --- /dev/null +++ b/snmpproxy/value_test.go @@ -0,0 +1,82 @@ +package snmpproxy_test + +import ( + "testing" + + "github.com/gosnmp/gosnmp" + "github.com/grongor/go-snmp-proxy/snmpproxy" + "github.com/grongor/go-snmp-proxy/snmpproxy/mib" + "github.com/stretchr/testify/require" +) + +func TestValueFormatter_Format(t *testing.T) { + formatter := snmpproxy.NewValueFormatter( + mib.NewDataProvider( + map[string]mib.DisplayHint{ + ".1.3.6.3": mib.DisplayHintString, + ".1.3.6.4": mib.DisplayHintHexadecimal, + ".1.3.6.5": mib.DisplayHintDateAndTime, + }, + ), + ) + + tests := []struct { + name string + pdu gosnmp.SnmpPDU + expected interface{} + }{ + { + name: "not octet string", + pdu: gosnmp.SnmpPDU{Name: ".666", Type: gosnmp.Integer, Value: 123}, + expected: 123, + }, + { + name: "no display hint, not printable", + pdu: gosnmp.SnmpPDU{Name: ".666", Type: gosnmp.OctetString, Value: []byte{0, 1, 2}}, + expected: "00 01 02", + }, + { + name: "no display hint, not valid utf8", + pdu: gosnmp.SnmpPDU{Name: ".666", Type: gosnmp.OctetString, Value: []byte{145, 226, 186, 227, 90, 97}}, + expected: "91 E2 BA E3 5A 61", + }, + { + name: "no display hint, printable", + pdu: gosnmp.SnmpPDU{Name: ".666", Type: gosnmp.OctetString, Value: []byte{'a', 'b', 'c'}}, + expected: "abc", + }, + { + name: "hint string", + pdu: gosnmp.SnmpPDU{Name: ".1.3.6.3", Type: gosnmp.OctetString, Value: []byte{'a', 'b', 'c'}}, + expected: "abc", + }, + { + name: "hint hexadecimal", + pdu: gosnmp.SnmpPDU{Name: ".1.3.6.4", Type: gosnmp.OctetString, Value: []byte{0, 255, 9, 17}}, + expected: "00 FF 09 11", + }, + { + name: "hint dateAndTime", + pdu: gosnmp.SnmpPDU{ + Name: ".1.3.6.5", + Type: gosnmp.OctetString, + Value: []byte{0o7, 229, 10, 15, 14, 56, 8, 0o0, 43, 0o0, 0o0}, + }, + expected: "2021-10-15,14:56:8.0,+0:0", + }, + { + name: "hint dateAndTime without timezone", + pdu: gosnmp.SnmpPDU{ + Name: ".1.3.6.5", + Type: gosnmp.OctetString, + Value: []byte{0o7, 229, 10, 15, 14, 56, 8, 0o0}, + }, + expected: "2021-10-15,14:56:8.0", + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + require.Equal(t, test.expected, formatter.Format(test.pdu)) + }) + } +}