[VOL-4679] BBSIM: OMCI extended message set - support MIB upload

Change-Id: I291524f9d33194fb54c508e2344e53dd47370a8f
diff --git a/internal/common/omci/onu_mib_db.go b/internal/common/omci/onu_mib_db.go
index 08322ca..57ca215 100644
--- a/internal/common/omci/onu_mib_db.go
+++ b/internal/common/omci/onu_mib_db.go
@@ -20,6 +20,9 @@
 	"bytes"
 	"encoding/binary"
 	"encoding/hex"
+	"fmt"
+	"strings"
+
 	"github.com/google/gopacket"
 	"github.com/opencord/bbsim/internal/common"
 	"github.com/opencord/omci-lib-go/v2"
@@ -37,12 +40,23 @@
 }
 
 type MibDb struct {
-	NumberOfCommands uint16
-	items            []MibDbEntry
+	NumberOfBaselineCommands uint16
+	NumberOfExtendedCommands uint16
+	baselineItems            []MibDbEntry
+	extendedResponses        [][]byte
 }
 
 type EntityID []byte
 
+const (
+	unknownMePktReportedMeHdr     string = "002500018000"
+	unknownMePktAttributes        string = "0102030405060708090A0B0C0D0E0F101112131415161718191A"
+	unknownAttribPktReportedMeHdr string = "0101000007FD"
+	unknownAttribPktAttributes    string = "00400801000800000006007F07003F00010001000100"
+	extRespMsgContentsLenStart           = 8
+	extRespMsgContentsLenEnd             = 10
+)
+
 func (e EntityID) ToString() string {
 	return hex.EncodeToString(e)
 }
