Merge "Fix the storage of TCONT in bbsim. The TCONTs should be referenced by EntityID and not AllocID. The AllocID is 0xff or 0xffff during reset so we can't find its reference in the map. However the EntityID is always passed correctly to reference the TCONT."
diff --git a/internal/bbsim/devices/flows_test.go b/internal/bbsim/devices/flows_test.go
new file mode 100644
index 0000000..391b39e
--- /dev/null
+++ b/internal/bbsim/devices/flows_test.go
@@ -0,0 +1,223 @@
+/*
+ * Copyright 2018-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 devices
+
+import (
+	"context"
+	"testing"
+	"time"
+
+	"github.com/google/gopacket"
+	"github.com/google/gopacket/layers"
+	"github.com/opencord/bbsim/internal/bbsim/responders/dhcp"
+	"github.com/opencord/bbsim/internal/common"
+	"github.com/opencord/voltha-protos/v5/go/openolt"
+	"github.com/stretchr/testify/assert"
+)
+
+func getTestOlt(t *testing.T, ctx context.Context, services []common.ServiceYaml) (olt *OltDevice, pon *PonPort, onu *Onu, uni *UniPort, stream *mockStream) {
+	common.Services = services
+
+	common.Config = &common.GlobalConfig{
+		Olt: common.OltConfig{
+			ID:          1,
+			NniPorts:    1,
+			PonPorts:    1,
+			OnusPonPort: 1,
+			UniPorts:    1,
+		},
+	}
+
+	olt = CreateOLT(*common.Config, common.Services, true)
+
+	stream = &mockStream{
+		Calls: make(map[int]*openolt.Indication),
+	}
+	olt.OpenoltStream = stream
+	olt.enableContext = ctx
+
+	if len(olt.Pons) <= 0 {
+		assert.Fail(t, "No PONs on OLT")
+		return
+	}
+	pon = olt.Pons[0]
+
+	if len(pon.Onus) <= 0 {
+		assert.Fail(t, "No ONUs on PON")
+		return
+	}
+	onu = pon.Onus[0]
+
+	if len(onu.UniPorts) <= 0 {
+		assert.Fail(t, "No UNIs on ONU")
+		return
+	}
+	uni = onu.UniPorts[0].(*UniPort)
+
+	_, err := olt.ActivateOnu(ctx, &openolt.Onu{
+		IntfId:       pon.ID,
+		OnuId:        onu.ID,
+		SerialNumber: onu.SerialNumber,
+	})
+
+	assert.Nil(t, err)
+
+	assert.Equal(t, len(services), len(uni.Services))
+	for i, s := range uni.Services {
+		service := s.(*Service)
+		assert.Equal(t, services[i].Name, service.Name)
+		assert.Equal(t, services[i].NeedsDhcp, service.NeedsDhcp)
+	}
+
+	err = onu.InternalState.Event(OnuTxInitialize)
+	assert.Nil(t, err)
+
+	//A mock ONU won't start processing messages, so we have to call it manually
+	go onu.ProcessOnuMessages(ctx, stream, nil)
+
+	err = onu.InternalState.Event(OnuTxDiscover)
+	assert.Nil(t, err)
+	err = onu.InternalState.Event(OnuTxEnable)
+	assert.Nil(t, err)
+
+	err = uni.Enable()
+	assert.Nil(t, err)
+
+	return
+}
+
+func addTestFlow(t *testing.T, ctx context.Context, olt *OltDevice, onu *Onu, flow openolt.Flow) {
+	//Check if the flow is correctly added
+	_, err := olt.FlowAdd(ctx, &flow)
+	assert.Nil(t, err)
+}
+
+func removeTestFlow(t *testing.T, ctx context.Context, olt *OltDevice, onu *Onu, flow openolt.Flow) {
+	//Check if the flow is correctly removed
+	_, err := olt.FlowRemove(ctx, &flow)
+	assert.Nil(t, err)
+}
+
+func Test_Flows_FttbTrapRules(t *testing.T) {
+	const (
+		VID_VENDOR_MGMT      = 6
+		VID_NETWORK_DPU_MGMT = 60
+
+		allocId   = int32(1024)
+		gemportId = int32(1024)
+		uniPortNo = uint32(256)
+		nniId     = 0
+		nniPortNo = 0x1000000
+		pbitNone  = 255
+
+		dhcpServiceName = "dpu_dhcp"
+		hsiaServicename = "hsia"
+	)
+
+	testServices := []common.ServiceYaml{
+		{Name: dhcpServiceName, CTag: 60, CTagAllocation: common.TagAllocationShared.String(), STagAllocation: common.TagAllocationShared.String(), NeedsDhcp: true, TechnologyProfileID: 64},
+		{Name: hsiaServicename, UniTagMatch: 4096, CTag: 4096, CTagAllocation: common.TagAllocationShared.String(), STag: 3101, STagAllocation: common.TagAllocationUnique.String(), TechnologyProfileID: 64},
+	}
+
+	ctx, cancel := context.WithCancel(context.Background())
+	defer cancel()
+
+	olt, pon, onu, uni, stream := getTestOlt(t, ctx, testServices)
+
+	flows := []openolt.Flow{
+		{
+			AccessIntfId:  int32(pon.ID),
+			OnuId:         int32(onu.ID),
+			UniId:         int32(uni.ID),
+			FlowId:        1,
+			FlowType:      flowTypeUpstream,
+			AllocId:       allocId,
+			NetworkIntfId: nniId,
+			GemportId:     gemportId,
+			Classifier: &openolt.Classifier{
+				OVid:       VID_NETWORK_DPU_MGMT,
+				OPbits:     pbitNone,
+				EthType:    uint32(layers.EthernetTypeIPv4),
+				IpProto:    uint32(layers.IPProtocolUDP),
+				SrcPort:    68,
+				DstPort:    67,
+				PktTagType: flowTagTypeSingle,
+			},
+			Action: &openolt.Action{
+				Cmd: &openolt.ActionCmd{
+					TrapToHost: true,
+				},
+			},
+			Priority: 40000,
+			PortNo:   uniPortNo,
+		},
+		{
+			AccessIntfId:  -1,
+			OnuId:         -1,
+			UniId:         -1,
+			FlowId:        2,
+			FlowType:      flowTypeDownstream,
+			AllocId:       -1,
+			NetworkIntfId: nniId,
+			GemportId:     -1,
+			Classifier: &openolt.Classifier{
+				OVid:       VID_NETWORK_DPU_MGMT,
+				OPbits:     pbitNone,
+				EthType:    uint32(layers.EthernetTypeIPv4),
+				IpProto:    uint32(layers.IPProtocolUDP),
+				SrcPort:    67,
+				PktTagType: flowTagTypeDouble,
+			},
+			Action: &openolt.Action{
+				Cmd: &openolt.ActionCmd{
+					TrapToHost: true,
+				},
+			},
+			Priority: 40000,
+			PortNo:   nniPortNo,
+		},
+	}
+
+	for _, f := range flows {
+		addTestFlow(t, ctx, olt, onu, f)
+	}
+
+	//Wait a bit for messages on various channels to be processed
+	time.Sleep(time.Second)
+
+	//Check if a DHCP request is sent correctly after trap rules have been added
+	assert.True(t, stream.CallCount > 0)
+	dhcpCall := stream.Calls[stream.CallCount]
+
+	switch ind := dhcpCall.Data.(type) {
+	case *openolt.Indication_PktInd:
+		assert.Equal(t, uniPortNo, ind.PktInd.PortNo)
+		assert.Equal(t, pon.ID, ind.PktInd.IntfId)
+		assert.Equal(t, "pon", ind.PktInd.IntfType)
+		assert.Equal(t, uint32(gemportId), ind.PktInd.GemportId)
+
+		packet := gopacket.NewPacket(ind.PktInd.Pkt, layers.LayerTypeEthernet, gopacket.Default)
+		_, err := dhcp.GetDhcpLayer(packet)
+		assert.Nil(t, err)
+	default:
+		assert.Fail(t, "Wrong indication type for DHCP request")
+	}
+
+	for _, f := range flows {
+		removeTestFlow(t, ctx, olt, onu, f)
+	}
+}
diff --git a/internal/bbsim/devices/helpers.go b/internal/bbsim/devices/helpers.go
index 1389162..bc88dca 100644
--- a/internal/bbsim/devices/helpers.go
+++ b/internal/bbsim/devices/helpers.go
@@ -43,6 +43,14 @@
 	"both":     Both,
 }
 
+//Constants for openolt Flows
+const (
+	flowTypeUpstream   = "upstream"
+	flowTypeDownstream = "downstream"
+	flowTagTypeSingle  = "single_tag"
+	flowTagTypeDouble  = "double_tag"
+)
+
 var newFSM = fsm.NewFSM
 
 func getOperStateFSM(cb fsm.Callback) *fsm.FSM {
diff --git a/internal/bbsim/devices/onu.go b/internal/bbsim/devices/onu.go
index 168de07..089196f 100644
--- a/internal/bbsim/devices/onu.go
+++ b/internal/bbsim/devices/onu.go
@@ -1675,7 +1675,7 @@
 		OnuId:         int32(o.ID),
 		UniId:         int32(0), // NOTE do not hardcode this, we need to support multiple UNIs
 		FlowId:        uint64(o.ID),
-		FlowType:      "downstream",
+		FlowType:      flowTypeDownstream,
 		NetworkIntfId: int32(0),
 		Classifier:    &classifierProto,
 		Action:        &actionProto,
@@ -1726,7 +1726,7 @@
 		OnuId:         int32(o.ID),
 		UniId:         int32(0), // BBR only supports a single UNI
 		FlowId:        uint64(o.ID),
-		FlowType:      "downstream",
+		FlowType:      flowTypeDownstream,
 		NetworkIntfId: int32(0),
 		Classifier:    &classifierProto,
 		Action:        &actionProto,
diff --git a/internal/bbsim/devices/onu_flow_test.go b/internal/bbsim/devices/onu_flow_test.go
index 66a775f..f0d55fd 100644
--- a/internal/bbsim/devices/onu_flow_test.go
+++ b/internal/bbsim/devices/onu_flow_test.go
@@ -17,12 +17,13 @@
 package devices
 
 import (
+	"testing"
+
 	"github.com/google/gopacket/layers"
 	"github.com/looplab/fsm"
 	"github.com/opencord/bbsim/internal/bbsim/types"
 	"github.com/opencord/voltha-protos/v5/go/openolt"
 	"gotest.tools/assert"
-	"testing"
 )
 
 // test that BBR correctly sends the EAPOL Flow
@@ -43,7 +44,7 @@
 	assert.Equal(t, client.FlowAddSpy.Calls[1].OnuId, int32(onu.ID))
 	assert.Equal(t, client.FlowAddSpy.Calls[1].UniId, int32(0))
 	assert.Equal(t, client.FlowAddSpy.Calls[1].FlowId, uint64(onu.ID))
-	assert.Equal(t, client.FlowAddSpy.Calls[1].FlowType, "downstream")
+	assert.Equal(t, client.FlowAddSpy.Calls[1].FlowType, flowTypeDownstream)
 	assert.Equal(t, client.FlowAddSpy.Calls[1].PortNo, onu.ID)
 }
 
@@ -152,7 +153,7 @@
 		OnuId:         int32(onu.ID),
 		UniId:         int32(0),
 		FlowId:        uint64(onu.ID),
-		FlowType:      "downstream",
+		FlowType:      flowTypeDownstream,
 		AllocId:       int32(0),
 		NetworkIntfId: int32(0),
 		Classifier: &openolt.Classifier{
@@ -199,7 +200,7 @@
 		OnuId:         int32(onu.ID),
 		UniId:         int32(0),
 		FlowId:        uint64(onu.ID),
-		FlowType:      "downstream",
+		FlowType:      flowTypeDownstream,
 		AllocId:       int32(0),
 		NetworkIntfId: int32(0),
 		Classifier: &openolt.Classifier{
@@ -240,7 +241,7 @@
 		OnuId:         int32(onu.ID),
 		UniId:         int32(1),
 		FlowId:        uint64(onu.ID),
-		FlowType:      "downstream",
+		FlowType:      flowTypeDownstream,
 		AllocId:       int32(0),
 		NetworkIntfId: int32(0),
 		Classifier: &openolt.Classifier{
@@ -282,7 +283,7 @@
 		OnuId:         int32(onu.ID),
 		UniId:         int32(0),
 		FlowId:        uint64(onu.ID),
-		FlowType:      "downstream",
+		FlowType:      flowTypeDownstream,
 		AllocId:       int32(0),
 		NetworkIntfId: int32(0),
 		Classifier: &openolt.Classifier{
@@ -326,7 +327,7 @@
 		OnuId:         int32(onu.ID),
 		UniId:         int32(0),
 		FlowId:        uint64(onu.ID),
-		FlowType:      "downstream",
+		FlowType:      flowTypeDownstream,
 		AllocId:       int32(0),
 		NetworkIntfId: int32(0),
 		Classifier: &openolt.Classifier{
@@ -370,7 +371,7 @@
 		OnuId:         int32(onu.ID),
 		UniId:         int32(0),
 		FlowId:        uint64(onu.ID),
-		FlowType:      "downstream",
+		FlowType:      flowTypeDownstream,
 		AllocId:       int32(0),
 		NetworkIntfId: int32(0),
 		Classifier: &openolt.Classifier{
@@ -413,7 +414,7 @@
 		OnuId:         int32(onu.ID),
 		UniId:         int32(0),
 		FlowId:        uint64(onu.ID),
-		FlowType:      "downstream",
+		FlowType:      flowTypeDownstream,
 		AllocId:       int32(0),
 		NetworkIntfId: int32(0),
 		Classifier: &openolt.Classifier{
@@ -458,7 +459,7 @@
 		OnuId:         int32(onu.ID),
 		UniId:         int32(0),
 		FlowId:        uint64(onu.ID),
-		FlowType:      "downstream",
+		FlowType:      flowTypeDownstream,
 		AllocId:       int32(0),
 		NetworkIntfId: int32(0),
 		Classifier: &openolt.Classifier{