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

Change-Id: I64e60efa041df317b4ce87e0aa9678b0da4ce14e
diff --git a/VERSION b/VERSION
index 7ac4e5e..fa52388 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.1.13
+0.1.14-dev141
diff --git a/internal/pkg/onuadaptercore/device_handler.go b/internal/pkg/onuadaptercore/device_handler.go
index 13f5ec5..3f53050 100644
--- a/internal/pkg/onuadaptercore/device_handler.go
+++ b/internal/pkg/onuadaptercore/device_handler.go
@@ -316,8 +316,13 @@
 			techProfMsg.UniId, dh.deviceID))
 	}
 	uniID := uint8(techProfMsg.UniId)
+	tpID, err := GetTpIDFromTpPath(techProfMsg.Path)
+	if err != nil {
+		logger.Errorw("error-parsing-tpid-from-tppath", log.Fields{"err": err, "tp-path": techProfMsg.Path})
+		return err
+	}
 
-	if bTpModify := pDevEntry.updateOnuUniTpPath(uniID, techProfMsg.Path); bTpModify {
+	if bTpModify := pDevEntry.updateOnuUniTpPath(uniID, uint8(tpID), techProfMsg.Path); bTpModify {
 		//	if there has been some change for some uni TechProfilePath
 		//in order to allow concurrent calls to other dh instances we do not wait for execution here
 		//but doing so we can not indicate problems to the caller (who does what with that then?)
@@ -330,7 +335,7 @@
 		deadline := time.Now().Add(dh.pOpenOnuAc.maxTimeoutInterAdapterComm) //allowed run time to finish before execution
 		dctx, cancel := context.WithDeadline(context.Background(), deadline)
 
-		dh.pOnuTP.resetTpProcessingErrorIndication()
+		dh.pOnuTP.resetTpProcessingErrorIndication(uniID, tpID)
 		pDevEntry.resetKvProcessingErrorIndication()
 
 		var wg sync.WaitGroup
@@ -340,7 +345,7 @@
 		go pDevEntry.updateOnuKvStore(dctx, &wg)
 		dh.waitForCompletion(cancel, &wg, "TechProfDwld") //wait for background process to finish
 
-		return dh.combineErrorStrings(dh.pOnuTP.getTpProcessingErrorIndication(), pDevEntry.getKvProcessingErrorIndication())
+		return dh.combineErrorStrings(dh.pOnuTP.getTpProcessingErrorIndication(uniID, tpID), pDevEntry.getKvProcessingErrorIndication())
 	}
 	// no change, nothing really to do - return success
 	return nil
@@ -380,6 +385,11 @@
 			delGemPortMsg.UniId, dh.deviceID))
 	}
 	uniID := uint8(delGemPortMsg.UniId)
+	tpID, err := GetTpIDFromTpPath(delGemPortMsg.TpPath)
+	if err != nil {
+		logger.Errorw("error-extracting-tp-id-from-tp-path", log.Fields{"err": err, "tp-path": delGemPortMsg.TpPath})
+		return err
+	}
 
 	//a removal of some GemPort would never remove the complete TechProfile entry (done on T-Cont)
 
@@ -387,7 +397,7 @@
 	deadline := time.Now().Add(dh.pOpenOnuAc.maxTimeoutInterAdapterComm) //allowed run time to finish before execution
 	dctx, cancel := context.WithDeadline(context.Background(), deadline)
 
-	dh.pOnuTP.resetTpProcessingErrorIndication()
+	dh.pOnuTP.resetTpProcessingErrorIndication(uniID, tpID)
 
 	var wg sync.WaitGroup
 	wg.Add(1) // for the 1 go routine to finish
@@ -395,7 +405,7 @@
 		cResourceGemPort, delGemPortMsg.GemPortId, &wg)
 	dh.waitForCompletion(cancel, &wg, "GemDelete") //wait for background process to finish
 
-	return dh.pOnuTP.getTpProcessingErrorIndication()
+	return dh.pOnuTP.getTpProcessingErrorIndication(uniID, tpID)
 }
 
 func (dh *deviceHandler) processInterAdapterDeleteTcontReqMessage(
@@ -432,13 +442,19 @@
 			delTcontMsg.UniId, dh.deviceID))
 	}
 	uniID := uint8(delTcontMsg.UniId)
+	tpPath := delTcontMsg.TpPath
+	tpID, err := GetTpIDFromTpPath(tpPath)
+	if err != nil {
+		logger.Errorw("error-extracting-tp-id-from-tp-path", log.Fields{"err": err, "tp-path": tpPath})
+		return err
+	}
 
-	if bTpModify := pDevEntry.updateOnuUniTpPath(uniID, ""); bTpModify {
+	if bTpModify := pDevEntry.updateOnuUniTpPath(uniID, tpID, ""); bTpModify {
 		// deadline context to ensure completion of background routines waited for
 		deadline := time.Now().Add(dh.pOpenOnuAc.maxTimeoutInterAdapterComm) //allowed run time to finish before execution
 		dctx, cancel := context.WithDeadline(context.Background(), deadline)
 
-		dh.pOnuTP.resetTpProcessingErrorIndication()
+		dh.pOnuTP.resetTpProcessingErrorIndication(uniID, tpID)
 		pDevEntry.resetKvProcessingErrorIndication()
 
 		var wg sync.WaitGroup
@@ -449,7 +465,7 @@
 		go pDevEntry.updateOnuKvStore(dctx, &wg)
 		dh.waitForCompletion(cancel, &wg, "TContDelete") //wait for background process to finish
 
-		return dh.combineErrorStrings(dh.pOnuTP.getTpProcessingErrorIndication(), pDevEntry.getKvProcessingErrorIndication())
+		return dh.combineErrorStrings(dh.pOnuTP.getTpProcessingErrorIndication(uniID, tpID), pDevEntry.getKvProcessingErrorIndication())
 	}
 	return nil
 }
@@ -726,20 +742,20 @@
 	defer dh.pOnuTP.unlockTpProcMutex()
 
 	for _, uniData := range pDevEntry.sOnuPersistentData.PersUniConfig {
-		// deadline context to ensure completion of background routines waited for
-		//20200721: 10s proved to be less in 8*8 ONU test on local vbox machine with debug, might be further adapted
-		deadline := time.Now().Add(dh.pOpenOnuAc.maxTimeoutInterAdapterComm) //allowed run time to finish before execution
-		dctx, cancel := context.WithDeadline(context.Background(), deadline)
+		for tpID := range uniData.PersTpPathMap {
+			// deadline context to ensure completion of background routines waited for
+			//20200721: 10s proved to be less in 8*8 ONU test on local vbox machine with debug, might be further adapted
+			deadline := time.Now().Add(dh.pOpenOnuAc.maxTimeoutInterAdapterComm) //allowed run time to finish before execution
+			dctx, cancel := context.WithDeadline(context.Background(), deadline)
 
-		dh.pOnuTP.resetTpProcessingErrorIndication()
-
-		var wg sync.WaitGroup
-		wg.Add(1) // for the 1 go routine to finish
-		go dh.pOnuTP.configureUniTp(dctx, uniData.PersUniID, uniData.PersTpPath, &wg)
-		dh.waitForCompletion(cancel, &wg, "TechProfReconcile") //wait for background process to finish
-
-		if err := dh.pOnuTP.getTpProcessingErrorIndication(); err != nil {
-			logger.Errorw(err.Error(), log.Fields{"device-id": dh.deviceID})
+			dh.pOnuTP.resetTpProcessingErrorIndication(uniData.PersUniID, tpID)
+			var wg sync.WaitGroup
+			wg.Add(1) // for the 1 go routine to finish
+			go dh.pOnuTP.configureUniTp(dctx, uniData.PersUniID, uniData.PersTpPathMap[tpID], &wg)
+			dh.waitForCompletion(cancel, &wg, "TechProfReconcile") //wait for background process to finish
+			if err := dh.pOnuTP.getTpProcessingErrorIndication(uniData.PersUniID, tpID); err != nil {
+				logger.Errorw(err.Error(), log.Fields{"device-id": dh.deviceID})
+			}
 		}
 	}
 }
