[VOL-4437] Adding support for unkown attributes

Change-Id: I54b1fbefdb75ca3ec4abfc83b57b232d07c76873
diff --git a/internal/common/omci/mib_test.go b/internal/common/omci/mib_test.go
index 9578416..2214f2e 100644
--- a/internal/common/omci/mib_test.go
+++ b/internal/common/omci/mib_test.go
@@ -17,6 +17,8 @@
 package omci
 
 import (
+	"encoding/binary"
+	"reflect"
 	"testing"
 
 	"github.com/google/gopacket"
@@ -45,6 +47,16 @@
 	assert.Equal(t, msgObj.Result, me.Success)
 }
 
+func TestSetTxIdInEncodedPacket(t *testing.T) {
+	basePkt := []byte{129, 42, 46, 10, 0, 2, 0, 0, 0, 37, 0, 1, 128, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 0, 0, 0, 40, 40, 206, 0, 226}
+	res := SetTxIdInEncodedPacket(basePkt, uint16(292))
+	assert.Equal(t, len(basePkt), len(res))
+
+	b := res[0:2]
+	txId := binary.BigEndian.Uint16(b)
+	assert.Equal(t, uint16(292), txId)
+}
+
 // types for TestCreateMibUploadNextResponse test
 type mibArgs struct {
 	omciPkt gopacket.Packet
@@ -92,6 +104,7 @@
 		me.OnuDataClassID,
 		onuDataEntityId,
 		me.AttributeValueMap{me.OnuData_MibDataSync: 0},
+		nil,
 	})
 
 	mibDb.items = append(mibDb.items, MibDbEntry{
@@ -103,6 +116,7 @@
 			me.CircuitPack_SerialNumber:  ToOctets("BBSM-Circuit-Pack", 20),
 			me.CircuitPack_Version:       ToOctets("v0.0.1", 20),
 		},
+		nil,
 	})
 
 	mibDb.items = append(mibDb.items, MibDbEntry{
@@ -126,6 +140,7 @@
 			me.AniG_UpperOpticalThreshold:       255,
 			me.AniG_UpperTransmitPowerThreshold: 129,
 		},
+		nil,
 	})
 
 	mibDb.items = append(mibDb.items, MibDbEntry{
@@ -142,6 +157,17 @@
 			me.Onu2G_TotalPriorityQueueNumber:                    64,
 			me.Onu2G_TotalTrafficSchedulerNumber:                 8,
 		},
+		nil,
+	})
+
+	// create an entry with UnkownAttributes set
+	var customPktTxId uint16 = 5
+	customPkt := []byte{0, byte(customPktTxId), 46, 10, 0, 2, 0, 0, 0, 37, 0, 1, 128, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 0, 0, 0, 40, 40, 206, 0, 226}
+	mibDb.items = append(mibDb.items, MibDbEntry{
+		me.ClassID(37),
+		nil,
+		me.AttributeValueMap{},
+		customPkt,
 	})
 
 	tests := []struct {
@@ -157,6 +183,8 @@
 			mibExpected{messageType: omci.MibUploadNextResponseType, transactionId: 3, entityID: anigEntityId.ToUint16(), entityClass: me.AniGClassID, attributes: map[string]interface{}{me.AniG_GemBlockLength: uint16(48)}}},
 		{"mibUploadNext-3", createTestMibUploadNextArgs(t, 4, 3),
 			mibExpected{messageType: omci.MibUploadNextResponseType, transactionId: 4, entityID: onuDataEntityId.ToUint16(), entityClass: me.Onu2GClassID, attributes: map[string]interface{}{me.Onu2G_TotalPriorityQueueNumber: uint16(64)}}},