@@ -102,11 +116,11 @@
 func GenerateMibDatabase(ethUniPortCount int, potsUniPortCount int, technology common.PonTechnology) (*MibDb, error) {
 
 	mibDb := MibDb{
-		items: []MibDbEntry{},
+		baselineItems: []MibDbEntry{},
 	}
 
 	// the first element to return is the ONU-Data
-	mibDb.items = append(mibDb.items, MibDbEntry{
+	mibDb.baselineItems = append(mibDb.baselineItems, MibDbEntry{
 		me.OnuDataClassID,
 		EntityID{0x00, 0x00},
 		me.AttributeValueMap{me.OnuData_MibDataSync: 0}, // FIXME this needs to be parametrized before sending the response
@@ -134,7 +148,7 @@
 		aniCPType = gPonUnitType
 	}
 
-	mibDb.items = append(mibDb.items, MibDbEntry{
+	mibDb.baselineItems = append(mibDb.baselineItems, MibDbEntry{
 		me.CircuitPackClassID,
 		circuitPackEntityID,
 		me.AttributeValueMap{
@@ -145,7 +159,7 @@
 		},
 		nil,
 	})
-	mibDb.items = append(mibDb.items, MibDbEntry{
+	mibDb.baselineItems = append(mibDb.baselineItems, MibDbEntry{
 		me.CircuitPackClassID,
 		circuitPackEntityID,
 		me.AttributeValueMap{
@@ -156,7 +170,7 @@
 		},
 		nil,
 	})
-	mibDb.items = append(mibDb.items, MibDbEntry{
+	mibDb.baselineItems = append(mibDb.baselineItems, MibDbEntry{
 		me.CircuitPackClassID,
 		circuitPackEntityID,
 		me.AttributeValueMap{
@@ -168,7 +182,7 @@
 		},
 		nil,
 	})
-	mibDb.items = append(mibDb.items, MibDbEntry{
+	mibDb.baselineItems = append(mibDb.baselineItems, MibDbEntry{
 		me.CircuitPackClassID,
 		circuitPackEntityID,
 		me.AttributeValueMap{
@@ -178,7 +192,7 @@
 	})
 
 	// ANI-G
-	mibDb.items = append(mibDb.items, MibDbEntry{
+	mibDb.baselineItems = append(mibDb.baselineItems, MibDbEntry{
 		me.AniGClassID,
 		EntityID{tcontSlotId, aniGId},
 		me.AttributeValueMap{
@@ -204,7 +218,7 @@
 
 	// circuitPack Ethernet
 	// NOTE the circuit pack is divided in multiple messages as too big to fit in a single one
-	mibDb.items = append(mibDb.items, MibDbEntry{
+	mibDb.baselineItems = append(mibDb.baselineItems, MibDbEntry{
 		me.CircuitPackClassID,
 		circuitPackEntityID,
 		me.AttributeValueMap{
@@ -215,7 +229,7 @@
 		},
 		nil,
 	})
-	mibDb.items = append(mibDb.items, MibDbEntry{
+	mibDb.baselineItems = append(mibDb.baselineItems, MibDbEntry{
 		me.CircuitPackClassID,
 		circuitPackEntityID,
 		me.AttributeValueMap{
@@ -226,7 +240,7 @@
 		},
 		nil,
 	})
-	mibDb.items = append(mibDb.items, MibDbEntry{
+	mibDb.baselineItems = append(mibDb.baselineItems, MibDbEntry{
 		me.CircuitPackClassID,
 		circuitPackEntityID,
 		me.AttributeValueMap{
@@ -238,7 +252,7 @@
 		},
 		nil,
 	})
-	mibDb.items = append(mibDb.items, MibDbEntry{
+	mibDb.baselineItems = append(mibDb.baselineItems, MibDbEntry{
 		me.CircuitPackClassID,
 		circuitPackEntityID,
 		me.AttributeValueMap{
@@ -250,7 +264,7 @@
 	if potsUniPortCount > 0 {
 		// circuitPack POTS
 		// NOTE the circuit pack is divided in multiple messages as too big to fit in a single one
-		mibDb.items = append(mibDb.items, MibDbEntry{
+		mibDb.baselineItems = append(mibDb.baselineItems, MibDbEntry{
 			me.CircuitPackClassID,
 			circuitPackEntityID,
 			me.AttributeValueMap{
@@ -261,7 +275,7 @@
 			},
 			nil,
 		})
-		mibDb.items = append(mibDb.items, MibDbEntry{
+		mibDb.baselineItems = append(mibDb.baselineItems, MibDbEntry{
 			me.CircuitPackClassID,
 			circuitPackEntityID,
 			me.AttributeValueMap{
@@ -272,7 +286,7 @@
 			},
 			nil,
 		})
-		mibDb.items = append(mibDb.items, MibDbEntry{
+		mibDb.baselineItems = append(mibDb.baselineItems, MibDbEntry{
 			me.CircuitPackClassID,
 			circuitPackEntityID,
 			me.AttributeValueMap{
@@ -284,7 +298,7 @@
 			},
 			nil,
 		})
-		mibDb.items = append(mibDb.items, MibDbEntry{
+		mibDb.baselineItems = append(mibDb.baselineItems, MibDbEntry{
 			me.CircuitPackClassID,
 			circuitPackEntityID,
 			me.AttributeValueMap{
@@ -303,7 +317,7 @@
 
 		if i <= ethUniPortCount {
 			// first, create the correct amount of ethernet UNIs, the same is done in onu.go
-			mibDb.items = append(mibDb.items, MibDbEntry{
+			mibDb.baselineItems = append(mibDb.baselineItems, MibDbEntry{
 				me.PhysicalPathTerminationPointEthernetUniClassID,
 				uniEntityId,
 				me.AttributeValueMap{
@@ -327,7 +341,7 @@
 			})
 		} else {
 			// the remaining ones are pots UNIs, the same is done in onu.go
-			mibDb.items = append(mibDb.items, MibDbEntry{
+			mibDb.baselineItems = append(mibDb.baselineItems, MibDbEntry{
 				me.PhysicalPathTerminationPointPotsUniClassID,
 				uniEntityId,
 				me.AttributeValueMap{
@@ -349,7 +363,7 @@
 			})
 		}
 
-		mibDb.items = append(mibDb.items, MibDbEntry{
+		mibDb.baselineItems = append(mibDb.baselineItems, MibDbEntry{
 			me.UniGClassID,
 			uniEntityId,
 			me.AttributeValueMap{
@@ -369,7 +383,7 @@
 			queueEntityId := EntityID{cardHolderSlotID, byte(j)}
 
 			// we first report the PriorityQueue without any attribute
-			mibDb.items = append(mibDb.items, MibDbEntry{
+			mibDb.baselineItems = append(mibDb.baselineItems, MibDbEntry{
 				me.PriorityQueueClassID,
 				queueEntityId, //was not reported in the original implementation
 				me.AttributeValueMap{},
@@ -379,7 +393,7 @@
 			// then we report it with the required attributes
 			// In the downstream direction, the first byte is the slot number and the second byte is the port number of the queue's destination port.
 			relatedPort := append(uniEntityId, 0x00, byte(j))
-			mibDb.items = append(mibDb.items, MibDbEntry{
+			mibDb.baselineItems = append(mibDb.baselineItems, MibDbEntry{
 				me.PriorityQueueClassID,
 				queueEntityId, //was not reported in the original implementation
 				me.AttributeValueMap{
@@ -405,7 +419,7 @@
 	for i := 1; i <= tconts; i++ {
 		tcontEntityId := EntityID{tcontSlotId, byte(i)}
 
-		mibDb.items = append(mibDb.items, MibDbEntry{
+		mibDb.baselineItems = append(mibDb.baselineItems, MibDbEntry{
 			me.TContClassID,
 			tcontEntityId,
 			me.AttributeValueMap{
@@ -415,7 +429,7 @@
 		})
 
 		tsEntityId := EntityID{cardHolderSlotID, byte(i)}
-		mibDb.items = append(mibDb.items, MibDbEntry{
+		mibDb.baselineItems = append(mibDb.baselineItems, MibDbEntry{
 			me.TrafficSchedulerClassID,
 			tsEntityId, //was not reported in the original implementation
 			me.AttributeValueMap{
@@ -434,7 +448,7 @@
 			// EntityID = tcontSlotId + Uni EntityID (8001)
 
 			// we first report the PriorityQueue without any attribute
-			mibDb.items = append(mibDb.items, MibDbEntry{
+			mibDb.baselineItems = append(mibDb.baselineItems, MibDbEntry{
 				me.PriorityQueueClassID,
 				queueEntityId, //was not reported in the original implementation
 				me.AttributeValueMap{},
@@ -444,7 +458,7 @@
 			// then we report it with the required attributes
 			// In the upstream direction, the first 2 bytes are the ME ID of the associated T- CONT, the first byte of which is a slot number, the second byte a T-CONT number.
 			relatedPort := append(tcontEntityId, 0x00, byte(j))
-			mibDb.items = append(mibDb.items, MibDbEntry{
+			mibDb.baselineItems = append(mibDb.baselineItems, MibDbEntry{
 				me.PriorityQueueClassID,
 				queueEntityId, //was not reported in the original implementation
 				me.AttributeValueMap{
@@ -500,7 +514,7 @@
 		}
 	}
 
-	mibDb.items = append(mibDb.items, onu2g)
+	mibDb.baselineItems = append(mibDb.baselineItems, onu2g)
 
 	if common.Config.BBSim.InjectOmciUnknownMe {
 		// NOTE the TxID is actually replaced
@@ -515,10 +529,8 @@
 
 		//omciHdr := "00032e0a"
 		msgHdr := "00020000"
-		reportedMeHdr := "002500018000"
-		attr := "0102030405060708090A0B0C0D0E0F101112131415161718191A"
 		trailer := "0000002828ce00e2"
-		msg := omciHdr + msgHdr + reportedMeHdr + attr + trailer
+		msg := omciHdr + msgHdr + unknownMePktReportedMeHdr + unknownMePktAttributes + trailer
 		data, err := hex.DecodeString(msg)
 		if err != nil {
 			omciLogger.Fatal("cannot-create-custom-packet")
@@ -526,7 +538,7 @@
 
 		packet := gopacket.NewPacket(data, omci.LayerTypeOMCI, gopacket.Lazy)
 
-		mibDb.items = append(mibDb.items, MibDbEntry{
+		mibDb.baselineItems = append(mibDb.baselineItems, MibDbEntry{
 			me.ClassID(37), // G.988 "Intentionally left blank"
 			nil,
 			me.AttributeValueMap{},
@@ -534,7 +546,126 @@
 		})
 	}
 
-	mibDb.NumberOfCommands = uint16(len(mibDb.items))
+	mibDb.NumberOfBaselineCommands = uint16(len(mibDb.baselineItems))
 
+	// Create extended MIB upload responses
+	omciLayer := &omci.OMCI{
+		TransactionID:    0xFFFF, //to be replaced later on
+		MessageType:      omci.MibUploadNextResponseType,
+		DeviceIdentifier: omci.ExtendedIdent,
+	}
+	var i uint16 = 0
+	for i < mibDb.NumberOfBaselineCommands {
+		currentEntry := mibDb.baselineItems[i]
+		for mibDb.baselineItems[i].packet != nil {
+			// Skip any entry with a predefined packet currently used for MEs with unknown ClassID or unknown attributes.
+			// This information will be added later to the last extended response packet
+			i++
+			currentEntry = mibDb.baselineItems[i]
+		}
+		reportedME, meErr := me.LoadManagedEntityDefinition(currentEntry.classId, me.ParamData{
+			EntityID:   currentEntry.entityId.ToUint16(),
+			Attributes: currentEntry.params,
+		})
+		if meErr.GetError() != nil {
+			omciLogger.Errorf("Error while generating reportedME %s: %v", currentEntry.classId.String(), meErr.Error())
+		}
+		request := &omci.MibUploadNextResponse{
+			MeBasePacket: omci.MeBasePacket{
+				EntityClass:    me.OnuDataClassID,
+				EntityInstance: uint16(0),
+				Extended:       true,
+			},
+			ReportedME:    *reportedME,
+			AdditionalMEs: make([]me.ManagedEntity, 0),
+		}
+		i++
+		var outgoingPacket []byte
+		var outgoingPacketLen = 0
+	addMeLoop:
+		for outgoingPacketLen <= omci.MaxExtendedLength-omci.MaxBaselineLength && i < mibDb.NumberOfBaselineCommands {
+			currentEntry := mibDb.baselineItems[i]
+			for mibDb.baselineItems[i].packet != nil {
+				// Skip any entry with a predefined packet currently used for MEs with unknown ClassID or unknown attributes.
+				// This information will be added later to the last extended response packet
+				i++
+				if i < mibDb.NumberOfBaselineCommands {
+					currentEntry = mibDb.baselineItems[i]
+				} else {
+					break addMeLoop
+				}
+			}
+			additionalME, meErr := me.LoadManagedEntityDefinition(currentEntry.classId, me.ParamData{
+				EntityID:   currentEntry.entityId.ToUint16(),
+				Attributes: currentEntry.params,
+			})
+			if meErr.GetError() != nil {
+				omciLogger.Errorf("Error while generating additionalME %s: %v", currentEntry.classId.String(), meErr.Error())
+			}
+			request.AdditionalMEs = append(request.AdditionalMEs, *additionalME)
+
+			var options gopacket.SerializeOptions
+			options.FixLengths = true
+
+			buffer := gopacket.NewSerializeBuffer()
+			omciErr := gopacket.SerializeLayers(buffer, options, omciLayer, request)
+			if omciErr != nil {
+				omciLogger.Errorf("Error while serializing generating additionalME %s: %v", currentEntry.classId.String(), omciErr)
+			}
+			outgoingPacket = buffer.Bytes()
+			outgoingPacketLen = len(outgoingPacket)
+			i++
+		}
+		mibDb.extendedResponses = append(mibDb.extendedResponses, outgoingPacket)
+		mibDb.NumberOfExtendedCommands = uint16(len(mibDb.extendedResponses))
+
+		outgoingPacketString := strings.ToLower(hex.EncodeToString(mibDb.extendedResponses[mibDb.NumberOfExtendedCommands-1]))
+		omciLogger.Debugf("Extended MIB upload response respNo: %d length: %d  string: %s", mibDb.NumberOfExtendedCommands, outgoingPacketLen, outgoingPacketString)
+	}
+	// Currently, there is enough space in the last extended response to add potential MEs with unknown ClassID or unknown attributes, if requested.
+	if common.Config.BBSim.InjectOmciUnknownMe {
+		var err error
+		mibDb.extendedResponses[mibDb.NumberOfExtendedCommands-1], err = AppendAdditionalMEs(mibDb.extendedResponses[mibDb.NumberOfExtendedCommands-1], unknownMePktReportedMeHdr, unknownMePktAttributes)
+		if err != nil {
+			omciLogger.Fatal(err)
+		} else {
+			outgoingPacketString := strings.ToLower(hex.EncodeToString(mibDb.extendedResponses[mibDb.NumberOfExtendedCommands-1]))
+			omciLogger.Debugf("Reponse with unknown ME added: %s", outgoingPacketString)
+		}
+	}
+	if common.Config.BBSim.InjectOmciUnknownAttributes {
+		var err error
+		mibDb.extendedResponses[mibDb.NumberOfExtendedCommands-1], err = AppendAdditionalMEs(mibDb.extendedResponses[mibDb.NumberOfExtendedCommands-1], unknownAttribPktReportedMeHdr, unknownAttribPktAttributes)
+		if err != nil {
+			omciLogger.Fatal(err)
+		} else {
+			outgoingPacketString := strings.ToLower(hex.EncodeToString(mibDb.extendedResponses[mibDb.NumberOfExtendedCommands-1]))
+			omciLogger.Debugf("Reponse with unknown attributes added: %s", outgoingPacketString)
+		}
+	}
 	return &mibDb, nil
 }
+
+func AppendAdditionalMEs(srcSlice []byte, reportedMeHdr string, attributes string) ([]byte, error) {
+	attribBytes, err := hex.DecodeString(attributes)
+	if err != nil {
+		return nil, fmt.Errorf("cannot-decode-attributes-string")
+	}
+	attribBytesLen := len(attribBytes)
+	attribBytesLenStr := fmt.Sprintf("%04X", attribBytesLen)
+	msg := attribBytesLenStr + reportedMeHdr + attributes
+	data, err := hex.DecodeString(msg)
+	if err != nil {
+		return nil, fmt.Errorf("cannot-decode-attributes")
+	}
+	dstSlice := make([]byte, len(srcSlice))
+	copy(dstSlice, srcSlice)
+	dstSlice = append(dstSlice[:], data[:]...)
+	messageContentsLen := binary.BigEndian.Uint16(dstSlice[extRespMsgContentsLenStart:extRespMsgContentsLenEnd])
+	dataLen := len(data)
+	newMessageContentsLen := messageContentsLen + uint16(dataLen)
+	newLenSlice := make([]byte, 2)
+	binary.BigEndian.PutUint16(newLenSlice, newMessageContentsLen)
+	copy(dstSlice[extRespMsgContentsLenStart:extRespMsgContentsLenEnd], newLenSlice[0:2])
+	return dstSlice, nil
+}