@@ -1438,7 +1454,9 @@
 		// should always be the case here
 		// FSM  stop maybe encapsulated as OnuTP method - perhaps later in context of module splitting
 		if dh.pOnuTP.pAniConfigFsm != nil {
-			_ = dh.pOnuTP.pAniConfigFsm.pAdaptFsm.pFsm.Event(aniEvReset)
+			for uniTP := range dh.pOnuTP.pAniConfigFsm {
+				_ = dh.pOnuTP.pAniConfigFsm[uniTP].pAdaptFsm.pFsm.Event(aniEvReset)
+			}
 		}
 		for _, uniPort := range dh.uniEntityMap {
 			// reset the possibly existing VlanConfigFsm
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*/ ) {
diff --git a/internal/pkg/onuadaptercore/omci_vlan_config.go b/internal/pkg/onuadaptercore/omci_vlan_config.go
index 36efa13..561212c 100644
--- a/internal/pkg/onuadaptercore/omci_vlan_config.go
+++ b/internal/pkg/onuadaptercore/omci_vlan_config.go
@@ -213,16 +213,16 @@
 			{Name: vlanEvRestart, Src: []string{vlanStResetting}, Dst: vlanStDisabled},
 		},
 		fsm.Callbacks{
-			"enter_state":                     func(e *fsm.Event) { instFsm.pAdaptFsm.logFsmStateChange(e) },
-			("enter_" + vlanStStarting):       func(e *fsm.Event) { instFsm.enterConfigStarting(e) },
-			("enter_" + vlanStConfigVtfd):     func(e *fsm.Event) { instFsm.enterConfigVtfd(e) },
-			("enter_" + vlanStConfigEvtocd):   func(e *fsm.Event) { instFsm.enterConfigEvtocd(e) },
-			("enter_" + vlanStConfigDone):     func(e *fsm.Event) { instFsm.enterVlanConfigDone(e) },
-			("enter_" + vlanStConfigIncrFlow): func(e *fsm.Event) { instFsm.enterConfigIncrFlow(e) },
-			("enter_" + vlanStRemoveFlow):     func(e *fsm.Event) { instFsm.enterRemoveFlow(e) },
-			("enter_" + vlanStCleanupDone):    func(e *fsm.Event) { instFsm.enterVlanCleanupDone(e) },
-			("enter_" + vlanStResetting):      func(e *fsm.Event) { instFsm.enterResetting(e) },
-			("enter_" + vlanStDisabled):       func(e *fsm.Event) { instFsm.enterDisabled(e) },
+			"enter_state":                   func(e *fsm.Event) { instFsm.pAdaptFsm.logFsmStateChange(e) },
+			"enter_" + vlanStStarting:       func(e *fsm.Event) { instFsm.enterConfigStarting(e) },
+			"enter_" + vlanStConfigVtfd:     func(e *fsm.Event) { instFsm.enterConfigVtfd(e) },
+			"enter_" + vlanStConfigEvtocd:   func(e *fsm.Event) { instFsm.enterConfigEvtocd(e) },
+			"enter_" + vlanStConfigDone:     func(e *fsm.Event) { instFsm.enterVlanConfigDone(e) },
+			"enter_" + vlanStConfigIncrFlow: func(e *fsm.Event) { instFsm.enterConfigIncrFlow(e) },
+			"enter_" + vlanStRemoveFlow:     func(e *fsm.Event) { instFsm.enterRemoveFlow(e) },
+			"enter_" + vlanStCleanupDone:    func(e *fsm.Event) { instFsm.enterVlanCleanupDone(e) },
+			"enter_" + vlanStResetting:      func(e *fsm.Event) { instFsm.enterResetting(e) },
+			"enter_" + vlanStDisabled:       func(e *fsm.Event) { instFsm.enterDisabled(e) },
 		},
 	)
 	if instFsm.pAdaptFsm.pFsm == nil {
@@ -393,7 +393,7 @@
 				"Cookies":  oFsm.uniVlanFlowParamsSlice[oFsm.numUniFlows].CookieSlice,
 				"MatchVid": strconv.FormatInt(int64(loRuleParams.MatchVid), 16),
 				"SetVid":   strconv.FormatInt(int64(loRuleParams.SetVid), 16),
-				"SetPcp":   loRuleParams.SetPcp, "numberofFlows": (oFsm.numUniFlows + 1),
+				"SetPcp":   loRuleParams.SetPcp, "numberofFlows": oFsm.numUniFlows + 1,
 				"device-id": oFsm.deviceID})
 
 			oFsm.numUniFlows++
@@ -482,7 +482,7 @@
 						oFsm.uniVlanFlowParamsSlice = nil //reset the slice
 						//at this point it is evident that no flow anymore refers to a still possibly active Techprofile
 						//request that this profile gets deleted before a new flow add is allowed
-						oFsm.pUniTechProf.setProfileToDelete(oFsm.pOnuUniPort.uniID, true)
+						oFsm.pUniTechProf.setProfileToDelete(oFsm.pOnuUniPort.uniID, uint8(oFsm.techProfileID), true)
 						logger.Debugw("UniVlanConfigFsm flow removal - no more flows", log.Fields{
 							"device-id": oFsm.deviceID})
 					} else {
@@ -512,7 +512,7 @@
 							logger.Debugw("UniVlanConfigFsm tp-id used in deleted flow is not used anymore", log.Fields{
 								"device-id": oFsm.deviceID, "tp-id": usedTpID})
 							//request that this profile gets deleted before a new flow add is allowed
-							oFsm.pUniTechProf.setProfileToDelete(oFsm.pOnuUniPort.uniID, true)
+							oFsm.pUniTechProf.setProfileToDelete(oFsm.pOnuUniPort.uniID, uint8(oFsm.techProfileID), true)
 						}
 						logger.Debugw("UniVlanConfigFsm flow removal - specific flow removed from data", log.Fields{
 							"device-id": oFsm.deviceID})
@@ -536,7 +536,7 @@
 					// state transition notification is checked in deviceHandler
 					if oFsm.pDeviceHandler != nil {
 						//making use of the add->remove successor enum assumption/definition
-						go oFsm.pDeviceHandler.deviceProcStatusUpdate(OnuDeviceEvent((uint8(oFsm.requestEvent) + 1)))
+						go oFsm.pDeviceHandler.deviceProcStatusUpdate(OnuDeviceEvent(uint8(oFsm.requestEvent) + 1))
 					}
 					logger.Debugw("UniVlanConfigFsm flow removal - rule persists with still valid cookies", log.Fields{
 						"device-id": oFsm.deviceID, "cookies": oFsm.uniVlanFlowParamsSlice[flow].CookieSlice})
@@ -565,7 +565,7 @@
 		// state transition notification is checked in deviceHandler
 		if oFsm.pDeviceHandler != nil {
 			//making use of the add->remove successor enum assumption/definition
-			go oFsm.pDeviceHandler.deviceProcStatusUpdate(OnuDeviceEvent((uint8(oFsm.requestEvent) + 1)))
+			go oFsm.pDeviceHandler.deviceProcStatusUpdate(OnuDeviceEvent(uint8(oFsm.requestEvent) + 1))
 		}
 		return nil
 	} //unknown cookie
@@ -593,7 +593,7 @@
 				//cmp also usage in EVTOCDE create in omci_cc
 				oFsm.evtocdID = macBridgeServiceProfileEID + uint16(oFsm.pOnuUniPort.macBpNo)
 