+		{"mibUploadNext-unknown-attrs", createTestMibUploadNextArgs(t, 5, 4),
+			mibExpected{messageType: omci.MibUploadNextResponseType, transactionId: customPktTxId, entityID: 1, entityClass: me.ClassID(37), attributes: map[string]interface{}{"UnknownAttr_1": []uint8{1}}}},
 	}
 
 	for _, tt := range tests {
@@ -182,7 +210,15 @@
 			for k, v := range tt.want.attributes {
 				attr, err := msgObj.ReportedME.GetAttribute(k)
 				assert.NilError(t, err)
-				assert.Equal(t, attr, v)
+				if isSlice(attr) {
+					expected := v.([]uint8)
+					data := attr.([]uint8)
+					for i, val := range data {
+						assert.Equal(t, expected[i], val)
+					}
+				} else {
+					assert.Equal(t, attr, v)
+				}
 			}
 		})
 	}
@@ -192,3 +228,7 @@
 	_, err := CreateMibUploadNextResponse(args.omciPkt, args.omciMsg, MDS, &mibDb)
 	assert.Error(t, err, "mibdb-does-not-contain-item")
 }
+
+func isSlice(v interface{}) bool {
+	return reflect.TypeOf(v).Kind() == reflect.Slice
+}
diff --git a/internal/common/omci/mibpackets.go b/internal/common/omci/mibpackets.go
index 8b7afcb..d22908d 100755
--- a/internal/common/omci/mibpackets.go
+++ b/internal/common/omci/mibpackets.go
@@ -17,6 +17,7 @@
 package omci
 
 import (
+	"encoding/binary"
 	"errors"
 	"fmt"
 	"github.com/google/gopacket"
@@ -47,6 +48,15 @@
 	return buffer.Bytes(), nil
 }
 
+func SetTxIdInEncodedPacket(packet []byte, txId uint16) []byte {
+	valid := packet[2:]
+
+	b := make([]byte, 2)
+	binary.BigEndian.PutUint16(b, txId)
+
+	return append(b, valid...)
+}
+
 func CreateMibResetRequest(tid uint16) ([]byte, error) {
 
 	request := &omci.MibResetRequest{
@@ -106,6 +116,9 @@
 		},
 		NumberOfCommands: numberOfCommands,
 	}
+
+	omciLogger.WithFields(log.Fields{"NumberOfCommands": numberOfCommands}).Debug("mib-upload-response")
+
 	pkt, err := Serialize(omci.MibUploadResponseType, request, tid)
 	if err != nil {
 		omciLogger.WithFields(log.Fields{
@@ -175,6 +188,22 @@
 		return nil, fmt.Errorf("mibdb-does-not-contain-item")
 	}
 	currentEntry := mibDb.items[int(msgObj.CommandSequenceNumber)]
+
+	// if packet is set then we don't need to serialize the packet, it's already done
+	if currentEntry.packet != nil {
+		omciLogger.WithFields(log.Fields{
+			"CommandSequenceNumber": msgObj.CommandSequenceNumber,
+			"MibDbNumberOfCommands": mibDb.NumberOfCommands,
+			"packet":                currentEntry.packet,
+			"request-txid":          omciMsg.TransactionID,
+		}).Info("sending-custom-packet")
+
+		// NOTE we need to replace the first two bytes of the packet with the correct transactionId
+		pkt := SetTxIdInEncodedPacket(currentEntry.packet, omciMsg.TransactionID)
+
+		return pkt, nil
+	}
+
 	reportedMe, meErr := me.LoadManagedEntityDefinition(currentEntry.classId, me.ParamData{
 		EntityID:   currentEntry.entityId.ToUint16(),
 		Attributes: currentEntry.params,
diff --git a/internal/common/omci/onu_mib_db.go b/internal/common/omci/onu_mib_db.go
index 2902fd2..23d2e99 100644
--- a/internal/common/omci/onu_mib_db.go
+++ b/internal/common/omci/onu_mib_db.go
@@ -20,15 +20,20 @@
 	"bytes"
 	"encoding/binary"
 	"encoding/hex"
-
+	"github.com/google/gopacket"
 	"github.com/opencord/bbsim/internal/common"
+	"github.com/opencord/omci-lib-go/v2"
 	me "github.com/opencord/omci-lib-go/v2/generated"
 )
 
+// MibDbEntry contains all the information needed to build a
+// MibUploadNextResponse packet.
+// if Packet has a value all the other fields are ignored and the packet is sent as is.
 type MibDbEntry struct {
 	classId  me.ClassID
 	entityId EntityID
 	params   me.AttributeValueMap
+	packet   []byte
 }
 
 type MibDb struct {
@@ -105,6 +110,7 @@
 		me.OnuDataClassID,
 		EntityID{0x00, 0x00},
 		me.AttributeValueMap{me.OnuData_MibDataSync: 0}, // FIXME this needs to be parametrized before sending the response
+		nil,
 	})
 
 	// then we report the CardHolder
@@ -137,6 +143,7 @@
 			me.CircuitPack_SerialNumber:  ToOctets("BBSM-Circuit-Pack-ani", 20),
 			me.CircuitPack_Version:       ToOctets("v0.0.1", 20),
 		},
+		nil,
 	})
 	mibDb.items = append(mibDb.items, MibDbEntry{
 		me.CircuitPackClassID,
@@ -147,6 +154,7 @@
 			me.CircuitPack_OperationalState:    0,
 			me.CircuitPack_BridgedOrIpInd:      0,
 		},
+		nil,
 	})
 	mibDb.items = append(mibDb.items, MibDbEntry{
 		me.CircuitPackClassID,
@@ -158,6 +166,7 @@
 			me.CircuitPack_TotalPriorityQueueNumber:    8,
 			me.CircuitPack_TotalTrafficSchedulerNumber: 0,
 		},
+		nil,
 	})
 	mibDb.items = append(mibDb.items, MibDbEntry{
 		me.CircuitPackClassID,
@@ -165,6 +174,7 @@
 		me.AttributeValueMap{
 			me.CircuitPack_PowerShedOverride: uint32(0),
 		},
+		nil,
 	})
 
 	// ANI-G
