Updated Adapter to support to handle DHCP trap on NNI and packet-in/out and Bug Fixing.
Tested EAPOL/DHCP/HSIA functionality E2E with EdgeCore OLT and TWSH ONU KIT.

patch: PON port is derived from platform and sent to core and bug fixes

Retested EAPOL/DHCP/HSIA use case end to end with EdgeCore OLT and TWSH ONU KIT

Change-Id: I99df82fd7a1385c10878f6fe09ce0d30c48d8e99
diff --git a/adaptercore/openolt_flowmgr.go b/adaptercore/openolt_flowmgr.go
index 0b8120b..8d9361e 100644
--- a/adaptercore/openolt_flowmgr.go
+++ b/adaptercore/openolt_flowmgr.go
@@ -80,10 +80,36 @@
 	TRAP_TO_HOST = "trap_to_host"
 )
 
+type onuInfo struct {
+	intfId       uint32
+	onuId        uint32
+	serialNumber string
+}
+
+type onuIdKey struct {
+	intfId uint32
+	onuId  uint32
+}
+
+type gemPortKey struct {
+	intfId  uint32
+	gemPort uint32
+}
+
+type packetInInfoKey struct {
+	intfId      uint32
+	onuId       uint32
+	logicalPort uint32
+}
+
 type OpenOltFlowMgr struct {
-	techprofile   []*tp.TechProfileMgr
-	deviceHandler *DeviceHandler
-	resourceMgr   *rsrcMgr.OpenOltResourceMgr
+	techprofile      []*tp.TechProfileMgr
+	deviceHandler    *DeviceHandler
+	resourceMgr      *rsrcMgr.OpenOltResourceMgr
+	onuIds           map[onuIdKey]onuInfo       //OnuId -> OnuInfo
+	onuSerialNumbers map[string]onuInfo         //onu serial_number (string) -> OnuInfo
+	onuGemPortIds    map[gemPortKey]onuInfo     //GemPortId -> OnuInfo
+	packetInGemPort  map[packetInInfoKey]uint32 //packet in gem port
 }
 
 func NewFlowManager(dh *DeviceHandler, rsrcMgr *rsrcMgr.OpenOltResourceMgr) *OpenOltFlowMgr {
@@ -95,6 +121,10 @@
 		log.Error("Error while populating tech profile mgr\n")
 		return nil
 	}
+	flowMgr.onuIds = make(map[onuIdKey]onuInfo)
+	flowMgr.onuSerialNumbers = make(map[string]onuInfo)
+	flowMgr.onuGemPortIds = make(map[gemPortKey]onuInfo)
+	flowMgr.packetInGemPort = make(map[packetInInfoKey]uint32)
 	log.Info("Initialization of  flow manager success!!")
 	return &flowMgr
 }
@@ -238,6 +268,9 @@
 		log.Error("Errow while uploading gemtopon map to KV store")
 	}
 	log.Debug("Stored tconts and GEM into KV store successfully")
+	for _, gemPort := range gemPortIDs {
+		f.addGemPortToOnuInfoMap(intfId, onuId, gemPort)
+	}
 }
 
 func (f *OpenOltFlowMgr) populateTechProfilePerPonPort() error {
@@ -357,8 +390,8 @@
 	}
 
 	action[TRAP_TO_HOST] = true
-	classifier[UDP_SRC] = 68
-	classifier[UDP_DST] = 67
+	classifier[UDP_SRC] = uint32(68)
+	classifier[UDP_DST] = uint32(67)
 	classifier[PACKET_TAG_TYPE] = SINGLE_TAG
 	delete(classifier, VLAN_VID)
 
