Support group mods and group stats requests.

Added support for match IPv4 destination address.

Change-Id: I8693299f29ba3c45f61b936877d443218e5410c4
diff --git a/internal/pkg/openflow/connection.go b/internal/pkg/openflow/connection.go
index 691644c..af388b2 100644
--- a/internal/pkg/openflow/connection.go
+++ b/internal/pkg/openflow/connection.go
@@ -407,7 +407,7 @@
 			ofc.sendRoleSlaveError(header)
 			return
 		}
-		// TODO handle group mods
+		ofc.handleGroupMod(header.(ofp.IGroupMod))
 	}
 }
 
diff --git a/internal/pkg/openflow/flowMod.go b/internal/pkg/openflow/flowMod.go
index 5424c12..08c0b98 100644
--- a/internal/pkg/openflow/flowMod.go
+++ b/internal/pkg/openflow/flowMod.go
@@ -26,6 +26,7 @@
 	"github.com/opencord/voltha-lib-go/v3/pkg/log"
 	"github.com/opencord/voltha-protos/v3/go/openflow_13"
 	"github.com/opencord/voltha-protos/v3/go/voltha"
+	"net"
 )
 
 var oxmMap = map[string]int32{
@@ -119,6 +120,10 @@
 			field.Value = &voltha.OfpOxmOfbField_IpProto{
 				IpProto: uint32(val.(ofp.IpPrototype)),
 			}
+		case voltha.OxmOfbFieldTypes_OFPXMT_OFB_IPV4_DST:
+			field.Value = &voltha.OfpOxmOfbField_Ipv4Dst{
+				Ipv4Dst: binary.BigEndian.Uint32(val.(net.IP)),
+			}
 		case voltha.OxmOfbFieldTypes_OFPXMT_OFB_UDP_SRC:
 			field.Value = &voltha.OfpOxmOfbField_UdpSrc{
 				UdpSrc: uint32(val.(uint16)),
@@ -355,6 +360,10 @@
 			field.Value = &voltha.OfpOxmOfbField_IpProto{
 				IpProto: uint32(val.(ofp.IpPrototype)),
 			}
+		case voltha.OxmOfbFieldTypes_OFPXMT_OFB_IPV4_DST:
+			field.Value = &voltha.OfpOxmOfbField_Ipv4Dst{
+				Ipv4Dst: binary.BigEndian.Uint32(val.(net.IP)),
+			}
 		case voltha.OxmOfbFieldTypes_OFPXMT_OFB_UDP_SRC:
 			field.Value = &voltha.OfpOxmOfbField_UdpSrc{
 				UdpSrc: uint32(val.(uint16)),
diff --git a/internal/pkg/openflow/group.go b/internal/pkg/openflow/group.go
new file mode 100644
index 0000000..a1e09c7
--- /dev/null
+++ b/internal/pkg/openflow/group.go
@@ -0,0 +1,156 @@
+/*
+   Copyright 2020 the original author or authors.
+
+   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 openflow
+
+import (
+	"context"
+	"github.com/donNewtonAlpha/goloxi"
+	ofp "github.com/donNewtonAlpha/goloxi/of13"
+	"github.com/opencord/voltha-lib-go/v3/pkg/log"
+	"github.com/opencord/voltha-protos/v3/go/openflow_13"
+	"github.com/opencord/voltha-protos/v3/go/voltha"
+)
+
+func (ofc *OFConnection) handleGroupMod(groupMod ofp.IGroupMod) {
+
+	volthaClient := ofc.VolthaClient.Get()
+	if volthaClient == nil {
+		logger.Errorw("no-voltha-connection",
+			log.Fields{"device-id": ofc.DeviceID})
+		return
+	}
+
+	groupUpdate := &openflow_13.FlowGroupTableUpdate{
+		Id: ofc.DeviceID,
+		GroupMod: &voltha.OfpGroupMod{
+			Command: openflowGroupModCommandToVoltha(groupMod.GetCommand()),
+			Type:    openflowGroupTypeToVoltha(groupMod.GetGroupType()),
+			GroupId: groupMod.GetGroupId(),
+			Buckets: openflowBucketsToVoltha(groupMod.GetBuckets()),
+		},
+	}
+
+	_, err := volthaClient.UpdateLogicalDeviceFlowGroupTable(context.Background(), groupUpdate)
+	if err != nil {
+		logger.Errorw("Error updating group table",
+			log.Fields{"device-id": ofc.DeviceID, "error": err})
+	}
+
+}
+
+func openflowGroupModCommandToVoltha(command ofp.GroupModCommand) openflow_13.OfpGroupModCommand {
+	switch command {
+	case ofp.OFPGCAdd:
+		return openflow_13.OfpGroupModCommand_OFPGC_ADD
+	case ofp.OFPGCModify:
+		return openflow_13.OfpGroupModCommand_OFPGC_MODIFY
+	case ofp.OFPGCDelete:
+		return openflow_13.OfpGroupModCommand_OFPGC_DELETE
+	}
+	logger.Errorw("Unknown group mod command", log.Fields{"command": command})
+	return 0
+}
+
+func openflowGroupTypeToVoltha(t ofp.GroupType) openflow_13.OfpGroupType {
+	switch t {
+	case ofp.OFPGTAll:
+		return openflow_13.OfpGroupType_OFPGT_ALL
+	case ofp.OFPGTSelect:
+		return openflow_13.OfpGroupType_OFPGT_SELECT
+	case ofp.OFPGTIndirect:
+		return openflow_13.OfpGroupType_OFPGT_INDIRECT
+	case ofp.OFPGTFf:
+		return openflow_13.OfpGroupType_OFPGT_FF
+	}
+	logger.Errorw("Unknown openflow group type", log.Fields{"type": t})
+	return 0
+}
+
+func volthaGroupTypeToOpenflow(t openflow_13.OfpGroupType) ofp.GroupType {
+	switch t {
+	case openflow_13.OfpGroupType_OFPGT_ALL:
+		return ofp.OFPGTAll
+	case openflow_13.OfpGroupType_OFPGT_SELECT:
+		return ofp.OFPGTSelect
+	case openflow_13.OfpGroupType_OFPGT_INDIRECT:
+		return ofp.OFPGTIndirect
+	case openflow_13.OfpGroupType_OFPGT_FF:
+		return ofp.OFPGTFf
+	}
+	logger.Errorw("Unknown voltha group type", log.Fields{"type": t})
+	return 0
+}
+
+func openflowBucketsToVoltha(buckets []*ofp.Bucket) []*openflow_13.OfpBucket {
+	outBuckets := make([]*openflow_13.OfpBucket, len(buckets))
+
+	for i, bucket := range buckets {
+		b := &openflow_13.OfpBucket{
+			Weight:     uint32(bucket.Weight),
+			WatchPort:  uint32(bucket.WatchPort),
+			WatchGroup: bucket.WatchGroup,
+			Actions:    openflowActionsToVoltha(bucket.Actions),
+		}
+		outBuckets[i] = b
+	}
+
+	return outBuckets
+}
+
+func openflowActionsToVoltha(actions []goloxi.IAction) []*openflow_13.OfpAction {
+	outActions := make([]*openflow_13.OfpAction, len(actions))
+
+	for i, action := range actions {
+		outActions[i] = extractAction(action)
+	}
+
+	return outActions
+}
+
+func volthaBucketsToOpenflow(buckets []*openflow_13.OfpBucket) []*ofp.Bucket {
+	outBuckets := make([]*ofp.Bucket, len(buckets))
+
+	for i, bucket := range buckets {
+		actions, length := volthaActionsToOpenflow(bucket.Actions)
+		b := &ofp.Bucket{
+			// TODO loxi should set lengths automatically
+			// last 4 is padding
+			Len:        2 + 2 + 4 + 4 + length + 4, // length field  + weight + watchPort + watchGroup + actions
+			Weight:     uint16(bucket.Weight),
+			WatchPort:  ofp.Port(bucket.WatchPort),
+			WatchGroup: bucket.WatchGroup,
+			Actions:    actions,
+		}
+		outBuckets[i] = b
+	}
+
+	return outBuckets
+}
+
+func volthaActionsToOpenflow(actions []*openflow_13.OfpAction) ([]goloxi.IAction, uint16) {
+	outActions := make([]goloxi.IAction, len(actions))
+
+	var length uint16
+
+	for i, action := range actions {
+		a, l := parseAction(action)
+		length += l
+		outActions[i] = a
+	}
+
+	return outActions, length
+}
diff --git a/internal/pkg/openflow/parseGrpcReturn.go b/internal/pkg/openflow/parseGrpcReturn.go
index a5bd1d1..beff0be 100644
--- a/internal/pkg/openflow/parseGrpcReturn.go
+++ b/internal/pkg/openflow/parseGrpcReturn.go
@@ -16,6 +16,8 @@
 package openflow
 
 import (
+	"bytes"
+	"encoding/binary"
 	"encoding/json"
 	"github.com/donNewtonAlpha/goloxi"
 	ofp "github.com/donNewtonAlpha/goloxi/of13"
@@ -24,13 +26,11 @@
 	"github.com/opencord/voltha-protos/v3/go/voltha"
 )
 
-func parseOxm(ofbField *openflow_13.OfpOxmOfbField, DeviceID string) (goloxi.IOxm, uint16) {
+func parseOxm(ofbField *openflow_13.OfpOxmOfbField) (goloxi.IOxm, uint16) {
 	if logger.V(log.DebugLevel) {
 		js, _ := json.Marshal(ofbField)
 		logger.Debugw("parseOxm called",
-			log.Fields{
-				"device-id": DeviceID,
-				"ofbField":  js})
+			log.Fields{"ofbField": js})
 	}
 
 	switch ofbField.Type {
@@ -54,6 +54,17 @@
 		val := ofbField.GetValue().(*openflow_13.OfpOxmOfbField_IpProto)
 		ofpIpProto.Value = ofp.IpPrototype(val.IpProto)
 		return ofpIpProto, 1
+	case voltha.OxmOfbFieldTypes_OFPXMT_OFB_IPV4_DST:
+		ofpIpv4Dst := ofp.NewOxmIpv4Dst()
+		val := ofbField.GetValue().(*openflow_13.OfpOxmOfbField_Ipv4Dst)
+		buf := new(bytes.Buffer)
+		err := binary.Write(buf, binary.BigEndian, val.Ipv4Dst)
+		if err != nil {
+			logger.Errorw("error writing ipv4 address %v",
+				log.Fields{"error": err})
+		}
+		ofpIpv4Dst.Value = buf.Bytes()
+		return ofpIpv4Dst, 4
 	case voltha.OxmOfbFieldTypes_OFPXMT_OFB_UDP_SRC:
 		ofpUdpSrc := ofp.NewOxmUdpSrc()
 		val := ofbField.GetValue().(*openflow_13.OfpOxmOfbField_UdpSrc)
@@ -97,21 +108,17 @@
 		if logger.V(log.WarnLevel) {
 			js, _ := json.Marshal(ofbField)
 			logger.Warnw("ParseOXM Unhandled OxmField",
-				log.Fields{
-					"device-id": DeviceID,
-					"OfbField":  js})
+				log.Fields{"OfbField": js})
 		}
 	}
 	return nil, 0
 }
 
-func parseInstructions(ofpInstruction *openflow_13.OfpInstruction, DeviceID string) (ofp.IInstruction, uint16) {
+func parseInstructions(ofpInstruction *openflow_13.OfpInstruction) (ofp.IInstruction, uint16) {
 	if logger.V(log.DebugLevel) {
 		js, _ := json.Marshal(ofpInstruction)
 		logger.Debugw("parseInstructions called",
-			log.Fields{
-				"device-id":   DeviceID,
-				"Instruction": js})
+			log.Fields{"Instruction": js})
 	}
 	instType := ofpInstruction.Type
 	data := ofpInstruction.GetData()
@@ -141,7 +148,7 @@
 		//ofpActions := ofpInstruction.GetActions().Actions
 		var actions []goloxi.IAction
 		for _, ofpAction := range ofpInstruction.GetActions().Actions {
-			action, actionSize := parseAction(ofpAction, DeviceID)
+			action, actionSize := parseAction(ofpAction)
 			actions = append(actions, action)
 			instructionSize += actionSize
 
@@ -152,7 +159,6 @@
 			js, _ := json.Marshal(instruction)
 			logger.Debugw("parseInstructions returning",
 				log.Fields{
-					"device-id":          DeviceID,
 					"size":               instructionSize,
 					"parsed-instruction": js})
 		}
@@ -162,13 +168,11 @@
 	return nil, 0
 }
 
-func parseAction(ofpAction *openflow_13.OfpAction, DeviceID string) (goloxi.IAction, uint16) {
+func parseAction(ofpAction *openflow_13.OfpAction) (goloxi.IAction, uint16) {
 	if logger.V(log.DebugLevel) {
 		js, _ := json.Marshal(ofpAction)
 		logger.Debugw("parseAction called",
-			log.Fields{
-				"device-id": DeviceID,
-				"action":    js})
+			log.Fields{"action": js})
 	}
 	switch ofpAction.Type {
 	case openflow_13.OfpActionType_OFPAT_OUTPUT:
@@ -191,17 +195,21 @@
 		ofpActionSetField := ofpAction.GetSetField()
 		setFieldAction := ofp.NewActionSetField()
 
-		iOxm, _ := parseOxm(ofpActionSetField.GetField().GetOfbField(), DeviceID)
+		iOxm, _ := parseOxm(ofpActionSetField.GetField().GetOfbField())
 		setFieldAction.Field = iOxm
 		setFieldAction.Len = 16
 		return setFieldAction, 16
+	case openflow_13.OfpActionType_OFPAT_GROUP:
+		ofpGroupAction := ofpAction.GetGroup()
+		groupAction := ofp.NewActionGroup()
+		groupAction.GroupId = ofpGroupAction.GroupId
+		groupAction.Len = 8
+		return groupAction, 8
 	default:
 		if logger.V(log.WarnLevel) {
 			js, _ := json.Marshal(ofpAction)
 			logger.Warnw("parseAction unknow action",
-				log.Fields{
-					"device-id": DeviceID,
-					"action":    js})
+				log.Fields{"action": js})
 		}
 	}
 	return nil, 0
diff --git a/internal/pkg/openflow/stats.go b/internal/pkg/openflow/stats.go
index 246bc80..9b642ce 100644
--- a/internal/pkg/openflow/stats.go
+++ b/internal/pkg/openflow/stats.go
@@ -350,7 +350,7 @@
 		for _, oxmField := range pbMatch.GetOxmFields() {
 			field := oxmField.GetField()
 			ofbField := field.(*openflow_13.OfpOxmField_OfbField).OfbField
-			iOxm, oxmSize := parseOxm(ofbField, ofc.DeviceID)
+			iOxm, oxmSize := parseOxm(ofbField)
 			fields = append(fields, iOxm)
 			if oxmSize > 0 {
 				size += 4 //header for oxm
@@ -368,7 +368,7 @@
 		entry.SetMatch(*match)
 		var instructions []ofp.IInstruction
 		for _, ofpInstruction := range item.Instructions {
-			instruction, size := parseInstructions(ofpInstruction, ofc.DeviceID)
+			instruction, size := parseInstructions(ofpInstruction)
 			instructions = append(instructions, instruction)
 			entrySize += size
 		}
@@ -416,13 +416,20 @@
 		entry.SetRefCount(stats.GetRefCount())
 		entry.SetGroupId(stats.GetGroupId())
 		var bucketStatsList []*ofp.BucketCounter
-		for _, bucketStat := range stats.GetBucketStats() {
+		// TODO fix this when API handler is fixed in the core
+		// Core doesn't return any buckets in the Stats object, so just
+		// fill out an empty BucketCounter for each bucket in the group Desc for now.
+		for range item.GetDesc().GetBuckets() {
 			bucketCounter := ofp.BucketCounter{}
-			bucketCounter.SetPacketCount(bucketStat.GetPacketCount())
-			bucketCounter.SetByteCount(bucketStat.GetByteCount())
+			bucketCounter.SetPacketCount(0)
+			bucketCounter.SetByteCount(0)
 			bucketStatsList = append(bucketStatsList, &bucketCounter)
 		}
 		entry.SetBucketStats(bucketStatsList)
+		// TODO loxi should set lengths automatically
+		// last 2 + 4 are padding
+		length := 2 + 4 + 4 + 8 + 8 + 4 + 4 + len(bucketStatsList)*16 + 2 + 4
+		entry.SetLength(uint16(length))
 		groupStatsEntries = append(groupStatsEntries, &entry)
 	}
 	response.SetEntries(groupStatsEntries)
@@ -445,19 +452,23 @@
 	}
 	var groupDescStatsEntries []*ofp.GroupDescStatsEntry
 	for _, item := range reply.GetItems() {
-		stats := item.GetStats()
-		var groupDesc ofp.GroupDescStatsEntry
-		groupDesc.SetGroupId(stats.GetGroupId())
-		/*
-			buckets := item.g
-			var bucketList []*ofp.Bucket
-			for j:=0;j<len(buckets);j++{
+		desc := item.GetDesc()
 
-			}
+		buckets := volthaBucketsToOpenflow(desc.Buckets)
+		var bucketsLength uint16
+		for _, b := range buckets {
+			bucketsLength += b.Len
+		}
 
-			groupDesc.SetBuckets(bucketList)
-		*/
-		groupDescStatsEntries = append(groupDescStatsEntries, &groupDesc)
+		groupDesc := &ofp.GroupDescStatsEntry{
+			// TODO loxi should set lengths automatically
+			// last 1 is padding
+			Length:    2 + 1 + 4 + bucketsLength + 1, // length field + groupType + groupId + buckets
+			GroupType: volthaGroupTypeToOpenflow(desc.Type),
+			GroupId:   desc.GroupId,
+			Buckets:   buckets,
+		}
+		groupDescStatsEntries = append(groupDescStatsEntries, groupDesc)
 	}
 	response.SetEntries(groupDescStatsEntries)
 	return response, nil
diff --git a/internal/pkg/openflow/utils.go b/internal/pkg/openflow/utils.go
index 7a29e66..dac2f22 100644
--- a/internal/pkg/openflow/utils.go
+++ b/internal/pkg/openflow/utils.go
@@ -77,6 +77,13 @@
 	case ofp.OFPATPopMpls: //PopMpls
 	case ofp.OFPATSetQueue: //SetQueue
 	case ofp.OFPATGroup: //ActionGroup
+		ofpAction.Type = openflow_13.OfpActionType_OFPAT_GROUP
+		group := action.(*ofp.ActionGroup)
+		ofpAction.Action = &openflow_13.OfpAction_Group{
+			Group: &openflow_13.OfpActionGroup{
+				GroupId: group.GroupId,
+			},
+		}
 	case ofp.OFPATSetNwTtl: //SetNwTtl
 	case ofp.OFPATDecNwTtl: //DecNwTtl
 	case ofp.OFPATSetField: //SetField