@@ -189,6 +199,7 @@
 			me.AniG_UpperOpticalThreshold:       255,
 			me.AniG_UpperTransmitPowerThreshold: 129,
 		},
+		nil,
 	})
 
 	// circuitPack Ethernet
@@ -202,6 +213,7 @@
 			me.CircuitPack_SerialNumber:  ToOctets("BBSM-Circuit-Pack", 20),
 			me.CircuitPack_Version:       ToOctets("v0.0.1", 20),
 		},
+		nil,
 	})
 	mibDb.items = append(mibDb.items, MibDbEntry{
 		me.CircuitPackClassID,
@@ -212,6 +224,7 @@
 			me.CircuitPack_OperationalState:    0,
 			me.CircuitPack_BridgedOrIpInd:      0,
 		},
+		nil,
 	})
 	mibDb.items = append(mibDb.items, MibDbEntry{
 		me.CircuitPackClassID,
@@ -223,6 +236,7 @@
 			me.CircuitPack_TotalPriorityQueueNumber:    8,
 			me.CircuitPack_TotalTrafficSchedulerNumber: 16,
 		},
+		nil,
 	})
 	mibDb.items = append(mibDb.items, MibDbEntry{
 		me.CircuitPackClassID,
@@ -230,6 +244,7 @@
 		me.AttributeValueMap{
 			me.CircuitPack_PowerShedOverride: uint32(0),
 		},
+		nil,
 	})
 
 	if potsUniPortCount > 0 {
@@ -244,6 +259,7 @@
 				me.CircuitPack_SerialNumber:  ToOctets("BBSM-Circuit-Pack", 20),
 				me.CircuitPack_Version:       ToOctets("v0.0.1", 20),
 			},