-				if oFsm.pUniTechProf.getTechProfileDone(oFsm.pOnuUniPort.uniID, oFsm.techProfileID) {
+				if oFsm.pUniTechProf.getTechProfileDone(oFsm.pOnuUniPort.uniID, uint8(oFsm.techProfileID)) {
 					// let the vlan processing begin
 					_ = a_pAFsm.pFsm.Event(vlanEvStartConfig)
 				} else {
@@ -684,13 +684,13 @@
 	// state transition notification is checked in deviceHandler
 	if oFsm.pDeviceHandler != nil {
 		//making use of the add->remove successor enum assumption/definition
-		go oFsm.pDeviceHandler.deviceProcStatusUpdate(OnuDeviceEvent((uint8(oFsm.requestEvent) + oFsm.requestEventOffset)))
+		go oFsm.pDeviceHandler.deviceProcStatusUpdate(OnuDeviceEvent(uint8(oFsm.requestEvent) + oFsm.requestEventOffset))
 	}
 }
 
 func (oFsm *UniVlanConfigFsm) enterConfigIncrFlow(e *fsm.Event) {
 	logger.Debugw("UniVlanConfigFsm - start config further incremental flow", log.Fields{
-		"in state": e.FSM.Current(), "recent flow-number": (oFsm.configuredUniFlow),
+		"in state": e.FSM.Current(), "recent flow-number": oFsm.configuredUniFlow,
 		"device-id": oFsm.deviceID})
 	oFsm.mutexFlowParams.Lock()
 
@@ -859,7 +859,7 @@
 						EntityID: oFsm.vtfdID,
 						Attributes: me.AttributeValueMap{
 							"VlanFilterList":  vtfdFilterList,
-							"NumberOfEntries": (oFsm.numVlanFilterEntries - 1), //one element less
+							"NumberOfEntries": oFsm.numVlanFilterEntries - 1, //one element less
 						},
 					}
 					meInstance := oFsm.pOmciCC.sendSetVtfdVar(context.TODO(), ConstDefaultOmciTimeout, true,
diff --git a/internal/pkg/onuadaptercore/onu_device_db.go b/internal/pkg/onuadaptercore/onu_device_db.go
index db5a84b..eb069ce 100644
--- a/internal/pkg/onuadaptercore/onu_device_db.go
+++ b/internal/pkg/onuadaptercore/onu_device_db.go
@@ -108,6 +108,18 @@
 	}
 }
 
+/*
+func (onuDeviceDB *onuDeviceDB) getUint16Attrib(meAttribute interface{}) (uint16, error) {
+
+	switch reflect.TypeOf(meAttribute).Kind() {
+	case reflect.Uint16:
+		return meAttribute.(uint16), nil
+	default:
+		return uint16(0), fmt.Errorf(fmt.Sprintf("wrong interface-type received-%s", onuDeviceDB.pOnuDeviceEntry.deviceID))
+	}
+}
+*/
+
 func (onuDeviceDB *onuDeviceDB) getSortedInstKeys(meClassID me.ClassID) []uint16 {
 
 	var meInstKeys []uint16
diff --git a/internal/pkg/onuadaptercore/onu_device_entry.go b/internal/pkg/onuadaptercore/onu_device_entry.go
index 2f726a4..5217784 100644
--- a/internal/pkg/onuadaptercore/onu_device_entry.go
+++ b/internal/pkg/onuadaptercore/onu_device_entry.go
@@ -204,8 +204,8 @@
 
 type uniPersConfig struct {
 	PersUniID      uint8               `json:"uni_id"`
-	PersTpPath     string              `json:"tp_path"`
-	PersFlowParams []uniVlanFlowParams `json:"flow_params"` //as defined in omci_ani_config.go
+	PersTpPathMap  map[uint8]string    `json:"PersTpPathMap"` // tp-id to tp-path map
+	PersFlowParams []uniVlanFlowParams `json:"flow_params"`   //as defined in omci_ani_config.go
 }
 
 type onuPersistentData struct {
@@ -352,21 +352,21 @@
 		},
 
 		fsm.Callbacks{
-			"enter_state":                           func(e *fsm.Event) { onuDeviceEntry.pMibUploadFsm.logFsmStateChange(e) },
-			("enter_" + ulStStarting):               func(e *fsm.Event) { onuDeviceEntry.enterStartingState(e) },
-			("enter_" + ulStResettingMib):           func(e *fsm.Event) { onuDeviceEntry.enterResettingMibState(e) },
-			("enter_" + ulStGettingVendorAndSerial): func(e *fsm.Event) { onuDeviceEntry.enterGettingVendorAndSerialState(e) },
-			("enter_" + ulStGettingEquipmentID):     func(e *fsm.Event) { onuDeviceEntry.enterGettingEquipmentIDState(e) },
-			("enter_" + ulStGettingFirstSwVersion):  func(e *fsm.Event) { onuDeviceEntry.enterGettingFirstSwVersionState(e) },
-			("enter_" + ulStGettingSecondSwVersion): func(e *fsm.Event) { onuDeviceEntry.enterGettingSecondSwVersionState(e) },
-			("enter_" + ulStGettingMacAddress):      func(e *fsm.Event) { onuDeviceEntry.enterGettingMacAddressState(e) },
-			("enter_" + ulStGettingMibTemplate):     func(e *fsm.Event) { onuDeviceEntry.enterGettingMibTemplate(e) },
-			("enter_" + ulStUploading):              func(e *fsm.Event) { onuDeviceEntry.enterUploadingState(e) },
-			("enter_" + ulStExaminingMds):           func(e *fsm.Event) { onuDeviceEntry.enterExaminingMdsState(e) },
-			("enter_" + ulStResynchronizing):        func(e *fsm.Event) { onuDeviceEntry.enterResynchronizingState(e) },
-			("enter_" + ulStAuditing):               func(e *fsm.Event) { onuDeviceEntry.enterAuditingState(e) },
-			("enter_" + ulStOutOfSync):              func(e *fsm.Event) { onuDeviceEntry.enterOutOfSyncState(e) },
-			("enter_" + ulStInSync):                 func(e *fsm.Event) { onuDeviceEntry.enterInSyncState(e) },
+			"enter_state":                         func(e *fsm.Event) { onuDeviceEntry.pMibUploadFsm.logFsmStateChange(e) },
+			"enter_" + ulStStarting:               func(e *fsm.Event) { onuDeviceEntry.enterStartingState(e) },
+			"enter_" + ulStResettingMib:           func(e *fsm.Event) { onuDeviceEntry.enterResettingMibState(e) },
+			"enter_" + ulStGettingVendorAndSerial: func(e *fsm.Event) { onuDeviceEntry.enterGettingVendorAndSerialState(e) },
+			"enter_" + ulStGettingEquipmentID:     func(e *fsm.Event) { onuDeviceEntry.enterGettingEquipmentIDState(e) },
+			"enter_" + ulStGettingFirstSwVersion:  func(e *fsm.Event) { onuDeviceEntry.enterGettingFirstSwVersionState(e) },
+			"enter_" + ulStGettingSecondSwVersion: func(e *fsm.Event) { onuDeviceEntry.enterGettingSecondSwVersionState(e) },
+			"enter_" + ulStGettingMacAddress:      func(e *fsm.Event) { onuDeviceEntry.enterGettingMacAddressState(e) },
+			"enter_" + ulStGettingMibTemplate:     func(e *fsm.Event) { onuDeviceEntry.enterGettingMibTemplate(e) },
+			"enter_" + ulStUploading:              func(e *fsm.Event) { onuDeviceEntry.enterUploadingState(e) },
+			"enter_" + ulStExaminingMds:           func(e *fsm.Event) { onuDeviceEntry.enterExaminingMdsState(e) },
+			"enter_" + ulStResynchronizing:        func(e *fsm.Event) { onuDeviceEntry.enterResynchronizingState(e) },
+			"enter_" + ulStAuditing:               func(e *fsm.Event) { onuDeviceEntry.enterAuditingState(e) },
+			"enter_" + ulStOutOfSync:              func(e *fsm.Event) { onuDeviceEntry.enterOutOfSyncState(e) },
+			"enter_" + ulStInSync:                 func(e *fsm.Event) { onuDeviceEntry.enterInSyncState(e) },
 		},
 	)
 	// Omci related Mib download state machine
@@ -397,13 +397,13 @@
 		},
 
 		fsm.Callbacks{
-			"enter_state":                 func(e *fsm.Event) { onuDeviceEntry.pMibDownloadFsm.logFsmStateChange(e) },
-			("enter_" + dlStStarting):     func(e *fsm.Event) { onuDeviceEntry.enterDLStartingState(e) },
-			("enter_" + dlStCreatingGal):  func(e *fsm.Event) { onuDeviceEntry.enterCreatingGalState(e) },
-			("enter_" + dlStSettingOnu2g): func(e *fsm.Event) { onuDeviceEntry.enterSettingOnu2gState(e) },
-			("enter_" + dlStBridgeInit):   func(e *fsm.Event) { onuDeviceEntry.enterBridgeInitState(e) },
-			("enter_" + dlStDownloaded):   func(e *fsm.Event) { onuDeviceEntry.enterDownloadedState(e) },
-			("enter_" + dlStResetting):    func(e *fsm.Event) { onuDeviceEntry.enterResettingState(e) },
+			"enter_state":               func(e *fsm.Event) { onuDeviceEntry.pMibDownloadFsm.logFsmStateChange(e) },
+			"enter_" + dlStStarting:     func(e *fsm.Event) { onuDeviceEntry.enterDLStartingState(e) },
+			"enter_" + dlStCreatingGal:  func(e *fsm.Event) { onuDeviceEntry.enterCreatingGalState(e) },
+			"enter_" + dlStSettingOnu2g: func(e *fsm.Event) { onuDeviceEntry.enterSettingOnu2gState(e) },
+			"enter_" + dlStBridgeInit:   func(e *fsm.Event) { onuDeviceEntry.enterBridgeInitState(e) },
+			"enter_" + dlStDownloaded:   func(e *fsm.Event) { onuDeviceEntry.enterDownloadedState(e) },
+			"enter_" + dlStResetting:    func(e *fsm.Event) { onuDeviceEntry.enterResettingState(e) },
 		},
 	)
 	if onuDeviceEntry.pMibDownloadFsm == nil || onuDeviceEntry.pMibDownloadFsm.pFsm == nil {
@@ -633,7 +633,7 @@
 	oo.chOnuKvProcessingStep <- aProcessingStep //done
 }
 