@@ -557,7 +590,7 @@
 		classifier.IpProto = ipProto.(uint32)
 	}
 	if vlanId, ok := classifierInfo[VLAN_VID]; ok {
-		classifier.OVid = vlanId.(uint32)
+		classifier.OVid = (vlanId.(uint32)) & 0xFFF
 	}
 	if metadata, ok := classifierInfo[METADATA]; ok { // TODO: Revisit
 		classifier.IVid = uint32(metadata.(uint64))
@@ -647,8 +680,17 @@
 
 func (f *OpenOltFlowMgr) getUpdatedFlowInfo(flow *openolt_pb2.Flow, flowStoreCookie uint64, flowCategory string) *[]rsrcMgr.FlowInfo {
 	var flows []rsrcMgr.FlowInfo = []rsrcMgr.FlowInfo{rsrcMgr.FlowInfo{Flow: flow, FlowCategory: flowCategory, FlowStoreCookie: flowStoreCookie}}
-	// Get existing flow for flowid for given subscriber from KV store
-	existingFlows := f.resourceMgr.GetFlowIDInfo(uint32(flow.AccessIntfId), uint32(flow.OnuId), uint32(flow.UniId), flow.FlowId)
+	var intfId uint32
+	/* For flows which trap out of the NNI, the AccessIntfId is invalid
+	   (set to -1). In such cases, we need to refer to the NetworkIntfId .
+	*/
+	if flow.AccessIntfId != -1 {
+		intfId = uint32(flow.AccessIntfId)
+	} else {
+		intfId = uint32(flow.NetworkIntfId)
+	}
+	// Get existing flows matching flowid for given subscriber from KV store
+	existingFlows := f.resourceMgr.GetFlowIDInfo(intfId, uint32(flow.OnuId), uint32(flow.UniId), flow.FlowId)
 	if existingFlows != nil {
 		log.Debugw("Flow exists for given flowID, appending it to current flow", log.Fields{"flowID": flow.FlowId})
 		for _, f := range *existingFlows {
@@ -709,8 +751,7 @@
 
 func (f *OpenOltFlowMgr) getOnuChildDevice(intfId uint32, onuId uint32) (*voltha.Device, error) {
 	log.Debugw("GetChildDevice", log.Fields{"pon port": intfId, "onuId": onuId})
-	//parentPortNo := IntfIdToPortNo(intfId, voltha.Port_PON_OLT)
-	parentPortNo := intfId
+	parentPortNo := IntfIdToPortNo(intfId, voltha.Port_PON_OLT)
 	onuDevice := f.deviceHandler.GetChildDevice(parentPortNo, onuId)
 	if onuDevice == nil {
 		log.Errorw("onu not found", log.Fields{"intfId": parentPortNo, "onuId": onuId})
@@ -867,6 +908,17 @@
 	}
 	log.Infow("Flow ports", log.Fields{"classifierInfo_inport": classifierInfo[IN_PORT], "action_output": actionInfo[OUTPUT]})
 	portNo, intfId, onuId, uniId := ExtractAccessFromFlow(classifierInfo[IN_PORT].(uint32), actionInfo[OUTPUT].(uint32))
+	if ipProto, ok := classifierInfo[IP_PROTO]; ok {
+		if ipProto.(uint32) == IP_PROTO_DHCP {
+			if udpSrc, ok := classifierInfo[UDP_SRC]; ok {
+				if udpSrc.(uint32) == uint32(67) {
+					log.Debug("trap-dhcp-from-nni-flow")
+					f.addDHCPTrapFlowOnNNI(flow, classifierInfo, portNo)
+					return
+				}
+			}
+		}
+	}
 	f.divideAndAddFlow(intfId, onuId, uniId, portNo, classifierInfo, actionInfo, flow)
 }
 
@@ -898,3 +950,153 @@
 	log.Debugw("success Sending Load-tech-profile-request-to-brcm-onu-adapter", log.Fields{"msg": tpDownloadMsg})
 	return nil
 }
+
+// This function adds onu info to cache
+func (f *OpenOltFlowMgr) UpdateOnuInfo(intfID uint32, onuID uint32, serialNum string) {
+	onu := onuInfo{intfId: intfID, onuId: onuID, serialNumber: serialNum}
+	onuIDkey := onuIdKey{intfId: intfID, onuId: onuID}
+	f.onuIds[onuIDkey] = onu
+	log.Debugw("Updated onuinfo", log.Fields{"intfID": intfID, "onuID": onuID, "serialNum": serialNum})
+}
+
+// This function stores adds GEMport to ONU map
+func (f *OpenOltFlowMgr) addGemPortToOnuInfoMap(intfId uint32, onuId uint32, gemPort uint32) {
+	onuIDkey := onuIdKey{intfId: intfId, onuId: onuId}
+	if val, ok := f.onuIds[onuIDkey]; ok {
+		onuInfo := val
+		gemPortKey := gemPortKey{intfId: intfId, gemPort: gemPort}
+		f.onuGemPortIds[gemPortKey] = onuInfo
+		log.Debugw("Cached Gemport to Onuinfo map", log.Fields{"GemPort": gemPort, "intfId": onuInfo.intfId, "onuId": onuInfo.onuId})
+		return
+	}
+	log.Errorw("OnuInfo not found", log.Fields{"intfId": intfId, "onuId": onuId, "gemPort": gemPort})
+}
+
+// This function Lookup maps  by serialNumber or (intfId, gemPort)
+// Returns OnuID,nil if found or set 0,error if no onuId is found for serialNumber or (intfId, gemPort)
+func (f *OpenOltFlowMgr) getOnuIdfromGemPortMap(serialNumber string, intfId uint32, gemPortId uint32) (uint32, error) {
+	log.Debugw("Getting ONU ID from GEM port and PON port", log.Fields{"serialNumber": serialNumber, "intfId": intfId, "gemPortId": gemPortId})
+	if serialNumber != "" {
+		if onuInfo, ok := f.onuSerialNumbers[serialNumber]; ok {
+			return onuInfo.onuId, nil
+		}
+	} else {
+		gemPortKey := gemPortKey{intfId: intfId, gemPort: gemPortId}
+		if onuInfo, ok := f.onuGemPortIds[gemPortKey]; ok {
+			log.Debugw("Retrived onu info from access", log.Fields{"intfId": intfId, "gemPortId": gemPortId, "onuId": onuInfo.onuId})
+			return onuInfo.onuId, nil
+		}
+	}
+	log.Errorw("ONU ID  is not found", log.Fields{"serialNumber": serialNumber, "intfId": intfId, "gemPort": gemPortId})
+	return uint32(0), errors.New("Key Error ,ONU ID  is not found") // ONU ID 0 is not a valid one
+}
+
+// This function computes logical port UNI/NNI port from packet-in indication and returns the same
+func (f *OpenOltFlowMgr) GetLogicalPortFromPacketIn(packetIn *openolt_pb2.PacketIndication) (uint32, error) {
+	var logicalPortNum uint32
+	var onuId uint32
+	var err error
+
+	if packetIn.IntfType == "pon" {
+		// packet indication does not have serial number , so sending as nil
+		if onuId, err = f.getOnuIdfromGemPortMap("", packetIn.IntfId, packetIn.GemportId); err != nil {
+			log.Errorw("Unable to get ONU ID from GEM/PON port", log.Fields{"pon port": packetIn.IntfId, "gemport": packetIn.GemportId})
+			return logicalPortNum, err
+		}
+		if packetIn.PortNo != 0 {
+			logicalPortNum = packetIn.PortNo
+		} else {
+			uniId := uint32(0) //  FIXME - multi-uni support
+			logicalPortNum = MkUniPortNum(packetIn.IntfId, onuId, uniId)
+		}
+		// Store the gem port through which the packet_in came. Use the same gem port for packet_out
+		pktInkey := packetInInfoKey{intfId: packetIn.IntfId, onuId: onuId, logicalPort: logicalPortNum}
+		f.packetInGemPort[pktInkey] = packetIn.GemportId
+	} else if packetIn.IntfType == "nni" {
+		logicalPortNum = IntfIdToPortNo(packetIn.IntfId, voltha.Port_ETHERNET_NNI)
+	}
+	log.Debugw("Retrieved logicalport from  packet-in", log.Fields{"logicalPortNum": logicalPortNum, "IntfType": packetIn.IntfType})
+	return logicalPortNum, nil
+}
+
+func (f *OpenOltFlowMgr) GetPacketOutGemPortId(intfId uint32, onuId uint32, portNum uint32) (uint32, error) {
+	var gemPortId uint32
+	var err error
+	key := packetInInfoKey{intfId: intfId, onuId: onuId, logicalPort: portNum}
+	if val, ok := f.packetInGemPort[key]; ok {
+		gemPortId = val
+	} else {
+		log.Errorw("Key-Error while fetching packet-out GEM port", log.Fields{"key": key})
+		err = errors.New("Key-Error while fetching packet-out GEM port")
+	}
+	return gemPortId, err
+}
+
+func (f *OpenOltFlowMgr) addDHCPTrapFlowOnNNI(logicalFlow *ofp.OfpFlowStats, classifier map[string]interface{}, portNo uint32) {
+	log.Debug("Adding trap-dhcp-of-nni-flow")
+	action := make(map[string]interface{})
+	classifier[PACKET_TAG_TYPE] = DOUBLE_TAG
+	action[TRAP_TO_HOST] = true
+	/* We manage flowId resource pool on per PON port basis.
+	   Since this situation is tricky, as a hack, we pass the NNI port
+	   index (network_intf_id) as PON port Index for the flowId resource
+	   pool. Also, there is no ONU Id available for trapping DHCP packets
+	   on NNI port, use onu_id as -1 (invalid)
+	   ****************** CAVEAT *******************
+	   This logic works if the NNI Port Id falls within the same valid
+	   range of PON Port Ids. If this doesn't work for some OLT Vendor
+	   we need to have a re-look at this.
+	   *********************************************
+	*/
+	onuId := -1
+	uniId := -1
+	gemPortId := -1
+	allocId := -1
+	networkInterfaceId := DEFAULT_NETWORK_INTERFACE_ID
+	flowStoreCookie := getFlowStoreCookie(classifier, uint32(0))
+	if present := f.resourceMgr.IsFlowCookieOnKVStore(uint32(networkInterfaceId), uint32(onuId), uint32(uniId), flowStoreCookie); present {
+		log.Debug("Flow-exists--not-re-adding")
+		return
+	}
+	flowId, err := f.resourceMgr.GetFlowID(uint32(networkInterfaceId), uint32(onuId), uint32(uniId), flowStoreCookie, "")
+	if err != nil {
+		log.Errorw("Flow id unavailable for DHCP traponNNI flow", log.Fields{"error": err})
+		return
+	}
+	var classifierProto *openolt_pb2.Classifier
+	var actionProto *openolt_pb2.Action
+	if classifierProto = makeOpenOltClassifierField(classifier); classifierProto == nil {
+		log.Error("Error in making classifier protobuf for  dhcp trap on nni flow")
+		return
+	}
+	log.Debugw("Created classifier proto", log.Fields{"classifier": *classifierProto})
+	if actionProto = makeOpenOltActionField(action); actionProto == nil {
+		log.Error("Error in making action protobuf for dhcp trap on nni flow")
+		return
+	}
+	log.Debugw("Created action proto", log.Fields{"action": *actionProto})
+	downstreamflow := openolt_pb2.Flow{AccessIntfId: int32(-1), // AccessIntfId not required
+		OnuId:         int32(onuId), // OnuId not required
+		UniId:         int32(uniId), // UniId not used
+		FlowId:        flowId,
+		FlowType:      DOWNSTREAM,
+		AllocId:       int32(allocId),               // AllocId not used
+		NetworkIntfId: DEFAULT_NETWORK_INTERFACE_ID, // one NNI port is supported now
+		GemportId:     int32(gemPortId),             // GemportId not used
+		Classifier:    classifierProto,
+		Action:        actionProto,
+		Priority:      int32(logicalFlow.Priority),
+		Cookie:        logicalFlow.Cookie,
+		PortNo:        portNo}
+	if ok := f.addFlowToDevice(&downstreamflow); ok {
+		log.Debug("DHCP trap on NNI flow added to device successfully")
+		flowsToKVStore := f.getUpdatedFlowInfo(&downstreamflow, flowStoreCookie, "")
+		if err := f.updateFlowInfoToKVStore(int32(networkInterfaceId),
+			int32(onuId),
+			int32(uniId),
+			flowId, flowsToKVStore); err != nil {
+			log.Errorw("Error uploading DHCP DL  flow into KV store", log.Fields{"flow": downstreamflow, "error": err})
+		}
+	}
+	return
+}