+			nil,
 		})
 		mibDb.items = append(mibDb.items, MibDbEntry{
 			me.CircuitPackClassID,
@@ -254,6 +270,7 @@
 				me.CircuitPack_OperationalState:    0,
 				me.CircuitPack_BridgedOrIpInd:      0,
 			},
+			nil,
 		})
 		mibDb.items = append(mibDb.items, MibDbEntry{
 			me.CircuitPackClassID,
@@ -265,6 +282,7 @@
 				me.CircuitPack_TotalPriorityQueueNumber:    8,
 				me.CircuitPack_TotalTrafficSchedulerNumber: 16,
 			},
+			nil,
 		})
 		mibDb.items = append(mibDb.items, MibDbEntry{
 			me.CircuitPackClassID,
@@ -272,6 +290,7 @@
 			me.AttributeValueMap{
 				me.CircuitPack_PowerShedOverride: uint32(0),
 			},
+			nil,
 		})
 	}
 
@@ -304,6 +323,7 @@
 					me.PhysicalPathTerminationPointEthernetUni_PppoeFilter:                   0,
 					me.PhysicalPathTerminationPointEthernetUni_PowerControl:                  0,
 				},
+				nil,
 			})
 		} else {
 			// the remaining ones are pots UNIs, the same is done in onu.go
@@ -325,6 +345,7 @@
 					me.PhysicalPathTerminationPointPotsUni_NominalFeedVoltage:  0,
 					me.PhysicalPathTerminationPointPotsUni_LossOfSoftswitch:    0,
 				},
+				nil,
 			})
 		}
 
@@ -338,6 +359,7 @@
 				me.UniG_NonOmciManagementIdentifier: 0,
 				me.UniG_RelayAgentOptions:           0,
 			},
+			nil,
 		})
 
 		// Downstream Queues (related to PPTP)
@@ -351,6 +373,7 @@
 				me.PriorityQueueClassID,
 				queueEntityId, //was not reported in the original implementation
 				me.AttributeValueMap{},
+				nil,
 			})
 
 			// then we report it with the required attributes
@@ -373,6 +396,7 @@
 					me.PriorityQueue_BackPressureOccurQueueThreshold:                     0,
 					me.PriorityQueue_BackPressureClearQueueThreshold:                     0,
 				},
+				nil,
 			})
 		}
 	}
@@ -387,6 +411,7 @@
 			me.AttributeValueMap{
 				me.TCont_AllocId: 65535,
 			},
+			nil,
 		})
 
 		tsEntityId := EntityID{cardHolderSlotID, byte(i)}
@@ -399,6 +424,7 @@
 				me.TrafficScheduler_Policy:                  02,
 				me.TrafficScheduler_PriorityWeight:          0,
 			},
+			nil,
 		})
 
 		for j := 1; j <= upstreamPriorityQueues; j++ {
@@ -412,6 +438,7 @@
 				me.PriorityQueueClassID,
 				queueEntityId, //was not reported in the original implementation
 				me.AttributeValueMap{},
+				nil,
 			})
 
 			// then we report it with the required attributes
@@ -434,6 +461,7 @@
 					me.PriorityQueue_BackPressureOccurQueueThreshold:                     0,
 					me.PriorityQueue_BackPressureClearQueueThreshold:                     0,
 				},
+				nil,
 			})
 		}
 	}
@@ -453,8 +481,41 @@
 			me.Onu2G_TotalPriorityQueueNumber:                    64,
 			me.Onu2G_TotalTrafficSchedulerNumber:                 8,
 		},
+		nil,
 	})
 
+	if common.Config.BBSim.InjectOmciUnknownAttributes {
+		// NOTE the TxID is actually replaced
+		// by SetTxIdInEncodedPacket in CreateMibUploadNextResponse
+		txId := uint16(33066)
+
+		b := make([]byte, 4)
+		binary.BigEndian.PutUint16(b, txId)
+		b[2] = byte(omci.MibUploadNextResponseType)
+		b[3] = byte(omci.BaselineIdent)
+		omciHdr := hex.EncodeToString(b)
+
+		//omciHdr := "00032e0a"
+		msgHdr := "00020000"
+		reportedMeHdr := "002500018000"
+		attr := "0102030405060708090A0B0C0D0E0F101112131415161718191A"
+		trailer := "0000002828ce00e2"
+		msg := omciHdr + msgHdr + reportedMeHdr + attr + trailer
+		data, err := hex.DecodeString(msg)
+		if err != nil {
+			omciLogger.Fatal("cannot-create-custom-packet")
+		}
+
+		packet := gopacket.NewPacket(data, omci.LayerTypeOMCI, gopacket.Lazy)
+
+		mibDb.items = append(mibDb.items, MibDbEntry{
+			me.ClassID(37), // G.988 "Intentionally left blank"
+			nil,
+			me.AttributeValueMap{},
+			packet.Data(),
+		})
+	}
+
 	mibDb.NumberOfCommands = uint16(len(mibDb.items))
 
 	return &mibDb, nil
