[VOL-3039] : Multi-tcont support in openonu-go adapter
Start version 0.1.14-dev141

Change-Id: I64e60efa041df317b4ce87e0aa9678b0da4ce14e
diff --git a/internal/pkg/onuadaptercore/omci_ani_config.go b/internal/pkg/onuadaptercore/omci_ani_config.go
index 4c5f22f..4d46157 100644
--- a/internal/pkg/onuadaptercore/omci_ani_config.go
+++ b/internal/pkg/onuadaptercore/omci_ani_config.go
@@ -64,6 +64,10 @@
 	aniStResetting           = "aniStResetting"
 )
 
+const (
+	tpIDOffset = 64
+)
+
 type ponAniGemPortAttribs struct {
 	gemPortID   uint16
 	upQueueID   uint16
@@ -82,7 +86,7 @@
 	pOnuUniPort              *onuUniPort
 	pUniTechProf             *onuUniTechProf
 	pOnuDB                   *onuDeviceDB
-	techProfileID            uint16
+	techProfileID            uint8
 	requestEvent             OnuDeviceEvent
 	omciMIdsResponseReceived chan bool //separate channel needed for checking multiInstance OMCI message responses
 	pAdaptFsm                *AdapterFsm
@@ -99,7 +103,7 @@
 
 //newUniPonAniConfigFsm is the 'constructor' for the state machine to config the PON ANI ports of ONU UNI ports via OMCI
 func newUniPonAniConfigFsm(apDevOmciCC *omciCC, apUniPort *onuUniPort, apUniTechProf *onuUniTechProf,
-	apOnuDB *onuDeviceDB, aTechProfileID uint16, aRequestEvent OnuDeviceEvent, aName string,
+	apOnuDB *onuDeviceDB, aTechProfileID uint8, aRequestEvent OnuDeviceEvent, aName string,
 	apDeviceHandler *deviceHandler, aCommChannel chan Message) *uniPonAniConfigFsm {
 	instFsm := &uniPonAniConfigFsm{
 		pDeviceHandler: apDeviceHandler,
@@ -185,29 +189,87 @@
 
 func (oFsm *uniPonAniConfigFsm) prepareAndEnterConfigState(aPAFsm *AdapterFsm) {
 	if aPAFsm != nil && aPAFsm.pFsm != nil {
+		uniTpKey := uniTP{uniID: oFsm.pOnuUniPort.uniID, tpID: oFsm.techProfileID}
 		//stick to pythonAdapter numbering scheme
 		//index 0 in naming refers to possible usage of multiple instances (later)
-		oFsm.mapperSP0ID = ieeeMapperServiceProfileEID + uint16(oFsm.pOnuUniPort.macBpNo) + oFsm.techProfileID
-		oFsm.macBPCD0ID = macBridgePortAniEID + uint16(oFsm.pOnuUniPort.entityID) + oFsm.techProfileID
+		oFsm.mapperSP0ID = ieeeMapperServiceProfileEID + uint16(oFsm.pOnuUniPort.macBpNo) + uint16(oFsm.techProfileID)
+		oFsm.macBPCD0ID = macBridgePortAniEID + uint16(oFsm.pOnuUniPort.entityID) + uint16(oFsm.techProfileID)
 
-		// For the time being: if there are multiple T-Conts on the ONU the first one from the entityID-ordered list is used
-		// TODO!: if more T-Conts have to be supported (tcontXID!), then use the first instances of the entity-ordered list
-		// or use the py code approach, which might be a bit more complicated, but also more secure, as it
-		// ensures that the selected T-Cont also has queues (which I would assume per definition from ONU, but who knows ...)
-		// so this approach would search the (sorted) upstream PrioQueue list and use the T-Cont (if available) from highest Bytes
-		// or sndHighByte of relatedPort Attribute (T-Cont Reference) and in case of multiple TConts find the next free TContIndex
-		// that way from PrioQueue.relatedPort list
+		/*
+			// Find a free TCONT Instance ID and use it
+			foundFreeTcontInstID := false
+		*/
 		if tcontInstKeys := oFsm.pOnuDB.getSortedInstKeys(me.TContClassID); len(tcontInstKeys) > 0 {
-			oFsm.tcont0ID = tcontInstKeys[0]
-			logger.Debugw("Used TcontId:", log.Fields{"TcontId": strconv.FormatInt(int64(oFsm.tcont0ID), 16),
-				"device-id": oFsm.deviceID})
+
+			// FIXME: Ideally the ME configurations on the ONU should constantly be MIB Synced back to the ONU DB
+			// So, as soon as we use up a TCONT Entity on the ONU, the DB at ONU adapter should know that the TCONT
+			// entity is used up via MIB Sync procedure and it will not use it for subsequent TCONT on that ONU.
+			// But, it seems, due to the absence of the constant mib-sync procedure, the TCONT Entities show up as
+			// free even though they are already reserved on the ONU. It seems the mib is synced only once, initially
+			// when the ONU is discovered.
+			/*
+				for _, tcontInstID := range tcontInstKeys {
+					tconInst := oFsm.pOnuDB.GetMe(me.TContClassID, tcontInstID)
+					returnVal := tconInst["AllocId"]
+					if returnVal != nil {
+						if allocID, err := oFsm.pOnuDB.getUint16Attrib(returnVal); err == nil {
+							// If the TCONT Instance ID is set to 0xff or 0xffff, it means it is free to use.
+							if allocID == 0xff || allocID == 0xffff {
+								foundFreeTcontInstID = true
+								oFsm.tcont0ID = uint16(tcontInstID)
+								logger.Debugw("Used TcontId:", log.Fields{"TcontId": strconv.FormatInt(int64(oFsm.tcont0ID), 16),
+									"device-id": oFsm.deviceID})
+								break
+							}
+						} else {
+							logger.Errorw("error-converting-alloc-id-to-uint16", log.Fields{"device-id": oFsm.deviceID, "tcont-inst": tcontInstID})
+						}
+					} else {
+						logger.Errorw("error-extracting-alloc-id-attribute", log.Fields{"device-id": oFsm.deviceID, "tcont-inst": tcontInstID})
+					}
+				}
+			*/
+
+			// Ensure that the techProfileID is in a valid range so that we can allocate a free Tcont for it.
+			if oFsm.techProfileID >= tpIDOffset && oFsm.techProfileID < uint8(tpIDOffset+len(tcontInstKeys)) {
+				// For now, as a dirty workaround, use the tpIDOffset to index the TcontEntityID to be used.
+				// The first TP ID for the ONU will get the first TcontEntityID, the next will get second and so on.
+				// Here the assumption is TP ID will always start from 64 (this is also true to Technology Profile Specification) and the
+				// TP ID will increment in single digit
+				oFsm.tcont0ID = tcontInstKeys[oFsm.techProfileID-tpIDOffset]
+				logger.Debugw("Used TcontId:", log.Fields{"TcontId": strconv.FormatInt(int64(oFsm.tcont0ID), 16),
+					"device-id": oFsm.deviceID})
+			} else {
+				logger.Errorw("tech profile id not in valid range", log.Fields{"device-id": oFsm.deviceID, "tp-id": oFsm.techProfileID, "num-tcont": len(tcontInstKeys)})
+				if oFsm.chanSet {
+					// indicate processing error/abort to the caller
+					oFsm.chSuccess <- 0
+					oFsm.chanSet = false //reset the internal channel state
+				}
+				//reset the state machine to enable usage on subsequent requests
+				_ = aPAFsm.pFsm.Event(aniEvReset)
+				return
+			}
 		} else {
-			logger.Warnw("No TCont instances found", log.Fields{"device-id": oFsm.deviceID})
+			logger.Errorw("No TCont instances found", log.Fields{"device-id": oFsm.deviceID})
+			return
 		}
-		oFsm.alloc0ID = (*(oFsm.pUniTechProf.mapPonAniConfig[oFsm.pOnuUniPort.uniID]))[0].tcontParams.allocID
+		/*
+			if !foundFreeTcontInstID {
+				// This should never happen. If it does, the behavior is unpredictable.
+				logger.Warnw("No free TCONT instances found", log.Fields{"device-id": oFsm.deviceID})
+			}*/
+
+		// Access critical state with lock
+		oFsm.pUniTechProf.mutexTPState.Lock()
+		oFsm.alloc0ID = oFsm.pUniTechProf.mapPonAniConfig[uniTpKey].tcontParams.allocID
+		mapGemPortParams := oFsm.pUniTechProf.mapPonAniConfig[uniTpKey].mapGemPortParams
+		oFsm.pUniTechProf.mutexTPState.Unlock()
+
 		loGemPortAttribs := ponAniGemPortAttribs{}
 		//for all TechProfile set GemIndices
-		for _, gemEntry := range (*(oFsm.pUniTechProf.mapPonAniConfig[oFsm.pOnuUniPort.uniID]))[0].mapGemPortParams {
+
+		for _, gemEntry := range mapGemPortParams {
 			//collect all GemConfigData in a separate Fsm related slice (needed also to avoid mix-up with unsorted mapPonAniConfig)
 
 			if queueInstKeys := oFsm.pOnuDB.getSortedInstKeys(me.PriorityQueueClassID); len(queueInstKeys) > 0 {
@@ -456,7 +518,7 @@
 	//use DeviceHandler event notification directly
 	oFsm.pDeviceHandler.deviceProcStatusUpdate(oFsm.requestEvent)
 	//store that the UNI related techProfile processing is done for the given Profile and Uni
-	oFsm.pUniTechProf.setConfigDone(oFsm.pOnuUniPort.uniID, true)
+	oFsm.pUniTechProf.setConfigDone(oFsm.pOnuUniPort.uniID, oFsm.techProfileID, true)
 	//if techProfile processing is done it must be checked, if some prior/parallel flow configuration is pending
 	go oFsm.pDeviceHandler.verifyUniVlanConfigRequest(oFsm.pOnuUniPort)
 
@@ -499,7 +561,7 @@
 	oFsm.pLastTxMeInstance = nil
 
 	//remove all TechProf related internal data to allow for new configuration (e.g. with disable/enable procedure)
-	oFsm.pUniTechProf.clearAniSideConfig(oFsm.pOnuUniPort.uniID)
+	oFsm.pUniTechProf.clearAniSideConfig(oFsm.pOnuUniPort.uniID, oFsm.techProfileID)
 }
 
 func (oFsm *uniPonAniConfigFsm) processOmciAniMessages( /*ctx context.Context*/ ) {