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)
+}