diff --git a/internal/common/omci/onu_mib_db_test.go b/internal/common/omci/onu_mib_db_test.go
index 2794aa4..7a589ad 100644
--- a/internal/common/omci/onu_mib_db_test.go
+++ b/internal/common/omci/onu_mib_db_test.go
@@ -17,9 +17,11 @@
 package omci
 
 import (
+	"fmt"
+	"github.com/google/gopacket"
+	"github.com/opencord/bbsim/internal/common"
 	"testing"
 
-	"github.com/opencord/bbsim/internal/common"
 	"github.com/opencord/omci-lib-go/v2"
 	me "github.com/opencord/omci-lib-go/v2/generated"
 	"github.com/stretchr/testify/assert"
@@ -56,6 +58,11 @@
 }
 
 func Test_GenerateMibDatabase(t *testing.T) {
+	common.Config = &common.GlobalConfig{
+		BBSim: common.BBSimConfig{
+			InjectOmciUnknownAttributes: false,
+		},
+	}
 	const uniPortCount = 4
 	mibDb, err := GenerateMibDatabase(uniPortCount, 0, common.XGSPON)
 
@@ -93,6 +100,11 @@
 }
 
 func Test_GenerateMibDatabase_withPots(t *testing.T) {
+	common.Config = &common.GlobalConfig{
+		BBSim: common.BBSimConfig{
+			InjectOmciUnknownAttributes: false,
+		},
+	}
 	const uniPortCount = 4
 	const potsPortCount = 1
 	mibDb, err := GenerateMibDatabase(uniPortCount, potsPortCount, common.XGSPON)
@@ -129,3 +141,38 @@
 	}
 
 }
+
+func Test_GenerateMibDatabase_withUnkownAttrs(t *testing.T) {
+
+	common.Config = &common.GlobalConfig{
+		BBSim: common.BBSimConfig{
+			InjectOmciUnknownAttributes: true,
+		},
+	}
+
+	const uniPortCount = 4
+	const baseMibEntries = 291                    // see Test_GenerateMibDatabase for breakdown
+	const expectedMibEntries = baseMibEntries + 1 // expecting one hardcoded packet
+	mibDb, err := GenerateMibDatabase(uniPortCount, 0, common.XGSPON)
+
+	assert.NoError(t, err)
+	assert.NotNil(t, mibDb)
+	assert.Equal(t, expectedMibEntries, int(mibDb.NumberOfCommands))
+
+	entry := mibDb.items[expectedMibEntries-1] // select the last entry, it's the hardcoded packet
+	fmt.Println(entry.packet)
+	assert.NotNil(t, entry)
+	assert.Equal(t, me.ClassID(37), entry.classId)
+	assert.Nil(t, entry.entityId)
+	assert.Equal(t, 0, len(entry.params))
+	assert.NotNil(t, entry.packet)
+
+	// check that we're generating a valid OMCI payload
+	packet := gopacket.NewPacket(entry.packet, omci.LayerTypeOMCI, gopacket.Lazy)
+	omciLayer := packet.Layer(omci.LayerTypeOMCI)
+	assert.NotNil(t, omciLayer)
+	omciMsg, ok := omciLayer.(*omci.OMCI)
+	assert.True(t, ok)
+	assert.Equal(t, omci.MibUploadNextResponseType, omciMsg.MessageType)
+	assert.Equal(t, uint16(33066), omciMsg.TransactionID)
+}