VOL-3614 Created multicast GEM If is_multicast flag is enable into tech profile

Change-Id: I82f680df0c2a51841965c484ae3398a1606b2739
diff --git a/internal/pkg/onuadaptercore/omci_ani_config.go b/internal/pkg/onuadaptercore/omci_ani_config.go
index 0f44d84..70b5f67 100644
--- a/internal/pkg/onuadaptercore/omci_ani_config.go
+++ b/internal/pkg/onuadaptercore/omci_ani_config.go
@@ -19,7 +19,9 @@
 
 import (
 	"context"
+	"encoding/binary"
 	"fmt"
+	"net"
 	"strconv"
 	"time"
 
@@ -82,13 +84,17 @@
 )
 
 type ponAniGemPortAttribs struct {
-	gemPortID   uint16
-	upQueueID   uint16
-	downQueueID uint16
-	direction   uint8
-	qosPolicy   string
-	weight      uint8
-	pbitString  string
+	gemPortID      uint16
+	upQueueID      uint16
+	downQueueID    uint16
+	direction      uint8
+	qosPolicy      string
+	weight         uint8
+	pbitString     string
+	isMulticast    bool
+	multicastGemID uint16
+	staticACL      string
+	dynamicACL     string
 }
 
 //uniPonAniConfigFsm defines the structure for the state machine to config the PON ANI ports of ONU UNI ports via OMCI
@@ -361,6 +367,12 @@
 			loGemPortAttribs.qosPolicy = gemEntry.queueSchedPolicy
 			loGemPortAttribs.weight = gemEntry.queueWeight
 			loGemPortAttribs.pbitString = gemEntry.pbitString
+			if gemEntry.isMulticast {
+				loGemPortAttribs.isMulticast = true
+				loGemPortAttribs.multicastGemID = gemEntry.multicastGemPortID
+				loGemPortAttribs.staticACL = gemEntry.staticACL
+				loGemPortAttribs.dynamicACL = gemEntry.dynamicACL
+			}
 
 			logger.Debugw("prio-related GemPort attributes:", log.Fields{
 				"gemPortID":      loGemPortAttribs.gemPortID,
@@ -368,6 +380,10 @@
 				"downQueueID":    loGemPortAttribs.downQueueID,
 				"pbitString":     loGemPortAttribs.pbitString,
 				"prioQueueIndex": gemEntry.prioQueueIndex,
+				"isMulticast":    loGemPortAttribs.isMulticast,
+				"multicastGemID": loGemPortAttribs.multicastGemID,
+				"staticACL":      loGemPortAttribs.staticACL,
+				"dynamicACL":     loGemPortAttribs.dynamicACL,
 			})
 
 			oFsm.gemPortAttribsSlice = append(oFsm.gemPortAttribsSlice, loGemPortAttribs)
@@ -513,7 +529,7 @@
 
 		}
 	}
-	var foundIwPtr bool = false
+	var foundIwPtr = false
 	for index, value := range loPrioGemPortArray {
 		if value != 0 {
 			foundIwPtr = true
@@ -942,26 +958,52 @@
 			"EntitytId": strconv.FormatInt(int64(gemPortAttribs.gemPortID), 16),
 			"SPPtr":     strconv.FormatInt(int64(oFsm.mapperSP0ID), 16),
 			"device-id": oFsm.deviceID})
-		meParams := me.ParamData{
-			EntityID: gemPortAttribs.gemPortID,
-			Attributes: me.AttributeValueMap{
-				"GemPortNetworkCtpConnectivityPointer": gemPortAttribs.gemPortID, //same as EntityID, see above
-				"InterworkingOption":                   5,                        //fixed model:: G.998 .1pMapper
-				"ServiceProfilePointer":                oFsm.mapperSP0ID,
-				"InterworkingTerminationPointPointer":  0, //not used with .1PMapper Mac bridge
-				"GalProfilePointer":                    galEthernetEID,
-			},
-		}
-		meInstance := oFsm.pOmciCC.sendCreateGemIWTPVar(context.TODO(), ConstDefaultOmciTimeout, true,
-			oFsm.pAdaptFsm.commChan, meParams)
-		//accept also nil as (error) return value for writing to LastTx
-		//  - this avoids misinterpretation of new received OMCI messages
-		oFsm.pLastTxMeInstance = meInstance
 