-func (oo *OnuDeviceEntry) updateOnuUniTpPath(aUniID uint8, aPathString string) bool {
+func (oo *OnuDeviceEntry) updateOnuUniTpPath(aUniID uint8, aTpID uint8, aPathString string) bool {
 	/* within some specific InterAdapter processing request write/read access to data is ensured to be sequentially,
 	   as also the complete sequence is ensured to 'run to completion' before some new request is accepted
 	   no specific concurrency protection to sOnuPersistentData is required here
@@ -641,16 +641,19 @@
 	for k, v := range oo.sOnuPersistentData.PersUniConfig {
 		if v.PersUniID == aUniID {
 			logger.Debugw("PersUniConfig-entry already exists", log.Fields{"device-id": oo.deviceID, "uniID": aUniID})
-			existingPath := oo.sOnuPersistentData.PersUniConfig[k].PersTpPath
+			existingPath, ok := oo.sOnuPersistentData.PersUniConfig[k].PersTpPathMap[aTpID]
+			if !ok {
+				logger.Debugw("tp-does-not-exist--to-be-created-afresh", log.Fields{"device-id": oo.deviceID, "uniID": aUniID, "tpID": aTpID, "path": aPathString})
+			}
 			if existingPath != aPathString {
 				if aPathString == "" {
 					//existing entry to be deleted
 					logger.Debugw("UniTp delete path value", log.Fields{"device-id": oo.deviceID, "uniID": aUniID, "path": aPathString})
-					oo.sOnuPersistentData.PersUniConfig[k].PersTpPath = ""
+					oo.sOnuPersistentData.PersUniConfig[k].PersTpPathMap[aTpID] = ""
 				} else {
 					//existing entry to be modified
 					logger.Debugw("UniTp modify path value", log.Fields{"device-id": oo.deviceID, "uniID": aUniID, "path": aPathString})
-					oo.sOnuPersistentData.PersUniConfig[k].PersTpPath = aPathString
+					oo.sOnuPersistentData.PersUniConfig[k].PersTpPathMap[aTpID] = aPathString
 				}
 				return true
 			}
@@ -677,7 +680,7 @@
 				// and to be sure, that for some reason the corresponding TpDelete was lost somewhere in history
 				//  we also reset a possibly outstanding delete request - repeated TpConfig is regarded as valid for waiting flow config
 				if oo.baseDeviceHandler.pOnuTP != nil {
-					oo.baseDeviceHandler.pOnuTP.setProfileToDelete(aUniID, false)
+					oo.baseDeviceHandler.pOnuTP.setProfileToDelete(aUniID, aTpID, false)
 				}
 				go oo.baseDeviceHandler.VerifyVlanConfigRequest(aUniID)
 			}
@@ -693,8 +696,10 @@
 	}
 	//new entry to be created
 	logger.Debugw("New UniTp path set", log.Fields{"device-id": oo.deviceID, "uniID": aUniID, "path": aPathString})
+	perSubTpPathMap := make(map[uint8]string)
+	perSubTpPathMap[aTpID] = aPathString
 	oo.sOnuPersistentData.PersUniConfig =
-		append(oo.sOnuPersistentData.PersUniConfig, uniPersConfig{PersUniID: aUniID, PersTpPath: aPathString, PersFlowParams: make([]uniVlanFlowParams, 0)})
+		append(oo.sOnuPersistentData.PersUniConfig, uniPersConfig{PersUniID: aUniID, PersTpPathMap: perSubTpPathMap, PersFlowParams: make([]uniVlanFlowParams, 0)})
 	return true
 }
 
@@ -708,7 +713,7 @@
 		}
 	}
 	//flow update was faster than tp-config - create PersUniConfig-entry
-	tmpConfig := uniPersConfig{PersUniID: aUniID, PersTpPath: "", PersFlowParams: make([]uniVlanFlowParams, len(*aUniVlanFlowParams))}
+	tmpConfig := uniPersConfig{PersUniID: aUniID, PersTpPathMap: make(map[uint8]string), PersFlowParams: make([]uniVlanFlowParams, len(*aUniVlanFlowParams))}
 	copy(tmpConfig.PersFlowParams, *aUniVlanFlowParams)
 	oo.sOnuPersistentData.PersUniConfig = append(oo.sOnuPersistentData.PersUniConfig, tmpConfig)
 }
diff --git a/internal/pkg/onuadaptercore/onu_uni_tp.go b/internal/pkg/onuadaptercore/onu_uni_tp.go
index 14550f6..e7a1843 100644
--- a/internal/pkg/onuadaptercore/onu_uni_tp.go
+++ b/internal/pkg/onuadaptercore/onu_uni_tp.go
@@ -22,7 +22,6 @@
 	"encoding/json"
 	"errors"
 	"fmt"
-	"strconv"
 	"strings"
 	"sync"
 
@@ -52,7 +51,7 @@
 
 type tTechProfileIndication struct {
 	techProfileType       string
-	techProfileID         uint16
+	techProfileID         uint8
 	techProfileConfigDone bool
 	techProfileToDelete   bool
 }
@@ -81,8 +80,11 @@
 	mapGemPortParams map[uint16]*gemPortParamStruct
 }
 
-//refers to all tcont and their Tcont/GemPort Parameters
-type tMapPonAniConfig map[uint16]*tcontGemList
+// refers a unique combination of uniID and tpID for a given ONU.
+type uniTP struct {
+	uniID uint8
+	tpID  uint8
+}
 
 //onuUniTechProf structure holds information about the TechProfiles attached to Uni Ports of the ONU
 type onuUniTechProf struct {
@@ -91,12 +93,12 @@
 	tpProcMutex              sync.RWMutex
 	techProfileKVStore       *db.Backend
 	chTpConfigProcessingStep chan uint8
-	mapUniTpIndication       map[uint8]*tTechProfileIndication //use pointer values to ease assignments to the map
-	mapPonAniConfig          map[uint8]*tMapPonAniConfig       //per UNI: use pointer values to ease assignments to the map
-	pAniConfigFsm            *uniPonAniConfigFsm
-	procResult               error //error indication of processing
+	mapUniTpIndication       map[uniTP]*tTechProfileIndication //use pointer values to ease assignments to the map
+	mapPonAniConfig          map[uniTP]*tcontGemList           //per UNI: use pointer values to ease assignments to the map
+	pAniConfigFsm            map[uniTP]*uniPonAniConfigFsm
+	procResult               map[uniTP]error //error indication of processing
 	mutexTPState             sync.Mutex
-	tpProfileExists          bool
+	tpProfileExists          map[uniTP]bool
 }
 
 //newOnuUniTechProf returns the instance of a OnuUniTechProf
@@ -108,10 +110,10 @@
 	onuTP.deviceID = aDeviceHandler.deviceID
 	onuTP.tpProcMutex = sync.RWMutex{}
 	onuTP.chTpConfigProcessingStep = make(chan uint8)
-	onuTP.mapUniTpIndication = make(map[uint8]*tTechProfileIndication)
-	onuTP.mapPonAniConfig = make(map[uint8]*tMapPonAniConfig)
-	onuTP.procResult = nil //default assumption processing done with success
-
+	onuTP.mapUniTpIndication = make(map[uniTP]*tTechProfileIndication)
+	onuTP.mapPonAniConfig = make(map[uniTP]*tcontGemList)
+	onuTP.procResult = make(map[uniTP]error)
+	onuTP.tpProfileExists = make(map[uniTP]bool)
 	onuTP.techProfileKVStore = aDeviceHandler.setBackend(cBasePathTechProfileKVStore)
 	if onuTP.techProfileKVStore == nil {
 		logger.Errorw("Can't access techProfileKVStore - no backend connection to service",
@@ -133,12 +135,12 @@
 
 // resetTpProcessingErrorIndication resets the internal error indication
 // need to be called before evaluation of any subsequent processing (given by waitForTpCompletion())
-func (onuTP *onuUniTechProf) resetTpProcessingErrorIndication() {
-	onuTP.procResult = nil
+func (onuTP *onuUniTechProf) resetTpProcessingErrorIndication(aUniID uint8, aTpID uint8) {
+	onuTP.procResult[uniTP{uniID: aUniID, tpID: aTpID}] = nil
 }
 
-func (onuTP *onuUniTechProf) getTpProcessingErrorIndication() error {
-	return onuTP.procResult
+func (onuTP *onuUniTechProf) getTpProcessingErrorIndication(aUniID uint8, aTpID uint8) error {
+	return onuTP.procResult[uniTP{uniID: aUniID, tpID: aTpID}]
 }
 
 // configureUniTp checks existing tp resources to delete and starts the corresponding OMCI configuation of the UNI port
@@ -150,10 +152,15 @@
 	defer wg.Done() //always decrement the waitGroup on return
 	logger.Debugw("configure the Uni according to TpPath", log.Fields{
 		"device-id": onuTP.deviceID, "uni-id": aUniID, "path": aPathString})
-
+	tpID, err := GetTpIDFromTpPath(aPathString)
+	uniTpKey := uniTP{uniID: aUniID, tpID: tpID}
+	if err != nil {
+		logger.Errorw("error-extracting-tp-id-from-tp-path", log.Fields{"device-id": onuTP.deviceID, "uni-id": aUniID, "path": aPathString})
+		return
+	}
 	if onuTP.techProfileKVStore == nil {
 		logger.Debug("techProfileKVStore not set - abort")
-		onuTP.procResult = errors.New("techProfile config aborted: techProfileKVStore not set")
+		onuTP.procResult[uniTpKey] = errors.New("techProfile config aborted: techProfileKVStore not set")
 		return
 	}
 
@@ -161,7 +168,7 @@
 	var pCurrentUniPort *onuUniPort
 	for _, uniPort := range onuTP.baseDeviceHandler.uniEntityMap {
 		// only if this port is validated for operState transfer
-		if uniPort.uniID == uint8(aUniID) {
+		if uniPort.uniID == aUniID {
 			pCurrentUniPort = uniPort
 			break //found - end search loop
 		}
@@ -169,7 +176,7 @@
 	if pCurrentUniPort == nil {
 		logger.Errorw("TechProfile configuration aborted: requested uniID not found in PortDB",
 			log.Fields{"device-id": onuTP.deviceID, "uni-id": aUniID})
-		onuTP.procResult = fmt.Errorf("techProfile config aborted: requested uniID not found %d on %s",
+		onuTP.procResult[uniTpKey] = fmt.Errorf("techProfile config aborted: requested uniID not found %d on %s",
 			aUniID, onuTP.deviceID)
 		return
 	}
@@ -194,50 +201,56 @@
 
 	processingStep++
 	*/
-	go onuTP.readAniSideConfigFromTechProfile(ctx, aUniID, aPathString, processingStep)
+	go onuTP.readAniSideConfigFromTechProfile(ctx, aUniID, tpID, aPathString, processingStep)
 	if !onuTP.waitForTimeoutOrCompletion(ctx, onuTP.chTpConfigProcessingStep, processingStep) {
 		//timeout or error detected
-		if onuTP.tpProfileExists {
+		if onuTP.tpProfileExists[uniTpKey] {
 			//ignore the internal error in case the new profile is already configured
 			// and abort the processing here
 			return
 		}
 		logger.Debugw("tech-profile related configuration aborted on read",
 			log.Fields{"device-id": onuTP.deviceID, "uni-id": aUniID})
-		onuTP.procResult = fmt.Errorf("techProfile config aborted: tech-profile read issue for %d on %s",
+		onuTP.procResult[uniTpKey] = fmt.Errorf("techProfile config aborted: tech-profile read issue for %d on %s",
 			aUniID, onuTP.deviceID)
 		return
 	}
 
 	processingStep++
-	if valuePA, existPA := onuTP.mapPonAniConfig[aUniID]; existPA {
-		if _, existTG := (*valuePA)[0]; existTG {
+
+	valuePA, existPA := onuTP.mapPonAniConfig[uniTpKey]
+
+	if existPA {
+		if valuePA != nil {
 			//Config data for this uni and and at least TCont Index 0 exist
-			go onuTP.setAniSideConfigFromTechProfile(ctx, aUniID, pCurrentUniPort, processingStep)
+			go onuTP.setAniSideConfigFromTechProfile(ctx, aUniID, tpID, pCurrentUniPort, processingStep)
 			if !onuTP.waitForTimeoutOrCompletion(ctx, onuTP.chTpConfigProcessingStep, processingStep) {
 				//timeout or error detected
 				logger.Debugw("tech-profile related configuration aborted on set",
 					log.Fields{"device-id": onuTP.deviceID, "uni-id": aUniID})
-				onuTP.procResult = fmt.Errorf("techProfile config aborted: Omci AniSideConfig failed %d on %s",
+
+				onuTP.procResult[uniTpKey] = fmt.Errorf("techProfile config aborted: Omci AniSideConfig failed %d on %s",
 					aUniID, onuTP.deviceID)
 				//this issue here means that the AniConfigFsm has not finished successfully
 				//which requires to reset it to allow for new usage, e.g. also on a different UNI
 				//(without that it would be reset on device down indication latest)
-				_ = onuTP.pAniConfigFsm.pAdaptFsm.pFsm.Event(aniEvReset)
+				_ = onuTP.pAniConfigFsm[uniTpKey].pAdaptFsm.pFsm.Event(aniEvReset)
 				return
 			}
 		} else {
 			// strange: UNI entry exists, but no ANI data, maybe such situation should be cleared up (if observed)
 			logger.Debugw("no Tcont/Gem data for this UNI found - abort", log.Fields{
 				"device-id": onuTP.deviceID, "uni-id": aUniID})
-			onuTP.procResult = fmt.Errorf("techProfile config aborted: no Tcont/Gem data found for this UNI %d on %s",
+
+			onuTP.procResult[uniTpKey] = fmt.Errorf("techProfile config aborted: no Tcont/Gem data found for this UNI %d on %s",
 				aUniID, onuTP.deviceID)
 			return
 		}
 	} else {
 		logger.Debugw("no PonAni data for this UNI found - abort", log.Fields{
 			"device-id": onuTP.deviceID, "uni-id": aUniID})
-		onuTP.procResult = fmt.Errorf("techProfile config aborted: no AniSide data found for this UNI %d on %s",
+
+		onuTP.procResult[uniTpKey] = fmt.Errorf("techProfile config aborted: no AniSide data found for this UNI %d on %s",
 			aUniID, onuTP.deviceID)
 		return
 	}
@@ -246,10 +259,13 @@
 /* internal methods *********************/
 
 func (onuTP *onuUniTechProf) readAniSideConfigFromTechProfile(
-	ctx context.Context, aUniID uint8, aPathString string, aProcessingStep uint8) {
+	ctx context.Context, aUniID uint8, aTpID uint8, aPathString string, aProcessingStep uint8) {
 	var tpInst tp.TechProfile
 
-	onuTP.tpProfileExists = false
+	uniTPKey := uniTP{uniID: aUniID, tpID: aTpID}
+
+	onuTP.tpProfileExists[uniTP{uniID: aUniID, tpID: aTpID}] = false
+
 	//store profile type and identifier for later usage within the OMCI identifier and possibly ME setup
 	//pathstring is defined to be in the form of <ProfType>/<profID>/<Interface/../Identifier>
 	subStringSlice := strings.Split(aPathString, "/")
@@ -259,13 +275,6 @@
 		onuTP.chTpConfigProcessingStep <- 0 //error indication
 		return
 	}
-	profID, err := strconv.ParseUint(subStringSlice[1], 10, 32)
-	if err != nil {
-		logger.Errorw("invalid ProfileId from path",
-			log.Fields{"ParseErr": err})
-		onuTP.chTpConfigProcessingStep <- 0 //error indication
-		return
-	}
 
 	//at this point it is assumed that a new TechProfile is assigned to the UNI
 	//expectation is that no TPIndication entry exists here, if exists and with the same TPId
@@ -273,36 +282,36 @@
 	//  which is later re-defined to success response to OLT adapter
 	//  if TPId has changed, current data is removed (note that the ONU config state may be
 	// 	  ambivalent in such a case)
-	if _, existTP := onuTP.mapUniTpIndication[aUniID]; existTP {
+	if _, existTP := onuTP.mapUniTpIndication[uniTPKey]; existTP {
 		logger.Warnw("Some active profile entry at reading new TechProfile",
 			log.Fields{"path": aPathString, "device-id": onuTP.deviceID,
-				"uni-id": aUniID, "wrongProfile": onuTP.mapUniTpIndication[aUniID].techProfileID})
-		if uint16(profID) == onuTP.mapUniTpIndication[aUniID].techProfileID {
+				"uni-id": aUniID, "wrongProfile": onuTP.mapUniTpIndication[uniTPKey].techProfileID})
+		if aTpID == onuTP.mapUniTpIndication[uniTPKey].techProfileID {
 			// ProfId not changed - assume profile to be still the same
 			// anyway this should not appear after full support of profile (Gem/TCont) removal
 			logger.Warnw("New TechProfile already exists - aborting configuration",
 				log.Fields{"device-id": onuTP.deviceID})
-			onuTP.tpProfileExists = true
+			onuTP.tpProfileExists[uniTPKey] = true
 			onuTP.chTpConfigProcessingStep <- 0 //error indication
 			return
 		}
 		//delete on the mapUniTpIndication map not needed, just overwritten later
 		//delete on the PonAniConfig map should be safe, even if not existing
-		delete(onuTP.mapPonAniConfig, aUniID)
+		delete(onuTP.mapPonAniConfig, uniTPKey)
 	} else {
 		// this is normal processing
-		onuTP.mapUniTpIndication[aUniID] = &tTechProfileIndication{} //need to assign some (empty) struct memory first!
+		onuTP.mapUniTpIndication[uniTPKey] = &tTechProfileIndication{} //need to assign some (empty) struct memory first!
 	}
 
-	onuTP.mapUniTpIndication[aUniID].techProfileType = subStringSlice[0]
+	onuTP.mapUniTpIndication[uniTPKey].techProfileType = subStringSlice[0]
 	//note the limitation on ID range (probably even more limited) - based on usage within OMCI EntityID
-	onuTP.mapUniTpIndication[aUniID].techProfileID = uint16(profID)
-	onuTP.mapUniTpIndication[aUniID].techProfileConfigDone = false
-	onuTP.mapUniTpIndication[aUniID].techProfileToDelete = false
+	onuTP.mapUniTpIndication[uniTPKey].techProfileID = aTpID
+	onuTP.mapUniTpIndication[uniTPKey].techProfileConfigDone = false
+	onuTP.mapUniTpIndication[uniTPKey].techProfileToDelete = false
 	logger.Debugw("tech-profile path indications",
 		log.Fields{"device-id": onuTP.deviceID, "uni-id": aUniID,
-			"profType": onuTP.mapUniTpIndication[aUniID].techProfileType,
-			"profID":   onuTP.mapUniTpIndication[aUniID].techProfileID})
+			"profType": onuTP.mapUniTpIndication[uniTPKey].techProfileType,
+			"profID":   onuTP.mapUniTpIndication[uniTPKey].techProfileID})
 
 	Value, err := onuTP.techProfileKVStore.Get(ctx, aPathString)
 	if err == nil {
@@ -338,20 +347,18 @@
 	//default start with 1Tcont profile, later perhaps extend to MultiTcontMultiGem
 	localMapGemPortParams := make(map[uint16]*gemPortParamStruct)
 	localMapGemPortParams[0] = &gemPortParamStruct{}
-	localMapPonAniConfig := make(map[uint16]*tcontGemList)
-	localMapPonAniConfig[0] = &tcontGemList{tcontParamStruct{}, localMapGemPortParams}
-	onuTP.mapPonAniConfig[aUniID] = (*tMapPonAniConfig)(&localMapPonAniConfig)
+	onuTP.mapPonAniConfig[uniTPKey] = &tcontGemList{tcontParamStruct{}, localMapGemPortParams}
 
 	//note: the code is currently restricted to one TCcont per Onu (index [0])
 	//get the relevant values from the profile and store to mapPonAniConfig
-	(*(onuTP.mapPonAniConfig[aUniID]))[0].tcontParams.allocID = uint16(tpInst.UsScheduler.AllocID)
+	onuTP.mapPonAniConfig[uniTPKey].tcontParams.allocID = uint16(tpInst.UsScheduler.AllocID)
 	//maybe tCont scheduling not (yet) needed - just to basically have it for future
 	//  (would only be relevant in case of ONU-2G QOS configuration flexibility)
 	if tpInst.UsScheduler.QSchedPolicy == "StrictPrio" {
-		(*(onuTP.mapPonAniConfig[aUniID]))[0].tcontParams.schedPolicy = 1 //for the moment fixed value acc. G.988 //TODO: defines!
+		onuTP.mapPonAniConfig[uniTPKey].tcontParams.schedPolicy = 1 //for the moment fixed value acc. G.988 //TODO: defines!
 	} else {
 		//default profile defines "Hybrid" - which probably comes down to WRR with some weigthts for SP
-		(*(onuTP.mapPonAniConfig[aUniID]))[0].tcontParams.schedPolicy = 2 //for G.988 WRR
+		onuTP.mapPonAniConfig[uniTPKey].tcontParams.schedPolicy = 2 //for G.988 WRR
 	}
 	loNumGemPorts := tpInst.NumGemPorts
 	loGemPortRead := false
@@ -366,44 +373,44 @@
 			loGemPortRead = true
 		} else {
 			//for all further GemPorts we need to extend the mapGemPortParams
-			(*(onuTP.mapPonAniConfig[aUniID]))[0].mapGemPortParams[uint16(pos)] = &gemPortParamStruct{}
+			onuTP.mapPonAniConfig[uniTPKey].mapGemPortParams[uint16(pos)] = &gemPortParamStruct{}
 		}
-		(*(onuTP.mapPonAniConfig[aUniID]))[0].mapGemPortParams[uint16(pos)].gemPortID =
+		onuTP.mapPonAniConfig[uniTPKey].mapGemPortParams[uint16(pos)].gemPortID =
 			uint16(content.GemportID)
 		//direction can be correlated later with Downstream list,
 		//  for now just assume bidirectional (upstream never exists alone)
-		(*(onuTP.mapPonAniConfig[aUniID]))[0].mapGemPortParams[uint16(pos)].direction = 3 //as defined in G.988
+		onuTP.mapPonAniConfig[uniTPKey].mapGemPortParams[uint16(pos)].direction = 3 //as defined in G.988
 		// expected Prio-Queue values 0..7 with 7 for highest PrioQueue, QueueIndex=Prio = 0..7
 		if 7 < content.PriorityQueue {
 			logger.Errorw("PonAniConfig reject on GemPortList - PrioQueue value invalid",
 				log.Fields{"device-id": onuTP.deviceID, "index": pos, "PrioQueue": content.PriorityQueue})
 			//remove PonAniConfig  as done so far, delete map should be safe, even if not existing
-			delete(onuTP.mapPonAniConfig, aUniID)
+			delete(onuTP.mapPonAniConfig, uniTPKey)
 			onuTP.chTpConfigProcessingStep <- 0 //error indication
 			return
 		}
-		(*(onuTP.mapPonAniConfig[aUniID]))[0].mapGemPortParams[uint16(pos)].prioQueueIndex =
+		onuTP.mapPonAniConfig[uniTPKey].mapGemPortParams[uint16(pos)].prioQueueIndex =
 			uint8(content.PriorityQueue)
-		(*(onuTP.mapPonAniConfig[aUniID]))[0].mapGemPortParams[uint16(pos)].pbitString =
+		onuTP.mapPonAniConfig[uniTPKey].mapGemPortParams[uint16(pos)].pbitString =
 			strings.TrimPrefix(content.PbitMap, binaryStringPrefix)
 		if content.AesEncryption == "True" {
-			(*(onuTP.mapPonAniConfig[aUniID]))[0].mapGemPortParams[uint16(pos)].gemPortEncState = 1
+			onuTP.mapPonAniConfig[uniTPKey].mapGemPortParams[uint16(pos)].gemPortEncState = 1
 		} else {
-			(*(onuTP.mapPonAniConfig[aUniID]))[0].mapGemPortParams[uint16(pos)].gemPortEncState = 0
+			onuTP.mapPonAniConfig[uniTPKey].mapGemPortParams[uint16(pos)].gemPortEncState = 0
 		}
-		(*(onuTP.mapPonAniConfig[aUniID]))[0].mapGemPortParams[uint16(pos)].discardPolicy =
+		onuTP.mapPonAniConfig[uniTPKey].mapGemPortParams[uint16(pos)].discardPolicy =
 			content.DiscardPolicy
-		(*(onuTP.mapPonAniConfig[aUniID]))[0].mapGemPortParams[uint16(pos)].queueSchedPolicy =
+		onuTP.mapPonAniConfig[uniTPKey].mapGemPortParams[uint16(pos)].queueSchedPolicy =
 			content.SchedulingPolicy
 		//'GemWeight' looks strange in default profile, for now we just copy the weight to first queue
-		(*(onuTP.mapPonAniConfig[aUniID]))[0].mapGemPortParams[uint16(pos)].queueWeight =
+		onuTP.mapPonAniConfig[uniTPKey].mapGemPortParams[uint16(pos)].queueWeight =
 			uint8(content.Weight)
 	}
 	if !loGemPortRead {
 		logger.Errorw("PonAniConfig reject - no GemPort could be read from TechProfile",
 			log.Fields{"path": aPathString, "device-id": onuTP.deviceID})
 		//remove PonAniConfig  as done so far, delete map should be safe, even if not existing
-		delete(onuTP.mapPonAniConfig, aUniID)
+		delete(onuTP.mapPonAniConfig, uniTPKey)
 		onuTP.chTpConfigProcessingStep <- 0 //error indication
 		return
 	}
@@ -412,8 +419,8 @@
 	//logger does not simply output the given structures, just give some example debug values
 	logger.Debugw("PonAniConfig read from TechProfile", log.Fields{
 		"device-id": onuTP.deviceID,
-		"AllocId":   (*(onuTP.mapPonAniConfig[aUniID]))[0].tcontParams.allocID})
-	for gemIndex, gemEntry := range (*(onuTP.mapPonAniConfig[0]))[0].mapGemPortParams {
+		"AllocId":   onuTP.mapPonAniConfig[uniTPKey].tcontParams.allocID})
+	for gemIndex, gemEntry := range onuTP.mapPonAniConfig[uniTPKey].mapGemPortParams {
 		logger.Debugw("PonAniConfig read from TechProfile", log.Fields{
 			"GemIndex":        gemIndex,
 			"GemPort":         gemEntry.gemPortID,
@@ -424,15 +431,18 @@
 }
 
 func (onuTP *onuUniTechProf) setAniSideConfigFromTechProfile(
-	ctx context.Context, aUniID uint8, apCurrentUniPort *onuUniPort, aProcessingStep uint8) {
+	ctx context.Context, aUniID uint8, aTpID uint8, apCurrentUniPort *onuUniPort, aProcessingStep uint8) {
 
 	//OMCI transfer of ANI data acc. to mapPonAniConfig
 	// also the FSM's are running in background,
 	//   hence we have to make sure they indicate 'success' success on chTpConfigProcessingStep with aProcessingStep
+	uniTPKey := uniTP{uniID: aUniID, tpID: aTpID}
 	if onuTP.pAniConfigFsm == nil {
-		onuTP.createAniConfigFsm(aUniID, apCurrentUniPort, OmciAniConfigDone, aProcessingStep)
+		onuTP.createAniConfigFsm(aUniID, aTpID, apCurrentUniPort, OmciAniConfigDone, aProcessingStep)
+	} else if _, ok := onuTP.pAniConfigFsm[uniTPKey]; !ok {
+		onuTP.createAniConfigFsm(aUniID, aTpID, apCurrentUniPort, OmciAniConfigDone, aProcessingStep)
 	} else { //AniConfigFsm already init
-		onuTP.runAniConfigFsm(aProcessingStep)
+		onuTP.runAniConfigFsm(aProcessingStep, aUniID, aTpID)
 	}
 }
 
@@ -456,21 +466,25 @@
 }
 
 // createUniLockFsm initializes and runs the AniConfig FSM to transfer the OMCI related commands for ANI side configuration
-func (onuTP *onuUniTechProf) createAniConfigFsm(aUniID uint8,
+func (onuTP *onuUniTechProf) createAniConfigFsm(aUniID uint8, aTpID uint8,
 	apCurrentUniPort *onuUniPort, devEvent OnuDeviceEvent, aProcessingStep uint8) {
 	logger.Debugw("createAniConfigFsm", log.Fields{"device-id": onuTP.deviceID})
 	chAniConfigFsm := make(chan Message, 2048)
+	uniTPKey := uniTP{uniID: aUniID, tpID: aTpID}
 	pDevEntry := onuTP.baseDeviceHandler.getOnuDeviceEntry(true)
 	if pDevEntry == nil {
 		logger.Errorw("No valid OnuDevice - aborting", log.Fields{"device-id": onuTP.deviceID})
 		return
 	}
 	pAniCfgFsm := newUniPonAniConfigFsm(pDevEntry.PDevOmciCC, apCurrentUniPort, onuTP,
-		pDevEntry.pOnuDB, onuTP.mapUniTpIndication[aUniID].techProfileID, devEvent,
+		pDevEntry.pOnuDB, aTpID, devEvent,
 		"AniConfigFsm", onuTP.baseDeviceHandler, chAniConfigFsm)
 	if pAniCfgFsm != nil {
-		onuTP.pAniConfigFsm = pAniCfgFsm
-		onuTP.runAniConfigFsm(aProcessingStep)
+		if onuTP.pAniConfigFsm == nil {
+			onuTP.pAniConfigFsm = make(map[uniTP]*uniPonAniConfigFsm)
+		}
+		onuTP.pAniConfigFsm[uniTPKey] = pAniCfgFsm
+		onuTP.runAniConfigFsm(aProcessingStep, aUniID, aTpID)
 	} else {
 		logger.Errorw("AniConfigFSM could not be created - abort!!", log.Fields{"device-id": onuTP.deviceID})
 	}
@@ -483,6 +497,12 @@
 	defer wg.Done()
 	logger.Debugw("this would remove TP resources from ONU's UNI", log.Fields{
 		"device-id": onuTP.deviceID, "uniID": aUniID, "path": aPathString, "Resource": aResource})
+	tpID, err := GetTpIDFromTpPath(aPathString)
+	if err != nil {
+		logger.Errorw("error-extracting-tp-id-from-tp-path", log.Fields{"device-id": onuTP.deviceID, "uni-id": aUniID, "path": aPathString})
+		return
+	}
+	uniTpKey := uniTP{uniID: aUniID, tpID: tpID}
 	if cResourceGemPort == aResource {
 		logger.Debugw("remove GemPort from the list of existing ones of the TP", log.Fields{
 			"device-id": onuTP.deviceID, "uniID": aUniID, "path": aPathString, "entry": aEntryID})
@@ -500,11 +520,12 @@
 		//  TODO: To be updated with multi-T-Cont implementation
 		logger.Debugw("DeleteTcont clears the existing internal profile", log.Fields{
 			"device-id": onuTP.deviceID, "uniID": aUniID, "path": aPathString, "Resource": aResource})
-		onuTP.clearAniSideConfig(aUniID)
+
+		onuTP.clearAniSideConfig(aUniID, tpID)
 		// reset also the FSM in order to admit a new OMCI configuration in case a new profile is created
 		// FSM stop maybe encapsulated as OnuTP method - perhaps later in context of module splitting
 		if onuTP.pAniConfigFsm != nil {
-			_ = onuTP.pAniConfigFsm.pAdaptFsm.pFsm.Event(aniEvReset)
+			_ = onuTP.pAniConfigFsm[uniTpKey].pAdaptFsm.pFsm.Event(aniEvReset)
 		}
 
 		//TODO!!! - the real processing could look like that (for starting the removal, where the clearAniSideConfig is done implicitly):
@@ -524,15 +545,17 @@
 }
 
 // runAniConfigFsm starts the AniConfig FSM to transfer the OMCI related commands for  ANI side configuration
-func (onuTP *onuUniTechProf) runAniConfigFsm(aProcessingStep uint8) {
+func (onuTP *onuUniTechProf) runAniConfigFsm(aProcessingStep uint8, aUniID uint8, aTpID uint8) {
 	/*  Uni related ANI config procedure -
 	 ***** should run via 'aniConfigDone' state and generate the argument requested event *****
 	 */
-	pACStatemachine := onuTP.pAniConfigFsm.pAdaptFsm.pFsm
+	uniTpKey := uniTP{uniID: aUniID, tpID: aTpID}
+
+	pACStatemachine := onuTP.pAniConfigFsm[uniTpKey].pAdaptFsm.pFsm
 	if pACStatemachine != nil {
 		if pACStatemachine.Is(aniStDisabled) {
 			//FSM init requirement to get informed abou FSM completion! (otherwise timeout of the TechProf config)
-			onuTP.pAniConfigFsm.setFsmCompleteChannel(onuTP.chTpConfigProcessingStep, aProcessingStep)
+			onuTP.pAniConfigFsm[uniTpKey].setFsmCompleteChannel(onuTP.chTpConfigProcessingStep, aProcessingStep)
 			if err := pACStatemachine.Event(aniEvStart); err != nil {
 				logger.Warnw("AniConfigFSM: can't start", log.Fields{"err": err})
 				// maybe try a FSM reset and then again ... - TODO!!!
@@ -552,39 +575,42 @@
 	}
 }
 
-// clearAniSideConfig deletes all internal TechProfile related data connected to the requested UniPort
-func (onuTP *onuUniTechProf) clearAniSideConfig(aUniID uint8) {
+// clearAniSideConfig deletes internal TechProfile related data connected to the requested UniPort and TpID
+func (onuTP *onuUniTechProf) clearAniSideConfig(aUniID uint8, aTpID uint8) {
 	logger.Debugw("removing TpIndication and PonAniConfig data", log.Fields{
 		"device-id": onuTP.deviceID, "uni-id": aUniID})
-	//a mutex protection on the concerned data should not be needed here, as the config/write action should not
-	//  interfere with any read action or the initial write/config activity at start
-	//remove the TechProfile indications of this UNI, should be safe even if not existing
-	delete(onuTP.mapUniTpIndication, aUniID)
+	uniTpKey := uniTP{uniID: aUniID, tpID: aTpID}
+
+	onuTP.mutexTPState.Lock()
+	defer onuTP.mutexTPState.Unlock()
+	delete(onuTP.mapUniTpIndication, uniTpKey)
 	//delete on the PonAniConfig map of this UNI should be safe, even if not existing
-	delete(onuTP.mapPonAniConfig, aUniID)
+	delete(onuTP.mapPonAniConfig, uniTpKey)
 }
 
 // setConfigDone sets the requested techProfile config state (if possible)
-func (onuTP *onuUniTechProf) setConfigDone(aUniID uint8, aState bool) {
-	if _, existTP := onuTP.mapUniTpIndication[aUniID]; existTP {
-		onuTP.mutexTPState.Lock()
-		onuTP.mapUniTpIndication[aUniID].techProfileConfigDone = aState
-		onuTP.mutexTPState.Unlock()
+func (onuTP *onuUniTechProf) setConfigDone(aUniID uint8, aTpID uint8, aState bool) {
+	uniTpKey := uniTP{uniID: aUniID, tpID: aTpID}
+	onuTP.mutexTPState.Lock()
+	defer onuTP.mutexTPState.Unlock()
+	if _, existTP := onuTP.mapUniTpIndication[uniTpKey]; existTP {
+		onuTP.mapUniTpIndication[uniTpKey].techProfileConfigDone = aState
 	} //else: the state is just ignored (does not exist)
 }
 
 // getTechProfileDone checks if the Techprofile processing with the requested TechProfile ID was done
-func (onuTP *onuUniTechProf) getTechProfileDone(aUniID uint8, aTpID uint16) bool {
-	if _, existTP := onuTP.mapUniTpIndication[aUniID]; existTP {
-		if onuTP.mapUniTpIndication[aUniID].techProfileID == aTpID {
-			onuTP.mutexTPState.Lock()
-			defer onuTP.mutexTPState.Unlock()
-			if onuTP.mapUniTpIndication[aUniID].techProfileToDelete {
+func (onuTP *onuUniTechProf) getTechProfileDone(aUniID uint8, aTpID uint8) bool {
+	uniTpKey := uniTP{uniID: aUniID, tpID: aTpID}
+	onuTP.mutexTPState.Lock()
+	defer onuTP.mutexTPState.Unlock()
+	if _, existTP := onuTP.mapUniTpIndication[uniTpKey]; existTP {
+		if onuTP.mapUniTpIndication[uniTpKey].techProfileID == aTpID {
+			if onuTP.mapUniTpIndication[uniTpKey].techProfileToDelete {
 				logger.Debugw("TechProfile not relevant for requested flow config - waiting on delete",
 					log.Fields{"device-id": onuTP.deviceID, "uni-id": aUniID})
 				return false //still waiting for removal of this techProfile first
 			}
-			return onuTP.mapUniTpIndication[aUniID].techProfileConfigDone
+			return onuTP.mapUniTpIndication[uniTpKey].techProfileConfigDone
 		}
 	}
 	//for all other constellations indicate false = Config not done
@@ -592,10 +618,11 @@
 }
 
 // setProfileToDelete sets the requested techProfile toDelete state (if possible)
-func (onuTP *onuUniTechProf) setProfileToDelete(aUniID uint8, aState bool) {
-	if _, existTP := onuTP.mapUniTpIndication[aUniID]; existTP {
-		onuTP.mutexTPState.Lock()
-		onuTP.mapUniTpIndication[aUniID].techProfileToDelete = aState
-		onuTP.mutexTPState.Unlock()
+func (onuTP *onuUniTechProf) setProfileToDelete(aUniID uint8, aTpID uint8, aState bool) {
+	uniTpKey := uniTP{uniID: aUniID, tpID: aTpID}
+	onuTP.mutexTPState.Lock()
+	defer onuTP.mutexTPState.Unlock()
+	if _, existTP := onuTP.mapUniTpIndication[uniTpKey]; existTP {
+		onuTP.mapUniTpIndication[uniTpKey].techProfileToDelete = aState
 	} //else: the state is just ignored (does not exist)
 }
diff --git a/internal/pkg/onuadaptercore/openonu_utils.go b/internal/pkg/onuadaptercore/openonu_utils.go
new file mode 100644
index 0000000..aa7de95
--- /dev/null
+++ b/internal/pkg/onuadaptercore/openonu_utils.go
@@ -0,0 +1,43 @@
+/*
+ * 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 adaptercoreonu provides the utility for onu devices, flows and statistics
+package adaptercoreonu
+
+import (
+	"errors"
+	"regexp"
+	"strconv"
+	"strings"
+)
+
+// GetTpIDFromTpPath extracts TpID from the TpPath.
+// On success it returns a valid TpID and nil error.
+// On failure it returns TpID as 0 and the error.
+func GetTpIDFromTpPath(tpPath string) (uint8, error) {
+	// tpPath is of the format  <technology>/<table_id>/olt-{}/pon-{}/onu-{}/uni-{}
+	// A sample tpPath is ==> XGS-PON/64/olt-{12345abcd}/pon-{0}/onu-{1}/uni-{1}
+	var tpPathFormat = regexp.MustCompile(`^[a-zA-Z\-_]+/[0-9]+/olt-{[a-z0-9\-]+}/pon-{[0-9]+}/onu-{[0-9]+}/uni-{[0-9]+}$`)
+
+	// Ensure tpPath is of the format  <technology>/<table_id>/<uni_port_name>
+	if !tpPathFormat.Match([]byte(tpPath)) {
+		return 0, errors.New("tp-path-not-confirming-to-format")
+	}
+	// Extract the TP table-id field.
+	tpID, err := strconv.Atoi(strings.Split(tpPath, "/")[1])
+	// Atoi returns uint64 and need to be type-casted to uint8 as tpID is uint8 size.
+	return uint8(tpID), err
+}