VOL-4338: Relaxed MIB Upload Next response decoding
Change-Id: I4c0db4d4786a1d8501daec18a6980821a9267b84
diff --git a/FrameDecode.md b/FrameDecode.md
new file mode 100644
index 0000000..d82ab1f
--- /dev/null
+++ b/FrameDecode.md
@@ -0,0 +1,69 @@
+# Frame Decode Information
+
+The [omci-lib-go](https://github.com/opencord/omci-lib-go) library provides go-packet
+decode and serialization for the
+[ITU G.988 OMCI](https://www.itu.int/rec/T-REC-G.988/recommendation.asp?lang=en&parent=T-REC-G.988-202003-I!Amd3)
+specification and currently supports the 3/2020 Amendment 3 of G.988. As both new amendments are issued and
+ONU/OLT vendors may implement the standard differently in minor ways, a need to be able to relax the
+decode of a received frame was required of the library. Since the go-packet standard does not support this
+natively, an OMCI library specific pair of API calls was created to be able to get/set relaxed decoding
+of OMCI frames on a message-type basis.
+
+The default/initial setting of the library is to relax decode for received frames for known or
+expected issues.
+
+In addition to relaxed decoding, the ability to receive undefined Managed Entity (ITU or vendor specific)
+has been part of this library for some time. This document provides information on those capabilities as
+they are similar in nature with the need for relaxed decoding.
+
+## GetOmciRelaxedDecode
+
+func GetRelaxedDecode(msgType MsgType, request bool) bool
+
+This function can be used to query the state of relaxed decode for a specific OMCI direction type and
+direction (request/response). If relax decoding is not supported by the requested message type, false
+is returned.
+
+For Notifications, set 'request' to **false**.
+
+
+## SetOmciRelaxedDecode
+
+func SetRelaxedDecode(msgType MsgType, request bool, relax bool) error
+
+This function can be used to enable/disable relaxed decode for a specific OMCI direction type and
+direction (request/resposne). An error is returned if relax decoding is not supported for the
+requested type.
+
+For Notifications, set 'request' to **false**.
+
+## Expected Deviations
+
+This section details expected deviations from the standard that requires relaxed decode
+capability.
+
+### New Attribute Definitions
+
+As new revisions of G.988 are produced, existing Managed Entity definitions may have new
+attributes defined.
+
+### New/Unknown G.988 Managed Entity Definitions
+
+TODO: This section needs to be documented. This capability has been part of this library
+for some time.
+
+### Vendor Specific Managed Entity Definitions
+
+TODO: This section needs to be documented. This capability has been part of this library
+for some time.
+## Known Vendor Deviations
+
+This section documents known deviations of the G.988 standard that have been identified
+and can be compensated for by the OMCI library.
+
+### Get Response
+
+For the baseline message set, a Get Response reserves the last 4 octets of the frame for
+attribute error information. This space is always reserved even when the result of the
+response is zero (success). At least one vendor (or ONU library) makes use of this space
+for normal attribute storage when a success result is returned.
\ No newline at end of file
diff --git a/README.md b/README.md
index 829c877..6ec8ff6 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,15 @@
MEFrame library.
# Recent Changes
+In v2.1.0, a pair of library calls were added to help support relaxed decoding of OMCI
+frames. The primary intent is to allow for reception of frames that may have been
+encoded with either a newer release of the G.988 OMCI specification or with a few minor
+ONU/OLT implementation errors that are minor and by allowing them greater interoperability
+can be achieved. To track this change and document the API calls, the
+[FrameDecode](https://github.com/opencord/omci-lib-go/blob/master/FrameDecode.md) document
+provides further information on this new capability.
+## v2.0.0
In v2.0.0, the directory/package structure was reorganized (no API changes otherwise)
in order to separate message type functionality on a filename basis. This will allow
for future features and bug fixes to be better localized and to allow for better
@@ -24,32 +32,33 @@
library. The current coverage for version 2.0.0 (as of 9/08/2021) is:
Entire Project: 97.3% of files and 70.2% of statements
-Generated Subdirectory: 98.5% of files and 51.9% of statements
+Generated Subdirectory: 98.1% of files and 50.1% of statements
meframe Subdirectory: 80% of files and 55.4% of statements
Main Message Directory (below):
-| File | Coverage | < 75% |
-| --------------: | :------: | :---: |
-| alarms.go | 74.3% | Y |
-| avc.go | 86% | |
-| create.go | 82.5% | |
-| delete.go | 85.5% | |
-| get.go | 78.4% | |
-| getcurrent.go | 69.4% | |
-| getnext.go | 79.3% | |
-| layers.go | 100% | |
-| mebase.go | 93.3% | |
-| messagetypes.go | 100% | |
-| mibreset.go | 76.6% | |
-| mibupload.go | 77% | |
-| omci.go | 90.6% | |
-| reboot.go | 81.2% | |
-| set.go | 77.3% | |
-| settable.go | 81.5% | |
-| software.go | 75.2% | |
-| synctime.go | 79.3% | |
-| test.go | 79.9% | |
+| File | Coverage | < 75% |
+| ----------------: | :------: | :---: |
+| alarms.go | 74.3% | Y |
+| avc.go | 86% | |
+| create.go | 82.5% | |
+| delete.go | 85.5% | |
+| get.go | 78.4% | |
+| getcurrent.go | 69.4% | |
+| getnext.go | 79.3% | |
+| layers.go | 100% | |
+| mebase.go | 93.3% | |
+| messagetypes.go | 100% | |
+| mibreset.go | 76.6% | |
+| mibupload.go | 78.9% | |
+| omci.go | 90.6% | |
+| reboot.go | 81.2% | |
+| relaxed_decode.go | 78.3% | |
+| set.go | 77.3% | |
+| settable.go | 81.5% | |
+| software.go | 75.2% | |
+| synctime.go | 79.3% | |
+| test.go | 79.9% | |
## Other outstanding items
@@ -74,6 +83,7 @@
packet decode/serialization and a variety of structs and functions that are useful for handling
the creation of OMCI frames and handling decoded frames from the PON.
+### OLT
For an OLT-side OMCI stack, you would still need to write:
- OMCI CC sender & receiver (stop & wait protocol) with appropriate timeout support
- OLT State machines to support
@@ -85,10 +95,17 @@
- Performance Monitoring collection (and initial time synchronization),
- Service implementation
+### ONU
For an ONU-side OMCI stack, you would still need to write:
- OMCC implementation,
- MIB Database,
- Get-Next cache for table attributes,
- MIB upload next cache for MIB uploads,
- Generation of any alarms/AVC notifications,
- - Actually acting on the create/delete/get/set/... requests from an OLT
\ No newline at end of file
+ - Actually acting on the create/delete/get/set/... requests from an OLT
+
+### Vendor specific error information
+Rule 5 of section A.1.1 of the G.988 standard provides for the capability of adding
+vendor specific error information in the trailing octets of an OMCI response that has
+a non-zero (success) error code. The current library does not provide an easy mechanism
+for encoding or easy decoding of additional error information at this time.
diff --git a/VERSION b/VERSION
index 227cea2..7ec1d6d 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2.0.0
+2.1.0
diff --git a/generated/decodeerror.go b/generated/decodeerror.go
new file mode 100644
index 0000000..4358c0c
--- /dev/null
+++ b/generated/decodeerror.go
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2018 - present. Boling Consulting Solutions (bcsw.net)
+ * Copyright 2020-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/*
+ * NOTE: This file was generated, manual edits will be overwritten!
+ *
+ * Generated by 'goCodeGenerator.py':
+ * https://github.com/cboling/OMCI-parser/README.md
+ */
+
+package generated
+
+import (
+ "errors"
+)
+
+// Custom Go Error messages for relaxed decode error signaling.
+//
+// gopacket does not provide a way to easily perform relaxed decoding calls
+// during the 'DecodeFromBytes' decoding of a layer. It does allow for an error
+// code returned and this will be used to allow for relaxed decoding. If a
+// particular message type decode can be relaxed, process it as relax and
+// return an error derived from the RelaxedDecodeError below and check for
+// it as appropriate.
+
+// IRelaxedDecodeError provides a base interface that can be used to derive
+// other decode specific errors that can be relaxed at the application's
+// discretion
+type IRelaxedDecodeError interface {
+ // Error interface, so if relaxed decode is not supported, this behaves as
+ // a normal error
+ Error() string
+ GetError() error
+
+ ////////////////////////////////
+ // Relaxed error specific
+
+ // GetContents returns the octet payload specific to the error if it can be
+ // determined. Derived relaxed decode errors may provide more specific control
+ // and information
+ GetContents() []byte
+}
+
+type RelaxedDecodeError struct {
+ err string
+ Contents []byte
+}
+
+func (e *RelaxedDecodeError) GetError() error {
+ return errors.New(e.err)
+}
+
+func (e *RelaxedDecodeError) Error() string {
+ return e.err
+}
+
+func (e *RelaxedDecodeError) GetContents() []byte {
+ return e.Contents
+}
+
+// UnknownAttributeDecodeError is used to convey unknown attributes found in
+// a received packet undergoing decode. These will always be trailing attributes
+// in packets and are often due to:
+// o New versions of ITU G.988 being issued with an existing ME getting new
+// attributes,
+//
+// o An error in the code generated classes in this library where one or more
+// attributes were missed, or
+//
+// o An error in the OLT/ONU that serialized the message
+type UnknownAttributeDecodeError struct {
+ RelaxedDecodeError
+ AttributeMask uint16
+
+ EntityClass ClassID // Set by first level handler of the error
+ EntityInstance uint16 // Set by first level handler of the error
+}
+
+func NewUnknownAttributeDecodeError(msg string, mask uint16, contents []byte) *UnknownAttributeDecodeError {
+ err := &UnknownAttributeDecodeError{
+ RelaxedDecodeError: RelaxedDecodeError{
+ err: msg,
+ },
+ AttributeMask: mask,
+ }
+ if contents != nil {
+ err.Contents = make([]byte, len(contents))
+ copy(err.Contents, contents)
+ }
+ return err
+}
diff --git a/generated/me.go b/generated/me.go
index 8bec058..2d83255 100644
--- a/generated/me.go
+++ b/generated/me.go
@@ -286,15 +286,26 @@
entity.definition = meDefinition.definition
entity.attributeMask = binary.BigEndian.Uint16(data[4:6])
entity.attributes = make(map[string]interface{})
- entity.SetEntityID(entityID)
+ setErr := entity.SetEntityID(entityID)
+ if setErr != nil {
+ return setErr
+ }
packetAttributes, err := entity.DecodeAttributes(entity.GetAttributeMask(), data[6:], p, msgType)
+
+ // Decode packet attributes even if present in case relaxed attribute decoding is enabled.
+ if packetAttributes != nil {
+ for name, value := range packetAttributes {
+ entity.attributes[name] = value
+ }
+ }
if err != nil {
- return err
+ if attrError, ok := err.(*UnknownAttributeDecodeError); ok && GetRelaxedDecodeByOctetType(msgType) {
+ // Subtract off bad mask from what we computed
+ badMask := attrError.AttributeMask
+ entity.attributeMask &= ^badMask
+ }
}
- for name, value := range packetAttributes {
- entity.attributes[name] = value
- }
- return nil
+ return err
}
// SerializeTo serializes a Managed Entity into an octet stream
diff --git a/generated/medef.go b/generated/medef.go
index d6ab352..6d2c2dd 100644
--- a/generated/medef.go
+++ b/generated/medef.go
@@ -90,10 +90,15 @@
}
func (bme ManagedEntityDefinition) DecodeAttributes(mask uint16, data []byte, p gopacket.PacketBuilder, msgType byte) (AttributeValueMap, error) {
- if (mask | bme.GetAllowedAttributeMask()) != bme.GetAllowedAttributeMask() {
- return nil, fmt.Errorf("unsupported attribute mask %#x, valid: %#x for ME %v (Class ID: %d)",
+ badMask := (mask | bme.GetAllowedAttributeMask()) ^ bme.GetAllowedAttributeMask()
+
+ var maskErr error
+ if badMask != 0 {
+ maskErr = fmt.Errorf("unsupported attribute mask %#x, valid: %#x for ME %v (Class ID: %d)",
mask, bme.GetAllowedAttributeMask(), bme.GetName(), bme.ClassID)
+ mask &= bme.GetAllowedAttributeMask()
}
+ // Process known attributes
keyList := GetAttributeDefinitionMapKeys(bme.AttributeDefinitions)
attrMap := make(AttributeValueMap, bits.OnesCount16(mask))
@@ -153,7 +158,13 @@
}
}
}
- return attrMap, nil
+ // If badMask is non-zero. Handle it by re-encoding the error as a custom relaxed
+ // decode error that the caller of this decode can process if they wish to relax
+ // the decoding
+ if badMask != 0 {
+ maskErr = NewUnknownAttributeDecodeError(maskErr.Error(), badMask, data)
+ }
+ return attrMap, maskErr
}
func (bme ManagedEntityDefinition) SerializeAttributes(attr AttributeValueMap, mask uint16,
diff --git a/generated/relaxed.go b/generated/relaxed.go
new file mode 100644
index 0000000..a1e49d3
--- /dev/null
+++ b/generated/relaxed.go
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2018 - present. Boling Consulting Solutions (bcsw.net)
+ * Copyright 2020-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/*
+ * NOTE: This file was generated, manual edits will be overwritten!
+ *
+ * Generated by 'goCodeGenerator.py':
+ * https://github.com/cboling/OMCI-parser/README.md
+ */
+package generated
+
+import (
+ "fmt"
+)
+
+var relaxedDecode map[int]bool
+
+func init() {
+ relaxedDecode = make(map[int]bool)
+
+ // Default for relaxed decode is True if there is relaxed decode support available
+ // relaxedDecode[mkRelaxedKey(Get, false)] = true
+ relaxedDecode[mkRelaxedKey(MibUploadNext, false)] = true
+}
+
+func mkRelaxedKey(msgType MsgType, request bool) int {
+ if request {
+ return int(msgType)
+ }
+ return 100 + int(msgType)
+}
+
+func SetRelaxedDecode(msgType MsgType, request bool, relax bool) error {
+ key := mkRelaxedKey(msgType, request)
+
+ if _, ok := relaxedDecode[key]; !ok {
+ return fmt.Errorf("relax decode of '%v' is not supported", msgType)
+ }
+ relaxedDecode[key] = relax
+ return nil
+}
+
+func GetRelaxedDecode(msgType MsgType, request bool) bool {
+ key := mkRelaxedKey(msgType, request)
+
+ relaxed, ok := relaxedDecode[key]
+ return ok && relaxed
+}
+
+// GetRelaxedDecodeByOctetType decodes the payload message-type value and determine if
+// relaxed decode is enabled
+func GetRelaxedDecodeByOctetType(value byte) bool {
+ msgType := MsgType(value & MsgTypeMask)
+ var request bool
+
+ if msgType != AlarmNotification && msgType != AttributeValueChange && msgType != TestResult {
+ request = value&AR == AR || value&AK == 0
+ }
+ return GetRelaxedDecode(msgType, request)
+}
diff --git a/layers.go b/layers.go
index cc9aa88..d7904ad 100644
--- a/layers.go
+++ b/layers.go
@@ -117,6 +117,9 @@
LayerTypeGetAllAlarmsResponseExtended gopacket.LayerType
LayerTypeGetAllAlarmsNextResponseExtended gopacket.LayerType
)
+var (
+ LayerTypeUnknownAttributes gopacket.LayerType
+)
func mkReqLayer(mt me.MsgType, mts string, decode gopacket.DecodeFunc) gopacket.LayerType {
return gopacket.RegisterLayerType(1000+(int(mt)|int(me.AR)),
@@ -334,6 +337,16 @@
nextLayerMapping[AlarmNotificationType+ExtendedTypeDecodeOffset] = LayerTypeAlarmNotificationExtended
nextLayerMapping[AttributeValueChangeType+ExtendedTypeDecodeOffset] = LayerTypeAttributeValueChangeExtended
nextLayerMapping[TestResultType+ExtendedTypeDecodeOffset] = LayerTypeTestResultExtended
+
+ ////////////////////////////////////////////////////////////////////////
+ // The following are custom layers used during relaxed decode. They are defined
+ // as layers but will be appended to decoded packets as an error layer. The DecodeFunc
+ // is actually never called and does not have to be added to the nextLayerMapping
+ var decode gopacket.DecodeFunc
+
+ decode = decodeUnknownAttributes
+ LayerTypeUnknownAttributes = gopacket.RegisterLayerType(2000,
+ gopacket.LayerTypeMetadata{Name: "Unknown Attributes", Decoder: decode})
}
func MsgTypeToNextLayer(mt MessageType, isExtended bool) (gopacket.LayerType, error) {
diff --git a/mibupload.go b/mibupload.go
index dd893d4..a8549a1 100644
--- a/mibupload.go
+++ b/mibupload.go
@@ -353,6 +353,8 @@
MeBasePacket
ReportedME me.ManagedEntity
AdditionalMEs []me.ManagedEntity // Valid only for extended message set version
+
+ RelaxedErrors []me.IRelaxedDecodeError
}
type MibUploadNextManageEntity struct {
@@ -377,9 +379,21 @@
// NextLayerType returns the layer type contained by this DecodingLayer.
func (omci *MibUploadNextResponse) NextLayerType() gopacket.LayerType {
+
+ if omci.RelaxedErrors != nil && len(omci.RelaxedErrors) > 0 {
+ return LayerTypeUnknownAttributes
+ }
return gopacket.LayerTypePayload
}
+// addRelaxedError appends relaxed decode errors to this message
+func (omci *MibUploadNextResponse) addRelaxedError(err me.IRelaxedDecodeError) {
+ if omci.RelaxedErrors == nil {
+ omci.RelaxedErrors = make([]me.IRelaxedDecodeError, 0)
+ }
+ omci.RelaxedErrors = append(omci.RelaxedErrors, err)
+}
+
// DecodeFromBytes decodes the given bytes of a MIB Upload Next Response into this layer
func (omci *MibUploadNextResponse) DecodeFromBytes(data []byte, p gopacket.PacketBuilder) error {
// Common ClassID/EntityID decode in msgBase
@@ -418,47 +432,77 @@
// error of "managed entity definition not found" returned.
var offset int
var attrLen int
+ meLength := len(data)
if omci.Extended {
offset = 2 + 2 // Message Contents length (2) + first ME attribute values len (2)
attrLen = int(binary.BigEndian.Uint16(data[6:]))
+ meLength = 4 + offset + 6 + attrLen
if len(data[4+offset:]) < 6+attrLen {
p.SetTruncated()
return errors.New("frame too small: MIB Upload Response Managed Entity attribute truncated")
}
}
- err = omci.ReportedME.DecodeFromBytes(data[4+offset:], p, byte(MibUploadNextResponseType))
- if err != nil || !omci.Extended {
- return err
+ err = omci.ReportedME.DecodeFromBytes(data[4+offset:meLength], p, byte(MibUploadNextResponseType))
+ if err != nil {
+ attrError, ok := err.(*me.UnknownAttributeDecodeError)
+
+ // Error if relaxed decode not supported or other error signalled
+ if !ok || !me.GetRelaxedDecode(me.MibUploadNext, false) {
+ return err
+ }
+ // Save off which Managed Entity had the issue
+ attrError.EntityClass = omci.ReportedME.GetClassID()
+ attrError.EntityInstance = omci.ReportedME.GetEntityID()
+ if attrError.Contents != nil && !omci.Extended {
+ attrLen += len(attrError.Contents)
+ }
+ omci.addRelaxedError(attrError)
+ err = nil
}
- // Handle extended message set decode here for additional attributes
- remaining := len(data) - (6 + 8 + attrLen)
- if remaining > 0 {
- offset = 6 + 8 + attrLen
- omci.AdditionalMEs = make([]me.ManagedEntity, 0)
- for remaining > 0 {
- if len(data[offset:]) < 8 {
+ if err == nil && omci.Extended {
+ // Handle extended message set decode here for additional managed entities
+ data = data[meLength:]
+ if len(data) > 0 {
+ omci.AdditionalMEs = make([]me.ManagedEntity, 0)
+ }
+ for len(data) > 0 {
+ if len(data) < 8 {
p.SetTruncated()
// TODO: Review all "frame to small" and add an extra hint for developers
return errors.New("frame too small: MIB Upload Response Managed Entity header truncated")
}
additional := me.ManagedEntity{}
- attrLen = int(binary.BigEndian.Uint16(data[offset:]))
+ attrLen = int(binary.BigEndian.Uint16(data))
+ meLength = 8 + attrLen
- if len(data[offset:]) < 8+attrLen {
+ if len(data) < meLength {
p.SetTruncated()
return errors.New("frame too small: MIB Upload Response Managed Entity attribute truncated")
}
- err = additional.DecodeFromBytes(data[offset+2:], p, byte(MibUploadNextResponseType))
+ err = additional.DecodeFromBytes(data[2:meLength], p, byte(MibUploadNextResponseType))
if err != nil {
- return err
+ attrError, ok := err.(*me.UnknownAttributeDecodeError)
+
+ // Error if relaxed decode not supported
+ if !ok || !me.GetRelaxedDecode(me.MibUploadNext, false) {
+ return err
+ }
+ // Save off which Managed Entity had the issue
+ attrError.EntityClass = additional.GetClassID()
+ attrError.EntityInstance = additional.GetEntityID()
+ omci.addRelaxedError(attrError)
+ err = nil
}
omci.AdditionalMEs = append(omci.AdditionalMEs, additional)
- remaining -= 8 + attrLen
- offset += 8 + attrLen
+ data = data[meLength:]
}
}
- return nil
+ if err == nil && omci.RelaxedErrors != nil && len(omci.RelaxedErrors) > 0 {
+ // Create our error layer now
+ err = newUnknownAttributesLayer(omci, omci.RelaxedErrors, p)
+ }
+ return err
}
func decodeMibUploadNextResponse(data []byte, p gopacket.PacketBuilder) error {
diff --git a/mibupload_test.go b/mibupload_test.go
index 8fcc983..ecb9c9d 100644
--- a/mibupload_test.go
+++ b/mibupload_test.go
@@ -613,8 +613,6 @@
data, err := stringToPacket(goodMessage)
assert.NoError(t, err)
- // TODO: Implement and test
-
packet := gopacket.NewPacket(data, LayerTypeOMCI, gopacket.NoCopy)
assert.NotNil(t, packet)
diff --git a/relaxed_decode.go b/relaxed_decode.go
new file mode 100644
index 0000000..adf3fb2
--- /dev/null
+++ b/relaxed_decode.go
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2018 - present. Boling Consulting Solutions (bcsw.net)
+ * Copyright 2020-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package omci
+
+import (
+ "fmt"
+ "github.com/google/gopacket"
+ "github.com/google/gopacket/layers"
+ me "github.com/opencord/omci-lib-go/generated"
+)
+
+type UnknownAttributeInfo struct {
+ EntityClass me.ClassID
+ EntityInstance uint16
+ AttributeMask uint16
+ AttributeData []byte
+}
+type UnknownAttributes struct {
+ // Each Attributes entry relates one or more unknown attributes to a specific managed
+ // entity. For message types such as MIB Upload Next responses, there may be multiple
+ // Managed Entities in a single response if the Extended Message set is being used.
+ Attributes []UnknownAttributeInfo
+
+ gopacket.Layer
+ layers.BaseLayer
+ MsgLayerType gopacket.LayerType
+}
+
+// SerializeTo provides serialization of an Get Next Message Type Response
+func (msg *UnknownAttributes) String() string {
+ return fmt.Sprintf("Unknown Attributes, %v Managed Entities", len(msg.Attributes))
+}
+
+// LayerType returns LayerTypeGetNextResponse
+func (msg *UnknownAttributes) LayerType() gopacket.LayerType {
+ return LayerTypeUnknownAttributes
+}
+
+// CanDecode returns the set of layer types that this DecodingLayer can decode
+func (msg *UnknownAttributes) CanDecode() gopacket.LayerClass {
+ return LayerTypeUnknownAttributes
+}
+
+// LayerContents returns the bytes of the packet layer.
+func (msg *UnknownAttributes) LayerContents() []byte {
+ return msg.Contents
+}
+
+// LayerPayload returns the bytes contained within the packet layer
+func (msg *UnknownAttributes) LayerPayload() []byte {
+ return msg.Payload
+}
+
+// NextLayerType returns the layer type contained by this DecodingLayer.
+func (msg *UnknownAttributes) NextLayerType() gopacket.LayerType {
+ return gopacket.LayerTypeZero
+}
+
+// DecodeFromBytes decodes the given bytes of a Get Next Response into this layer
+func (msg *UnknownAttributes) DecodeFromBytes(_ []byte, _ gopacket.PacketBuilder) error {
+ // This is not a real layer. It is used to pass on relaxed decode error information
+ // as an ErrorLayer
+ return fmt.Errorf("This function is never called. This is an error layer that gets assigned")
+}
+
+func decodeUnknownAttributes(_ []byte, _ gopacket.PacketBuilder) error {
+ return fmt.Errorf("This function is never called. This is an error layer that gets assigned")
+}
+
+func (msg *UnknownAttributes) Error() error {
+ return fmt.Errorf("%v managed entities with Unknown Attributes detected during decode",
+ len(msg.Attributes))
+}
+
+func newUnknownAttributesLayer(prevLayer gopacket.Layer, errInfo []me.IRelaxedDecodeError, p gopacket.PacketBuilder) error {
+ // Add the previous layer
+ p.AddLayer(prevLayer)
+
+ // Append unknown attributes layer and also set ErrorLayer
+
+ errLayer := &UnknownAttributes{
+ Attributes: make([]UnknownAttributeInfo, 0),
+ MsgLayerType: LayerTypeUnknownAttributes,
+ }
+ for _, item := range errInfo {
+ unknown, ok := item.(*me.UnknownAttributeDecodeError)
+ if !ok {
+ return fmt.Errorf("only UnknownAttributeDecodeError information can be encoded. Found %T",
+ unknown)
+ }
+ data := UnknownAttributeInfo{
+ EntityClass: unknown.EntityClass,
+ EntityInstance: unknown.EntityInstance,
+ AttributeMask: unknown.AttributeMask,
+ }
+ if unknown.Contents != nil {
+ data.AttributeData = make([]byte, len(unknown.Contents))
+ copy(data.AttributeData, unknown.Contents)
+ }
+ errLayer.Attributes = append(errLayer.Attributes, data)
+ }
+ p.AddLayer(errLayer)
+ p.SetErrorLayer(errLayer)
+
+ // Return a valid error so that packet decoding stops
+ return errLayer.Error()
+}
diff --git a/relaxed_decode_test.go b/relaxed_decode_test.go
new file mode 100644
index 0000000..75714be
--- /dev/null
+++ b/relaxed_decode_test.go
@@ -0,0 +1,575 @@
+/*
+ * Copyright (c) 2018 - present. Boling Consulting Solutions (bcsw.net)
+ * Copyright 2020-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package omci_test
+
+import (
+ "github.com/google/gopacket"
+ . "github.com/opencord/omci-lib-go"
+ me "github.com/opencord/omci-lib-go/generated"
+ "github.com/stretchr/testify/assert"
+ "testing"
+)
+
+var relaxDecodeSupportedResponses = []me.MsgType{
+ // me.Get,
+ me.MibUploadNext,
+}
+
+func canRelax(arr []me.MsgType, msgType me.MsgType) bool {
+ for _, item := range arr {
+ if item == msgType {
+ return true
+ }
+ }
+ return false
+}
+
+func TestAllTypesRelaxedDecodeSupport(t *testing.T) {
+ // Requests (none are supported yet)
+ for _, msgType := range allMsgTypes {
+ assert.Error(t, me.SetRelaxedDecode(msgType, true, true))
+ assert.False(t, me.GetRelaxedDecode(msgType, true))
+ }
+ // Responses (only a couple are supported at this time)
+ for _, msgType := range allMsgTypes {
+
+ if canRelax(relaxDecodeSupportedResponses, msgType) {
+ // Default is True if it is supported
+ assert.True(t, me.GetRelaxedDecode(msgType, false))
+
+ // Set False
+ assert.NoError(t, me.SetRelaxedDecode(msgType, false, false))
+ assert.False(t, me.GetRelaxedDecode(msgType, false))
+
+ // Set back to True
+ assert.NoError(t, me.SetRelaxedDecode(msgType, false, true))
+ assert.True(t, me.GetRelaxedDecode(msgType, false))
+ } else {
+ // Default is False
+ assert.False(t, me.GetRelaxedDecode(msgType, false))
+ assert.Error(t, me.SetRelaxedDecode(msgType, false, true))
+ assert.Error(t, me.SetRelaxedDecode(msgType, false, false))
+ }
+ }
+}
+
+// TestMibUploadNextResponseRelaxedDecode will decode a frame with 'new' unknown
+// attributes at the end (fake ones for this test) and should fail. Then it will
+// enable relax decode and should be able to successfully decode the parts that
+// it knows and have access to the rest.
+func TestMibUploadNextResponseRelaxedDecode(t *testing.T) {
+ // Test msg has OLT-G ME that normally only has 4 attributes defined. Since several are
+ // pretty big and would normally take at least 3 MIB upload next frames. So in
+ // this one it has the last 'known' one, plus two new ones.
+ extraTrailer := "123400001234000000000000"
+ goodAttribute := "123456780a090807060504030201"
+ mibUploadNextLayer := "00020000008300001c00" + goodAttribute + extraTrailer
+ goodMessage := "02862e0a" + mibUploadNextLayer + "00000028"
+
+ data, err := stringToPacket(goodMessage)
+ assert.NoError(t, err)
+
+ extraTrailerData, _ := stringToPacket(extraTrailer)
+ assert.NotNil(t, extraTrailerData)
+
+ mibUploadNextLayerData, _ := stringToPacket(mibUploadNextLayer)
+ assert.NotNil(t, mibUploadNextLayerData)
+
+ goodAttributeData, _ := stringToPacket(goodAttribute)
+ assert.NotNil(t, goodAttributeData)
+
+ // Make sure relaxed decode is disabled
+ assert.NoError(t, me.SetRelaxedDecode(me.MibUploadNext, false, false))
+ assert.False(t, me.GetRelaxedDecode(me.MibUploadNext, false))
+
+ // Should get a packet but there should also be an error layer after the OMCI layer
+ packet := gopacket.NewPacket(data, LayerTypeOMCI, gopacket.NoCopy)
+ assert.NotNil(t, packet)
+
+ omciLayer := packet.Layer(LayerTypeOMCI)
+ assert.NotNil(t, omciLayer)
+
+ omciMsg, ok := omciLayer.(*OMCI)
+ assert.True(t, ok)
+ assert.NotNil(t, omciMsg)
+
+ assert.Equal(t, LayerTypeOMCI, omciMsg.LayerType())
+ assert.Equal(t, LayerTypeOMCI, omciMsg.CanDecode())
+ assert.Equal(t, LayerTypeMibUploadNextResponse, omciMsg.NextLayerType())
+ assert.Equal(t, uint16(0x0286), omciMsg.TransactionID)
+ assert.Equal(t, MibUploadNextResponseType, omciMsg.MessageType)
+ assert.Equal(t, BaselineIdent, omciMsg.DeviceIdentifier)
+ assert.Equal(t, uint16(40), omciMsg.Length)
+
+ // Without relaxed decode, the MIB Upload Next Response cannot be decoded further
+ // but can get the error layer and it's contents (which is the entire MIB Upload Response data
+ msgLayer := packet.Layer(LayerTypeMibUploadNextResponse)
+ assert.Nil(t, msgLayer)
+
+ errLayer := packet.Layer(gopacket.LayerTypeDecodeFailure)
+ assert.NotNil(t, errLayer)
+ assert.Nil(t, errLayer.LayerPayload())
+ errContents := errLayer.LayerContents()
+ assert.NotNil(t, errContents)
+ assert.Equal(t, mibUploadNextLayerData, errContents)
+
+ ////////////////////////////////////////////////////////////////////////////
+ ////////////////////////////////////////////////////////////////////////////
+ ////////////////////////////////////////////////////////////////////////////
+ // Now turn on relaxed decode and you can now go further into the packet
+ assert.NoError(t, me.SetRelaxedDecode(me.MibUploadNext, false, true))
+ assert.True(t, me.GetRelaxedDecode(me.MibUploadNext, false))
+
+ packet = gopacket.NewPacket(data, LayerTypeOMCI, gopacket.NoCopy)
+ assert.NotNil(t, packet)
+
+ omciLayer = packet.Layer(LayerTypeOMCI)
+ assert.NotNil(t, omciLayer)
+
+ omciMsg, ok = omciLayer.(*OMCI)
+ assert.True(t, ok)
+ assert.NotNil(t, omciMsg)
+
+ // Skipping the test of OMCI layer values. It is same as above
+ //
+ // Get that message layer that has data that could be decoded. If relaxed decode was
+ // not enable, this would have returned a 'nil' value
+ msgLayer = packet.Layer(LayerTypeMibUploadNextResponse)
+ assert.NotNil(t, msgLayer)
+
+ response, ok2 := msgLayer.(*MibUploadNextResponse)
+ assert.True(t, ok2)
+ assert.NotNil(t, response)
+ assert.Equal(t, LayerTypeMibUploadNextResponse, response.LayerType())
+ assert.Equal(t, LayerTypeMibUploadNextResponse, response.CanDecode())
+ assert.Equal(t, me.OltGClassID, response.ReportedME.GetClassID())
+ assert.Equal(t, uint16(0), response.ReportedME.GetEntityID())
+
+ // The attribute mask decoded at this layer only contains the attributes we
+ // could successfully decode
+ assert.Equal(t, uint16(0x1000), response.ReportedME.GetAttributeMask())
+
+ attributes := me.AttributeValueMap{
+ "TimeOfDayInformation": goodAttributeData, // NOTE: This is binary data for the comparison below
+ }
+ for name, value := range attributes {
+ pktValue, err := response.ReportedME.GetAttribute(name)
+ assert.Nil(t, err)
+ assert.Equal(t, pktValue, value)
+ }
+ assert.Nil(t, response.AdditionalMEs)
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Here is the new functionality. In order to store both a well decoded
+ // MIB UPLOAD NEXT response layer, along with a relaxed decode error, the
+ // layer addition has to be done in a specific way and an error returned.
+ //
+ // Note that the previous line (below) worked in the code above
+ //
+ // response, ok2 := msgLayer.(*MibUploadNextResponse)
+ //
+ // If you did not care about what the relaxed decode found out, just proceed
+ // on as normal. However, since you enabled relaxed decoding of the unknown
+ // attributes, here is where you can pull extra information from.
+ //
+ // The first way is to just try and see if that error layer was decoded
+ //
+ // if unknownAttrLayer = packet.Layer(LayerTypeUnknownAttributes); unknownAttrLayer != nil {
+ // log.warning(HEY! Got some unknown attributes to this ME: '%v', unknownAttrLayer)
+ //
+ // unknownAttributes, ok2 := msgLayer.(*UnknownAttributes); ok {
+ // //
+ // // Since some extended messages can actually return multiple managed entities,
+ // // all ME's with unknown attributes need to be uniquely identified
+ // //
+ // for _, unknown := range unknownAttibutes.Attributes {
+ // whichME := unknown.EntityClass // ClassID
+ // whichInst := unknown.EntityInstance // uint16
+ // unknownMask := unknown.AttributeMask // uint16
+ // unknownBlob := unknown.Attributes // []byte
+ //
+ // // Unless this is the extended message set and only a single attribute
+ // // mask bit is set, you really do not know what possible kind of data
+ // // type the attribute is...
+ // }
+ // }
+ // }
+ /////////////////////////////////////
+ assert.NotEqual(t, gopacket.LayerTypePayload, response.NextLayerType())
+ assert.Equal(t, LayerTypeUnknownAttributes, response.NextLayerType())
+
+ unknownAttrLayer := packet.Layer(LayerTypeUnknownAttributes)
+ assert.NotNil(t, unknownAttrLayer)
+
+ unknown, ok2 := unknownAttrLayer.(*UnknownAttributes)
+ assert.True(t, ok2)
+ assert.NotNil(t, unknown)
+ assert.Equal(t, LayerTypeUnknownAttributes, unknown.LayerType())
+ assert.Equal(t, LayerTypeUnknownAttributes, unknown.CanDecode())
+ assert.Equal(t, gopacket.LayerTypeZero, unknown.NextLayerType())
+
+ // Only one Managed entity was in this response and had a bad attribute
+ assert.Equal(t, 1, len(unknown.Attributes))
+ assert.Equal(t, me.OltGClassID, unknown.Attributes[0].EntityClass)
+ assert.Equal(t, uint16(0), unknown.Attributes[0].EntityInstance)
+ assert.Equal(t, uint16(0x0c00), unknown.Attributes[0].AttributeMask)
+ assert.Equal(t, extraTrailerData, unknown.Attributes[0].AttributeData)
+}
+
+// TestMibUploadNextResponseExtendedRelaxedDecode is the extended message
+// set test of the test above (just one Managed Entity)
+func TestMibUploadNextResponseExtendedRelaxedDecode(t *testing.T) {
+ // Test msg has OLT-G ME that normally only has 4 attributes defined. Since several are
+ // pretty big and would normally take at least 3 MIB upload next frames. So in
+ // this one it has the last 'known' one, plus two new ones.
+ extraTrailer := "123400001234" // 6 octets
+ goodAttribute := "123456780a090807060504030201" // 14 octets
+ mibUploadNextLayer := "00020000" + "001c" +
+ "0014" +
+ "008300001c00" + // 6 octets
+ goodAttribute + extraTrailer // 14 + 6 octets
+ goodMessage := "02862e0b" + mibUploadNextLayer
+
+ data, err := stringToPacket(goodMessage)
+ assert.NoError(t, err)
+
+ extraTrailerData, _ := stringToPacket(extraTrailer)
+ assert.NotNil(t, extraTrailerData)
+
+ mibUploadNextLayerData, _ := stringToPacket(mibUploadNextLayer)
+ assert.NotNil(t, mibUploadNextLayerData)
+
+ goodAttributeData, _ := stringToPacket(goodAttribute)
+ assert.NotNil(t, goodAttributeData)
+
+ // Make sure relaxed decode is disabled
+ assert.NoError(t, me.SetRelaxedDecode(me.MibUploadNext, false, false))
+ assert.False(t, me.GetRelaxedDecode(me.MibUploadNext, false))
+
+ // Should get a packet but there should also be an error layer after the OMCI layer
+ packet := gopacket.NewPacket(data, LayerTypeOMCI, gopacket.NoCopy)
+ assert.NotNil(t, packet)
+
+ omciLayer := packet.Layer(LayerTypeOMCI)
+ assert.NotNil(t, omciLayer)
+
+ omciMsg, ok := omciLayer.(*OMCI)
+ assert.True(t, ok)
+ assert.NotNil(t, omciMsg)
+
+ assert.Equal(t, LayerTypeOMCI, omciMsg.LayerType())
+ assert.Equal(t, LayerTypeOMCI, omciMsg.CanDecode())
+ assert.Equal(t, LayerTypeMibUploadNextResponse, omciMsg.NextLayerType())
+ assert.Equal(t, uint16(0x0286), omciMsg.TransactionID)
+ assert.Equal(t, MibUploadNextResponseType, omciMsg.MessageType)
+ assert.Equal(t, ExtendedIdent, omciMsg.DeviceIdentifier)
+ assert.Equal(t, uint16(28), omciMsg.Length)
+
+ // Without relaxed decode, the MIB Upload Next Response cannot be decoded further
+ // but can get the error layer and it's contents (which is the entire MIB Upload Response data
+ msgLayer := packet.Layer(LayerTypeMibUploadNextResponse)
+ assert.Nil(t, msgLayer)
+
+ errLayer := packet.Layer(gopacket.LayerTypeDecodeFailure)
+ assert.NotNil(t, errLayer)
+ assert.Nil(t, errLayer.LayerPayload())
+ errContents := errLayer.LayerContents()
+ assert.NotNil(t, errContents)
+ assert.Equal(t, mibUploadNextLayerData, errContents)
+
+ ////////////////////////////////////////////////////////////////////////////
+ ////////////////////////////////////////////////////////////////////////////
+ ////////////////////////////////////////////////////////////////////////////
+ // Now turn on relaxed decode and you can now go further into the packet
+ assert.NoError(t, me.SetRelaxedDecode(me.MibUploadNext, false, true))
+ assert.True(t, me.GetRelaxedDecode(me.MibUploadNext, false))
+
+ packet = gopacket.NewPacket(data, LayerTypeOMCI, gopacket.NoCopy)
+ assert.NotNil(t, packet)
+
+ omciLayer = packet.Layer(LayerTypeOMCI)
+ assert.NotNil(t, omciLayer)
+
+ omciMsg, ok = omciLayer.(*OMCI)
+ assert.True(t, ok)
+ assert.NotNil(t, omciMsg)
+
+ // Skipping the test of OMCI layer values. It is same as above
+ //
+ // Get that message layer that has data that could be decoded
+ msgLayer = packet.Layer(LayerTypeMibUploadNextResponse)
+ assert.NotNil(t, msgLayer)
+
+ response, ok2 := msgLayer.(*MibUploadNextResponse)
+ assert.True(t, ok2)
+ assert.NotNil(t, response)
+ assert.Equal(t, LayerTypeMibUploadNextResponse, response.LayerType())
+ assert.Equal(t, LayerTypeMibUploadNextResponse, response.CanDecode())
+ assert.Equal(t, me.OltGClassID, response.ReportedME.GetClassID())
+ assert.Equal(t, uint16(0), response.ReportedME.GetEntityID())
+
+ // The attribute mask decoded at this layer only contains the attributes we
+ // could successfully decode
+ assert.Equal(t, uint16(0x1000), response.ReportedME.GetAttributeMask())
+
+ attributes := me.AttributeValueMap{
+ "TimeOfDayInformation": goodAttributeData, // NOTE: This is binary data for the comparison below
+ }
+ for name, value := range attributes {
+ pktValue, err := response.ReportedME.GetAttribute(name)
+ assert.Nil(t, err)
+ assert.Equal(t, pktValue, value)
+ }
+ ////////////////////////////////////////////////////////////////////////////
+ // Here is the new functionality. In order to store both a well decoded
+ // MIB UPLOAD NEXT response layer, along with a relaxed decode error, the
+ // layer addition has to be done in a specific way and an error returned.
+ //
+ // Note that the previous line (below) worked in the code above
+ //
+ // response, ok2 := msgLayer.(*MibUploadNextResponse)
+ //
+ // If you did not care about what the relaxed decode found out, just proceed
+ // on as normal. However, since you enabled relaxed decoding of the unknown
+ // attributes, here is where you can pull extra information from.
+ //
+ // The first way is to just try and see if that error layer was decoded
+ //
+ // if unknownAttrLayer = packet.Layer(LayerTypeUnknownAttributes); unknownAttrLayer != nil {
+ // log.warning(HEY! Got some unknown attributes to this ME: '%v', unknownAttrLayer)
+ //
+ // unknownAttributes, ok2 := msgLayer.(*UnknownAttributes); ok {
+ // //
+ // // Since some extended messages can actually return multiple managed entities,
+ // // all ME's with unknown attributes need to be uniquely identified
+ // //
+ // for _, unknown := range unknownAttibutes.Attributes {
+ // whichME := unknown.EntityClass // ClassID
+ // whichInst := unknown.EntityInstance // uint16
+ // unknownMask := unknown.AttributeMask // uint16
+ // unknownBlob := unknown.Attributes // []byte
+ //
+ // // Unless this is the extended message set and only a single attribute
+ // // mask bit is set, you really do not know what possible kind of data
+ // // type the attribute is...
+ // }
+ // }
+ // }
+ /////////////////////////////////////
+ assert.NotEqual(t, gopacket.LayerTypePayload, response.NextLayerType())
+ assert.Equal(t, LayerTypeUnknownAttributes, response.NextLayerType())
+
+ unknownAttrLayer := packet.Layer(LayerTypeUnknownAttributes)
+ assert.NotNil(t, unknownAttrLayer)
+
+ unknown, ok2 := unknownAttrLayer.(*UnknownAttributes)
+ assert.True(t, ok2)
+ assert.NotNil(t, unknown)
+ assert.Equal(t, LayerTypeUnknownAttributes, unknown.LayerType())
+ assert.Equal(t, LayerTypeUnknownAttributes, unknown.CanDecode())
+ assert.Equal(t, gopacket.LayerTypeZero, unknown.NextLayerType())
+
+ // Only one Managed entity was in this response and had a bad attribute
+ assert.Equal(t, 1, len(unknown.Attributes))
+ assert.Equal(t, me.OltGClassID, unknown.Attributes[0].EntityClass)
+ assert.Equal(t, uint16(0), unknown.Attributes[0].EntityInstance)
+ assert.Equal(t, uint16(0x0c00), unknown.Attributes[0].AttributeMask)
+ assert.Equal(t, uint16(0x0c00), unknown.Attributes[0].AttributeMask)
+ assert.Equal(t, extraTrailerData, unknown.Attributes[0].AttributeData)
+
+}
+
+// TestMibUploadNextResponseExtendedRelaxedDecode is the extended message
+// set test of the test above (with two Managed Entity where both have bad attributes)
+func TestMibUploadNextResponseExtendedRelaxedDecodeTwoMEs(t *testing.T) {
+ // Test msg has OLT-G ME that normally only has 4 attributes defined. Since several are
+ // pretty big and would normally take at least 3 MIB upload next frames. So in
+ // this one it has the last 'known' one, plus two new ones.
+ extraTrailer1 := "123400001234" // 6 octets
+ extraTrailer2 := "432100004321" // 6 octets
+ goodAttribute := "123456780a090807060504030201" // 14 octets
+ mibUploadNextLayer := "00020000" + "0038" +
+ "0014" +
+ "008300001c00" + // 6 octets
+ goodAttribute + extraTrailer1 + // 14 + 6 octets
+ "0014" +
+ "008300011c00" + // 6 octets (entity ID 1 which is invalid for OLT-g, but this is a test)
+ goodAttribute + extraTrailer2 // 14 + 6 octets
+
+ goodMessage := "02862e0b" + mibUploadNextLayer
+
+ data, err := stringToPacket(goodMessage)
+ assert.NoError(t, err)
+
+ extraTrailerData1, _ := stringToPacket(extraTrailer1)
+ assert.NotNil(t, extraTrailerData1)
+ extraTrailerData2, _ := stringToPacket(extraTrailer2)
+ assert.NotNil(t, extraTrailerData2)
+
+ mibUploadNextLayerData, _ := stringToPacket(mibUploadNextLayer)
+ assert.NotNil(t, mibUploadNextLayerData)
+
+ goodAttributeData, _ := stringToPacket(goodAttribute)
+ assert.NotNil(t, goodAttributeData)
+
+ // Make sure relaxed decode is disabled
+ assert.NoError(t, me.SetRelaxedDecode(me.MibUploadNext, false, false))
+ assert.False(t, me.GetRelaxedDecode(me.MibUploadNext, false))
+
+ // Should get a packet but there should also be an error layer after the OMCI layer
+ packet := gopacket.NewPacket(data, LayerTypeOMCI, gopacket.NoCopy)
+ assert.NotNil(t, packet)
+
+ omciLayer := packet.Layer(LayerTypeOMCI)
+ assert.NotNil(t, omciLayer)
+
+ omciMsg, ok := omciLayer.(*OMCI)
+ assert.True(t, ok)
+ assert.NotNil(t, omciMsg)
+
+ assert.Equal(t, LayerTypeOMCI, omciMsg.LayerType())
+ assert.Equal(t, LayerTypeOMCI, omciMsg.CanDecode())
+ assert.Equal(t, LayerTypeMibUploadNextResponse, omciMsg.NextLayerType())
+ assert.Equal(t, uint16(0x0286), omciMsg.TransactionID)
+ assert.Equal(t, MibUploadNextResponseType, omciMsg.MessageType)
+ assert.Equal(t, ExtendedIdent, omciMsg.DeviceIdentifier)
+ assert.Equal(t, uint16(56), omciMsg.Length)
+
+ // Without relaxed decode, the MIB Upload Next Response cannot be decoded further
+ // but can get the error layer and it's contents (which is the entire MIB Upload Response data
+ msgLayer := packet.Layer(LayerTypeMibUploadNextResponse)
+ assert.Nil(t, msgLayer)
+
+ errLayer := packet.Layer(gopacket.LayerTypeDecodeFailure)
+ assert.NotNil(t, errLayer)
+ assert.Nil(t, errLayer.LayerPayload())
+ errContents := errLayer.LayerContents()
+ assert.NotNil(t, errContents)
+ assert.Equal(t, mibUploadNextLayerData, errContents)
+
+ ////////////////////////////////////////////////////////////////////////////
+ ////////////////////////////////////////////////////////////////////////////
+ ////////////////////////////////////////////////////////////////////////////
+ // Now turn on relaxed decode and you can now go further into the packet
+ assert.NoError(t, me.SetRelaxedDecode(me.MibUploadNext, false, true))
+ assert.True(t, me.GetRelaxedDecode(me.MibUploadNext, false))
+ packet = gopacket.NewPacket(data, LayerTypeOMCI, gopacket.NoCopy)
+ assert.NotNil(t, packet)
+
+ omciLayer = packet.Layer(LayerTypeOMCI)
+ assert.NotNil(t, omciLayer)
+
+ omciMsg, ok = omciLayer.(*OMCI)
+ assert.True(t, ok)
+ assert.NotNil(t, omciMsg)
+
+ // Skipping the test of OMCI layer values. It is same as above
+ //
+ // Get that message layer that has data that could be decoded
+ msgLayer = packet.Layer(LayerTypeMibUploadNextResponse)
+ assert.NotNil(t, msgLayer)
+
+ response, ok2 := msgLayer.(*MibUploadNextResponse)
+ assert.True(t, ok2)
+ assert.NotNil(t, response)
+ assert.Equal(t, LayerTypeMibUploadNextResponse, response.LayerType())
+ assert.Equal(t, LayerTypeMibUploadNextResponse, response.CanDecode())
+ assert.Equal(t, me.OltGClassID, response.ReportedME.GetClassID())
+ assert.Equal(t, uint16(0), response.ReportedME.GetEntityID())
+
+ // The attribute mask decoded at this layer only contains the attributes we
+ // could successfully decode
+ assert.Equal(t, uint16(0x1000), response.ReportedME.GetAttributeMask())
+
+ attributes := me.AttributeValueMap{
+ "TimeOfDayInformation": goodAttributeData, // NOTE: This is binary data for the comparison below
+ }
+ for name, value := range attributes {
+ pktValue, err := response.ReportedME.GetAttribute(name)
+ assert.Nil(t, err)
+ assert.Equal(t, pktValue, value)
+ }
+ // Now the second ME in the response
+ assert.NotNil(t, response.AdditionalMEs)
+ assert.Equal(t, 1, len(response.AdditionalMEs))
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Here is the new functionality. In order to store both a well decoded
+ // MIB UPLOAD NEXT response layer, along with a relaxed decode error, the
+ // layer addition has to be done in a specific way and an error returned.
+ //
+ // Note that the previous line (below) worked in the code above
+ //
+ // response, ok2 := msgLayer.(*MibUploadNextResponse)
+ //
+ // If you did not care about what the relaxed decode found out, just proceed
+ // on as normal. However, since you enabled relaxed decoding of the unknown
+ // attributes, here is where you can pull extra information from.
+ //
+ // The first way is to just try and see if that error layer was decoded
+ //
+ // if unknownAttrLayer = packet.Layer(LayerTypeUnknownAttributes); unknownAttrLayer != nil {
+ // log.warning(HEY! Got some unknown attributes to this ME: '%v', unknownAttrLayer)
+ //
+ // unknownAttributes, ok2 := msgLayer.(*UnknownAttributes); ok {
+ // //
+ // // Since some extended messages can actually return multiple managed entities,
+ // // all ME's with unknown attributes need to be uniquely identified
+ // //
+ // for _, unknown := range unknownAttibutes.Attributes {
+ // whichME := unknown.EntityClass // ClassID
+ // whichInst := unknown.EntityInstance // uint16
+ // unknownMask := unknown.AttributeMask // uint16
+ // unknownBlob := unknown.Attributes // []byte
+ //
+ // // Unless this is the extended message set and only a single attribute
+ // // mask bit is set, you really do not know what possible kind of data
+ // // type the attribute is...
+ // }
+ // }
+ // }
+ /////////////////////////////////////
+ assert.NotEqual(t, gopacket.LayerTypePayload, response.NextLayerType())
+ assert.Equal(t, LayerTypeUnknownAttributes, response.NextLayerType())
+
+ unknownAttrLayer := packet.Layer(LayerTypeUnknownAttributes)
+ assert.NotNil(t, unknownAttrLayer)
+
+ unknown, ok2 := unknownAttrLayer.(*UnknownAttributes)
+ assert.True(t, ok2)
+ assert.NotNil(t, unknown)
+ assert.Equal(t, LayerTypeUnknownAttributes, unknown.LayerType())
+ assert.Equal(t, LayerTypeUnknownAttributes, unknown.CanDecode())
+ assert.Equal(t, gopacket.LayerTypeZero, unknown.NextLayerType())
+
+ // Only one Managed entity was in this response and had a bad attribute
+ assert.Equal(t, 2, len(unknown.Attributes))
+ assert.Equal(t, me.OltGClassID, unknown.Attributes[0].EntityClass)
+ assert.Equal(t, uint16(0), unknown.Attributes[0].EntityInstance)
+ assert.Equal(t, uint16(0x0c00), unknown.Attributes[0].AttributeMask)
+ assert.Equal(t, extraTrailerData1, unknown.Attributes[0].AttributeData)
+
+ assert.Equal(t, me.OltGClassID, unknown.Attributes[1].EntityClass)
+ assert.Equal(t, uint16(1), unknown.Attributes[1].EntityInstance)
+ assert.Equal(t, uint16(0x0c00), unknown.Attributes[1].AttributeMask)
+ assert.Equal(t, extraTrailerData2, unknown.Attributes[1].AttributeData)
+
+ errStr := unknown.Error()
+ assert.NotNil(t, errStr)
+ assert.Greater(t, len(errStr.Error()), 0)
+}