+		//TODO if the port has only downstream direction the isMulticast flag can be removed.
+		if gemPortAttribs.isMulticast {
+			ipv4MulticastTable := make([]uint8, 12)
+			binary.BigEndian.PutUint16(ipv4MulticastTable[0:], gemPortAttribs.gemPortID)
+			binary.BigEndian.PutUint16(ipv4MulticastTable[2:], 0)
+			// This is the 224.0.0.1 address
+			binary.BigEndian.PutUint32(ipv4MulticastTable[4:], IPToInt32(net.IPv4allsys))
+			// this is the 255.255.255.255 address
+			binary.BigEndian.PutUint32(ipv4MulticastTable[8:], IPToInt32(net.IPv4bcast))
+
+			meParams := me.ParamData{
+				EntityID: gemPortAttribs.gemPortID,
+				Attributes: me.AttributeValueMap{
+					"GemPortNetworkCtpConnectivityPointer": gemPortAttribs.gemPortID,
+					"InterworkingOption":                   0, // Don't Care
+					"ServiceProfilePointer":                0, // Don't Care
+					"GalProfilePointer":                    galEthernetEID,
+					"Ipv4MulticastAddressTable":            ipv4MulticastTable,
+				},
+			}
+			meInstance := oFsm.pOmciCC.sendCreateMulticastGemIWTPVar(context.TODO(), ConstDefaultOmciTimeout,
+				true, oFsm.pAdaptFsm.commChan, meParams)
+			oFsm.pLastTxMeInstance = meInstance
+
+		} else {
+			meParams := me.ParamData{
+				EntityID: gemPortAttribs.gemPortID,
+				Attributes: me.AttributeValueMap{
+					"GemPortNetworkCtpConnectivityPointer": gemPortAttribs.gemPortID, //same as EntityID, see above
+					"InterworkingOption":                   5,                        //fixed model:: G.998 .1pMapper
+					"ServiceProfilePointer":                oFsm.mapperSP0ID,
+					"InterworkingTerminationPointPointer":  0, //not used with .1PMapper Mac bridge
+					"GalProfilePointer":                    galEthernetEID,
+				},
+			}
+			meInstance := oFsm.pOmciCC.sendCreateGemIWTPVar(context.TODO(), ConstDefaultOmciTimeout, true,
+				oFsm.pAdaptFsm.commChan, meParams)
+			//accept also nil as (error) return value for writing to LastTx
+			//  - this avoids misinterpretation of new received OMCI messages
+			oFsm.pLastTxMeInstance = meInstance
+		}
 		//verify response
 		err := oFsm.waitforOmciResponse()
 		if err != nil {
-			logger.Errorw("GemIwTp create failed, aborting AniConfig FSM!",
+			logger.Errorw("GemTP create failed, aborting AniConfig FSM!",
 				log.Fields{"device-id": oFsm.deviceID, "GemIndex": gemIndex})
 			_ = oFsm.pAdaptFsm.pFsm.Event(aniEvReset)
 			return
diff --git a/internal/pkg/onuadaptercore/onu_uni_tp.go b/internal/pkg/onuadaptercore/onu_uni_tp.go
index 102f32d..1ab6c0b 100644
--- a/internal/pkg/onuadaptercore/onu_uni_tp.go
+++ b/internal/pkg/onuadaptercore/onu_uni_tp.go
@@ -22,6 +22,7 @@
 	"encoding/json"
 	"errors"
 	"fmt"
+	"strconv"
 	"strings"
 	"sync"
 
@@ -73,6 +74,11 @@
 	queueSchedPolicy string
 	queueWeight      uint8
 	removeIndex      uint16
+	isMulticast      bool
+	//TODO check if this has any value/difference from gemPortId
+	multicastGemPortID uint16
+	staticACL          string
+	dynamicACL         string
 }
 
 //refers to one tcont and its properties and all assigned GemPorts and their properties
@@ -267,7 +273,7 @@
 }
 
 /* internal methods *********************/
-
+// nolint: gocyclo
 func (onuTP *onuUniTechProf) readAniSideConfigFromTechProfile(
 	ctx context.Context, aUniID uint8, aTpID uint8, aPathString string, aProcessingStep uint8) {
 	var tpInst tp.TechProfile
@@ -383,6 +389,8 @@
 			loGemPortRead = true
 		} else {
 			//for all further GemPorts we need to extend the mapGemPortParams
+			//FIXME one can use uint16(content.GemportID) as key to the map
+			// see jira https://jira.opencord.org/browse/VOL-3667
 			onuTP.mapPonAniConfig[uniTPKey].mapGemPortParams[uint16(pos)] = &gemPortParamStruct{}
 		}
 		onuTP.mapPonAniConfig[uniTPKey].mapGemPortParams[uint16(pos)].gemPortID =
@@ -391,7 +399,7 @@
 		//  for now just assume bidirectional (upstream never exists alone)
 		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 {
+		if content.PriorityQueue > 7 {
 			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
@@ -416,6 +424,58 @@
 		onuTP.mapPonAniConfig[uniTPKey].mapGemPortParams[uint16(pos)].queueWeight =
 			uint8(content.Weight)
 	}
+
+	for pos, downstreamContent := range tpInst.DownstreamGemPortAttributeList {
+		if uint32(pos) == loNumGemPorts {
+			logger.Debugw("PonAniConfig abort GemPortList - GemList exceeds set NumberOfGemPorts",
+				log.Fields{"device-id": onuTP.deviceID, "index": pos, "NumGem": loNumGemPorts})
+			break
+		}
+
+		//Flag is defined as string in the TP in voltha-lib-go, parsing it from string
+		isMulticast, err := strconv.ParseBool(downstreamContent.IsMulticast)
+
+		if err != nil {
+			logger.Errorw("multicast-error-config-unknown-flag-in-technology-profile", log.Fields{"UniTpKey": uniTPKey,
+				"downstream-gem": downstreamContent, "error": err})
+			continue
+		}
+
+		if isMulticast {
+			mcastGemID := uint16(downstreamContent.McastGemID)
+			_, existing := onuTP.mapPonAniConfig[uniTPKey].mapGemPortParams[mcastGemID]
+			if existing {
+				//GEM port was previously configured, avoid setting multicast attributes
+				logger.Errorw("multicast-error-config-existing-gem-port-config", log.Fields{"UniTpKey": uniTPKey,
+					"downstream-gem": downstreamContent, "key": mcastGemID})
+				continue
+			} else {
+				//GEM port is not configured, setting multicast attributes
+				logger.Infow("creating-multicast-gem-port", log.Fields{"uniPtKEy": uniTPKey,
+					"gemPortId": mcastGemID, "key": mcastGemID})
+
+				//for all further GemPorts we need to extend the mapGemPortParams
+				onuTP.mapPonAniConfig[uniTPKey].mapGemPortParams[mcastGemID] = &gemPortParamStruct{}
+
+				//Separate McastGemId is derived from OMCI-lib-go, if not needed first needs to be removed there.
+				onuTP.mapPonAniConfig[uniTPKey].mapGemPortParams[mcastGemID].gemPortID = mcastGemID
+				onuTP.mapPonAniConfig[uniTPKey].mapGemPortParams[mcastGemID].direction = 2 // for ANI to UNI as defined in G.988
+				//Downstream Priority Queue is set in the data of any message exchanged, using in mcast too.
+				if downstreamContent.PriorityQueue > 7 {
+					logger.Errorw("PonAniConfig reject on GemPortList - PrioQueue value invalid",
+						log.Fields{"device-id": onuTP.deviceID, "index": pos, "PrioQueue": downstreamContent.PriorityQueue})
+				}
+				onuTP.mapPonAniConfig[uniTPKey].mapGemPortParams[mcastGemID].prioQueueIndex =
+					uint8(downstreamContent.PriorityQueue)
+				onuTP.mapPonAniConfig[uniTPKey].mapGemPortParams[mcastGemID].isMulticast = isMulticast
+				onuTP.mapPonAniConfig[uniTPKey].mapGemPortParams[mcastGemID].multicastGemPortID =
+					uint16(downstreamContent.McastGemID)
+				onuTP.mapPonAniConfig[uniTPKey].mapGemPortParams[mcastGemID].staticACL = downstreamContent.SControlList
+				onuTP.mapPonAniConfig[uniTPKey].mapGemPortParams[mcastGemID].dynamicACL = downstreamContent.DControlList
+			}
+		}
+	}
+
 	if !loGemPortRead {
 		logger.Errorw("PonAniConfig reject - no GemPort could be read from TechProfile",
 			log.Fields{"path": aPathString, "device-id": onuTP.deviceID})
@@ -424,8 +484,6 @@
 		onuTP.chTpConfigProcessingStep <- 0 //error indication
 		return
 	}
-	//TODO!! MC (downstream) GemPorts can be set using DownstreamGemPortAttributeList separately
-
 	//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, "uni-id": aUniID,
diff --git a/internal/pkg/onuadaptercore/openonu_utils.go b/internal/pkg/onuadaptercore/openonu_utils.go
index aa7de95..0db62ed 100644
--- a/internal/pkg/onuadaptercore/openonu_utils.go
+++ b/internal/pkg/onuadaptercore/openonu_utils.go
@@ -18,7 +18,9 @@
 package adaptercoreonu
 
 import (
+	"encoding/binary"
 	"errors"
+	"net"
 	"regexp"
 	"strconv"
 	"strings"
@@ -41,3 +43,11 @@
 	// Atoi returns uint64 and need to be type-casted to uint8 as tpID is uint8 size.
 	return uint8(tpID), err
 }
+
+//IPToInt32 transforms an IP of net.Ip type to int32
+func IPToInt32(ip net.IP) uint32 {
+	if len(ip) == 16 {
+		return binary.BigEndian.Uint32(ip[12:16])
+	}
+	return binary.BigEndian.Uint32(ip)
+}