[SEBA-843] Storing PortNo in the ONU Device struct so that it can be passed with the packetIndications

Change-Id: I28e0bf5721d11fc52d70c6072a6cf70586ba5f98
diff --git a/api/bbsim/bbsim.proto b/api/bbsim/bbsim.proto
index bc76a92..85581fc 100644
--- a/api/bbsim/bbsim.proto
+++ b/api/bbsim/bbsim.proto
@@ -45,6 +45,7 @@
     int32 STag = 6;
     int32 CTag = 7;
     string HwAddress = 8;
+    int32 PortNo = 9;
 }
 
 message ONUs {
diff --git a/internal/bbsim/api/onus_handler.go b/internal/bbsim/api/onus_handler.go
index 1b823d8..7f42f89 100644
--- a/internal/bbsim/api/onus_handler.go
+++ b/internal/bbsim/api/onus_handler.go
@@ -42,6 +42,7 @@
 				STag:          int32(o.STag),
 				CTag:          int32(o.CTag),
 				HwAddress:     o.HwAddress.String(),
+				PortNo:        int32(o.PortNo),
 			}
 			onus.Items = append(onus.Items, &onu)
 		}
@@ -68,6 +69,7 @@
 		STag:          int32(onu.STag),
 		CTag:          int32(onu.CTag),
 		HwAddress:     onu.HwAddress.String(),
+		PortNo:        int32(onu.PortNo),
 	}
 	return &res, nil
 }
diff --git a/internal/bbsim/devices/messageTypes.go b/internal/bbsim/devices/messageTypes.go
index 6d3f076..fe6abef 100644
--- a/internal/bbsim/devices/messageTypes.go
+++ b/internal/bbsim/devices/messageTypes.go
@@ -75,7 +75,7 @@
 
 type OnuDiscIndicationMessage struct {
 	OperState OperState
-	Onu       Onu
+	Onu       *Onu
 }
 
 type OnuIndicationMessage struct {
diff --git a/internal/bbsim/devices/nni.go b/internal/bbsim/devices/nni.go
index 05585fc..30104fc 100644
--- a/internal/bbsim/devices/nni.go
+++ b/internal/bbsim/devices/nni.go
@@ -34,6 +34,22 @@
 	dhcpServerIp = "182.21.0.128"
 )
 
+type Executor interface {
+	Command(name string, arg ...string) Runnable
+}
+
+type DefaultExecutor struct{}
+
+func (d DefaultExecutor) Command(name string, arg ...string) Runnable {
+	return exec.Command(name, arg...)
+}
+
+type Runnable interface {
+	Run() error
+}
+
+var executor = DefaultExecutor{}
+
 type NniPort struct {
 	// BBSIM Internals
 	ID uint32
@@ -51,7 +67,7 @@
 		}),
 		Type: "nni",
 	}
-	createNNIPair(olt)
+	createNNIPair(executor, olt)
 	return nniPort, nil
 }
 
@@ -89,21 +105,21 @@
 	return nil
 }
 
-// createNNIBridge will create a veth bridge to fake the connection between the NNI port
-// and something upstream, in this case a DHCP server.
-// It is also responsible to start the DHCP server itself
-func createNNIPair(olt *OltDevice) error {
+//createNNIBridge will create a veth bridge to fake the connection between the NNI port
+//and something upstream, in this case a DHCP server.
+//It is also responsible to start the DHCP server itself
+func createNNIPair(executor Executor, olt *OltDevice) error {
 
-	if err := exec.Command("ip", "link", "add", nniVeth, "type", "veth", "peer", "name", upstreamVeth).Run(); err != nil {
+	if err := executor.Command("ip", "link", "add", nniVeth, "type", "veth", "peer", "name", upstreamVeth).Run(); err != nil {
 		nniLogger.Errorf("Couldn't create veth pair between %s and %s", nniVeth, upstreamVeth)
 		return err
 	}
 
-	if err := setVethUp(nniVeth); err != nil {
+	if err := setVethUp(executor, nniVeth); err != nil {
 		return err
 	}
 
-	if err := setVethUp(upstreamVeth); err != nil {
+	if err := setVethUp(executor, upstreamVeth); err != nil {
 		return err
 	}
 
@@ -120,21 +136,21 @@
 }
 
 // setVethUp is responsible to activate a virtual interface
-func setVethUp(vethName string) error {
-	if err := exec.Command("ip", "link", "set", vethName, "up").Run(); err != nil {
+func setVethUp(executor Executor, vethName string) error {
+	if err := executor.Command("ip", "link", "set", vethName, "up").Run(); err != nil {
 		nniLogger.Errorf("Couldn't change interface %s state to up: %v", vethName, err)
 		return err
 	}
 	return nil
 }
 
-func startDHCPServer() error {
+var startDHCPServer = func() error {
 	if err := exec.Command("ip", "addr", "add", dhcpServerIp, "dev", upstreamVeth).Run(); err != nil {
 		nniLogger.Errorf("Couldn't assing ip %s to interface %s: %v", dhcpServerIp, upstreamVeth, err)
 		return err
 	}
 
-	if err := setVethUp(upstreamVeth); err != nil {
+	if err := setVethUp(executor, upstreamVeth); err != nil {
 		return err
 	}
 
@@ -168,7 +184,7 @@
 	return handle, nil
 }
 
-func listenOnVeth(vethName string) (chan *types.PacketMsg, error) {
+var listenOnVeth = func(vethName string) (chan *types.PacketMsg, error) {
 
 	handle, err := getVethHandler(vethName)
 	if err != nil {
diff --git a/internal/bbsim/devices/nni_test.go b/internal/bbsim/devices/nni_test.go
new file mode 100644
index 0000000..60856c6
--- /dev/null
+++ b/internal/bbsim/devices/nni_test.go
@@ -0,0 +1,102 @@
+/*
+ * 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.
+ */
+
+//https://quii.gitbook.io/learn-go-with-tests/go-fundamentals/mocking
+package devices
+
+import (
+	"errors"
+	"github.com/opencord/bbsim/internal/bbsim/types"
+	"gotest.tools/assert"
+	"testing"
+)
+
+func TestSetVethUpSuccess(t *testing.T) {
+	spy := &ExecutorSpy{
+		Calls: make(map[int][]string),
+	}
+	err := setVethUp(spy, "test_veth")
+	assert.Equal(t, spy.CommandCallCount, 1)
+	assert.Equal(t, spy.Calls[1][2], "test_veth")
+	assert.Equal(t, err, nil)
+}
+
+func TestSetVethUpFail(t *testing.T) {
+	spy := &ExecutorSpy{
+		failRun: true,
+		Calls:   make(map[int][]string),
+	}
+	err := setVethUp(spy, "test_veth")
+	assert.Equal(t, spy.CommandCallCount, 1)
+	assert.Equal(t, err.Error(), "fake-error")
+}
+
+func TestCreateNNIPair(t *testing.T) {
+
+	startDHCPServerCalled := false
+	_startDHCPServer := startDHCPServer
+	defer func() { startDHCPServer = _startDHCPServer }()
+	startDHCPServer = func() error {
+		startDHCPServerCalled = true
+		return nil
+	}
+
+	listenOnVethCalled := false
+	_listenOnVeth := listenOnVeth
+	defer func() { listenOnVeth = _listenOnVeth }()
+	listenOnVeth = func(vethName string) (chan *types.PacketMsg, error) {
+		listenOnVethCalled = true
+		return make(chan *types.PacketMsg, 1), nil
+	}
+	spy := &ExecutorSpy{
+		failRun: false,
+		Calls:   make(map[int][]string),
+	}
+
+	olt := OltDevice{}
+
+	err := createNNIPair(spy, &olt)
+
+	assert.Equal(t, spy.CommandCallCount, 3)
+	assert.Equal(t, startDHCPServerCalled, true)
+	assert.Equal(t, listenOnVethCalled, true)
+	assert.Equal(t, err, nil)
+	assert.Assert(t, olt.nniPktInChannel != nil)
+}
+
+type ExecutorSpy struct {
+	failRun bool
+
+	CommandCallCount int
+	RunCallCount     int
+	Calls            map[int][]string
+}
+
+func (s *ExecutorSpy) Command(name string, arg ...string) Runnable {
+	s.CommandCallCount++
+
+	s.Calls[s.CommandCallCount] = arg
+
+	return s
+}
+
+func (s *ExecutorSpy) Run() error {
+	s.RunCallCount++
+	if s.failRun {
+		return errors.New("fake-error")
+	}
+	return nil
+}
diff --git a/internal/bbsim/devices/olt.go b/internal/bbsim/devices/olt.go
index 864a88e..c3a62ff 100644
--- a/internal/bbsim/devices/olt.go
+++ b/internal/bbsim/devices/olt.go
@@ -50,17 +50,17 @@
 	apiDoneChannel  *chan bool
 	nniPktInChannel chan *bbsim.PacketMsg
 
-	Pons []PonPort
-	Nnis []NniPort
+	Pons []*PonPort
+	Nnis []*NniPort
 
 	// OLT Attributes
 	OperState *fsm.FSM
 }
 
-var olt = OltDevice{}
+var olt OltDevice
 
-func GetOLT() OltDevice {
-	return olt
+func GetOLT() *OltDevice {
+	return &olt
 }
 
 func CreateOLT(seq int, nni int, pon int, onuPerPon int, sTag int, cTagInit int, oltDoneChannel *chan bool, apiDoneChannel *chan bool, group *sync.WaitGroup) OltDevice {
@@ -80,8 +80,8 @@
 		NumNni:          nni,
 		NumPon:          pon,
 		NumOnuPerPon:    onuPerPon,
-		Pons:            []PonPort{},
-		Nnis:            []NniPort{},
+		Pons:            []*PonPort{},
+		Nnis:            []*NniPort{},
 		channel:         make(chan Message),
 		oltDoneChannel:  oltDoneChannel,
 		apiDoneChannel:  apiDoneChannel,
@@ -110,7 +110,7 @@
 		oltLogger.Fatalf("Couldn't create NNI Port: %v", err)
 	}
 
-	olt.Nnis = append(olt.Nnis, nniPort)
+	olt.Nnis = append(olt.Nnis, &nniPort)
 
 	// create PON ports
 	availableCTag := cTagInit
@@ -120,6 +120,7 @@
 			ID:     uint32(i),
 			Type:   "pon",
 			Olt:    olt,
+			Onus:   []*Onu{},
 		}
 		p.OperState = getOperStateFSM(func(e *fsm.Event) {
 			oltLogger.WithFields(log.Fields{
@@ -129,13 +130,12 @@
 
 		// create ONU devices
 		for j := 0; j < onuPerPon; j++ {
-			//o := CreateONU(olt, p, uint32(onuId))
 			o := CreateONU(olt, p, uint32(j+1), sTag, availableCTag)
 			p.Onus = append(p.Onus, o)
 			availableCTag = availableCTag + 1
 		}
 
-		olt.Pons = append(olt.Pons, p)
+		olt.Pons = append(olt.Pons, &p)
 	}
 
 	newOltServer(olt)
@@ -246,7 +246,7 @@
 func (o OltDevice) getPonById(id uint32) (*PonPort, error) {
 	for _, pon := range o.Pons {
 		if pon.ID == id {
-			return &pon, nil
+			return pon, nil
 		}
 	}
 	return nil, errors.New(fmt.Sprintf("Cannot find PonPort with id %d in OLT %d", id, o.ID))
@@ -255,7 +255,7 @@
 func (o OltDevice) getNniById(id uint32) (*NniPort, error) {
 	for _, nni := range o.Nnis {
 		if nni.ID == id {
-			return &nni, nil
+			return nni, nil
 		}
 	}
 	return nil, errors.New(fmt.Sprintf("Cannot find NniPort with id %d in OLT %d", id, o.ID))
@@ -375,7 +375,7 @@
 				"IntfType": "nni",
 				"IntfId":   nniId,
 				"Pkt":      message.Pkt.Data(),
-			}).Info("Can't find Dst MacAddress in packet")
+			}).Error("Can't find Dst MacAddress in packet")
 			return
 		}
 
@@ -386,7 +386,7 @@
 				"IntfId":     nniId,
 				"Pkt":        message.Pkt.Data(),
 				"MacAddress": onuMac.String(),
-			}).Info("Can't find ONU with MacAddress")
+			}).Error("Can't find ONU with MacAddress")
 			return
 		}
 
@@ -421,7 +421,7 @@
 	for _, pon := range o.Pons {
 		for _, onu := range pon.Onus {
 			if onu.Sn() == serialNumber {
-				return &onu, nil
+				return onu, nil
 			}
 		}
 	}
@@ -436,7 +436,7 @@
 	for _, pon := range o.Pons {
 		for _, onu := range pon.Onus {
 			if onu.HwAddress.String() == mac.String() {
-				return &onu, nil
+				return onu, nil
 			}
 		}
 	}
@@ -531,8 +531,22 @@
 			"FlowId": flow.FlowId,
 		}).Debugf("This is an OLT flow")
 	} else {
-		pon, _ := o.getPonById(uint32(flow.AccessIntfId))
-		onu, _ := pon.getOnuById(uint32(flow.OnuId))
+		pon, err := o.getPonById(uint32(flow.AccessIntfId))
+		if err != nil {
+			oltLogger.WithFields(log.Fields{
+				"OnuId":  flow.OnuId,
+				"IntfId": flow.AccessIntfId,
+				"err":    err,
+			}).Error("Can't find PonPort")
+		}
+		onu, err := pon.getOnuById(uint32(flow.OnuId))
+		if err != nil {
+			oltLogger.WithFields(log.Fields{
+				"OnuId":  flow.OnuId,
+				"IntfId": flow.AccessIntfId,
+				"err":    err,
+			}).Error("Can't find Onu")
+		}
 
 		msg := Message{
 			Type: FlowUpdate,
@@ -606,8 +620,22 @@
 }
 
 func (o OltDevice) OnuPacketOut(ctx context.Context, onuPkt *openolt.OnuPacket) (*openolt.Empty, error) {
-	pon, _ := o.getPonById(onuPkt.IntfId)
-	onu, _ := pon.getOnuById(onuPkt.OnuId)
+	pon, err := o.getPonById(onuPkt.IntfId)
+	if err != nil {
+		oltLogger.WithFields(log.Fields{
+			"OnuId":  onuPkt.OnuId,
+			"IntfId": onuPkt.IntfId,
+			"err":    err,
+		}).Error("Can't find PonPort")
+	}
+	onu, err := pon.getOnuById(onuPkt.OnuId)
+	if err != nil {
+		oltLogger.WithFields(log.Fields{
+			"OnuId":  onuPkt.OnuId,
+			"IntfId": onuPkt.IntfId,
+			"err":    err,
+		}).Error("Can't find Onu")
+	}
 
 	oltLogger.WithFields(log.Fields{
 		"IntfId": onu.PonPortID,
diff --git a/internal/bbsim/devices/olt_test.go b/internal/bbsim/devices/olt_test.go
index f83fc62..bcb6f77 100644
--- a/internal/bbsim/devices/olt_test.go
+++ b/internal/bbsim/devices/olt_test.go
@@ -41,9 +41,9 @@
 				HwAddress: net.HardwareAddr{0x2e, 0x60, 0x70, 0x13, byte(pon.ID), byte(onuId)},
 			}
 			onu.SerialNumber = onu.NewSN(olt.ID, pon.ID, onu.ID)
-			pon.Onus = append(pon.Onus, onu)
+			pon.Onus = append(pon.Onus, &onu)
 		}
-		olt.Pons = append(olt.Pons, pon)
+		olt.Pons = append(olt.Pons, &pon)
 	}
 	return olt
 }
diff --git a/internal/bbsim/devices/onu.go b/internal/bbsim/devices/onu.go
index dfbd9ce..7d36bfd 100644
--- a/internal/bbsim/devices/onu.go
+++ b/internal/bbsim/devices/onu.go
@@ -34,11 +34,15 @@
 })
 
 type Onu struct {
-	ID            uint32
-	PonPortID     uint32
-	PonPort       PonPort
-	STag          int
-	CTag          int
+	ID        uint32
+	PonPortID uint32
+	PonPort   PonPort
+	STag      int
+	CTag      int
+	// PortNo comes with flows and it's used when sending packetIndications,
+	// There is one PortNo per UNI Port, for now we're only storing the first one
+	// FIXME add support for multiple UNIs
+	PortNo        uint32
 	HwAddress     net.HardwareAddr
 	InternalState *fsm.FSM
 
@@ -52,7 +56,7 @@
 	return onuSnToString(o.SerialNumber)
 }
 
-func CreateONU(olt OltDevice, pon PonPort, id uint32, sTag int, cTag int) Onu {
+func CreateONU(olt OltDevice, pon PonPort, id uint32, sTag int, cTag int) *Onu {
 
 	o := Onu{
 		ID:        id,
@@ -61,8 +65,8 @@
 		STag:      sTag,
 		CTag:      cTag,
 		HwAddress: net.HardwareAddr{0x2e, 0x60, 0x70, 0x13, byte(pon.ID), byte(id)},
-		// NOTE can we combine everything in a single Channel?
-		Channel: make(chan Message, 2048),
+		PortNo:    0,
+		Channel:   make(chan Message, 2048),
 	}
 	o.SerialNumber = o.NewSN(olt.ID, pon.ID, o.ID)
 
@@ -162,7 +166,7 @@
 			},
 		},
 	)
-	return o
+	return &o
 }
 
 func (o Onu) logStateChange(src string, dst string) {
@@ -201,21 +205,22 @@
 			o.handleFlowUpdate(msg, stream)
 		case StartEAPOL:
 			log.Infof("Receive StartEAPOL message on ONU Channel")
-			eapol.SendEapStart(o.ID, o.PonPortID, o.Sn(), o.HwAddress, o.InternalState, stream)
+			eapol.SendEapStart(o.ID, o.PonPortID, o.Sn(), o.PortNo, o.HwAddress, o.InternalState, stream)
 		case StartDHCP:
 			log.Infof("Receive StartDHCP message on ONU Channel")
-			dhcp.SendDHCPDiscovery(o.PonPortID, o.ID, o.Sn(), o.InternalState, o.HwAddress, o.CTag, stream)
+			// FIXME use id, ponId as SendEapStart
+			dhcp.SendDHCPDiscovery(o.PonPortID, o.ID, o.Sn(), o.PortNo, o.InternalState, o.HwAddress, o.CTag, stream)
 		case OnuPacketOut:
 			msg, _ := message.Data.(OnuPacketOutMessage)
 			pkt := msg.Packet
 			etherType := pkt.Layer(layers.LayerTypeEthernet).(*layers.Ethernet).EthernetType
 
 			if etherType == layers.EthernetTypeEAPOL {
-				eapol.HandleNextPacket(msg.OnuId, msg.IntfId, o.Sn(), o.InternalState, msg.Packet, stream)
+				eapol.HandleNextPacket(msg.OnuId, msg.IntfId, o.Sn(), o.PortNo, o.InternalState, msg.Packet, stream)
 			} else if packetHandlers.IsDhcpPacket(pkt) {
 				// NOTE here we receive packets going from the DHCP Server to the ONU
 				// for now we expect them to be double-tagged, but ideally the should be single tagged
-				dhcp.HandleNextPacket(o.ID, o.PonPortID, o.Sn(), o.HwAddress, o.CTag, o.InternalState, msg.Packet, stream)
+				dhcp.HandleNextPacket(o.ID, o.PonPortID, o.Sn(), o.PortNo, o.HwAddress, o.CTag, o.InternalState, msg.Packet, stream)
 			}
 		case DyingGaspIndication:
 			msg, _ := message.Data.(DyingGaspIndicationMessage)
@@ -349,14 +354,20 @@
 
 	onuLogger.WithFields(log.Fields{
 		"IntfId":       o.PonPortID,
-		"SerialNumber": o.SerialNumber,
+		"SerialNumber": o.Sn(),
 		"omciPacket":   msg.omciMsg.Pkt,
 	}).Tracef("Received OMCI message")
 
 	var omciInd openolt.OmciIndication
 	respPkt, err := omci.OmciSim(o.PonPortID, o.ID, HexDecode(msg.omciMsg.Pkt))
 	if err != nil {
-		onuLogger.Errorf("Error handling OMCI message %v", msg)
+		onuLogger.WithFields(log.Fields{
+			"IntfId":       o.PonPortID,
+			"SerialNumber": o.Sn(),
+			"omciPacket":   omciInd.Pkt,
+			"msg":          msg,
+		}).Errorf("Error handling OMCI message %v", msg)
+		return
 	}
 
 	omciInd.IntfId = o.PonPortID
@@ -365,16 +376,34 @@
 
 	omci := &openolt.Indication_OmciInd{OmciInd: &omciInd}
 	if err := stream.Send(&openolt.Indication{Data: omci}); err != nil {
-		onuLogger.Errorf("send omci indication failed: %v", err)
+		onuLogger.WithFields(log.Fields{
+			"IntfId":       o.PonPortID,
+			"SerialNumber": o.Sn(),
+			"omciPacket":   omciInd.Pkt,
+			"msg":          msg,
+		}).Errorf("send omci indication failed: %v", err)
+		return
 	}
 	onuLogger.WithFields(log.Fields{
 		"IntfId":       o.PonPortID,
-		"SerialNumber": o.SerialNumber,
+		"SerialNumber": o.Sn(),
 		"omciPacket":   omciInd.Pkt,
 	}).Tracef("Sent OMCI message")
 }
 
-func (o Onu) handleFlowUpdate(msg OnuFlowUpdateMessage, stream openolt.Openolt_EnableIndicationServer) {
+func (o *Onu) storePortNumber(portNo uint32) {
+	// FIXME this is a workaround to always use the SN-1 entry in sadis,
+	// we need to add support for multiple UNIs
+	// the action plan is:
+	// - refactor the omci-sim library to use https://github.com/cboling/omci instead of canned messages
+	// - change the library so that it reports a single UNI and remove this workaroung
+	// - add support for multiple UNIs in BBSim
+	if portNo < o.PortNo {
+		o.PortNo = portNo
+	}
+}
+
+func (o *Onu) handleFlowUpdate(msg OnuFlowUpdateMessage, stream openolt.Openolt_EnableIndicationServer) {
 	onuLogger.WithFields(log.Fields{
 		"DstPort":   msg.Flow.Classifier.DstPort,
 		"EthType":   fmt.Sprintf("%x", msg.Flow.Classifier.EthType),
@@ -392,6 +421,11 @@
 	}).Debug("ONU receives Flow")
 
 	if msg.Flow.Classifier.EthType == uint32(layers.EthernetTypeEAPOL) && msg.Flow.Classifier.OVid == 4091 {
+		// NOTE storing the PortNO, it's needed when sending PacketIndications
+		if o.PortNo == 0 {
+			o.storePortNumber(uint32(msg.Flow.PortNo))
+		}
+
 		// NOTE if we receive the EAPOL flows but we don't have GemPorts
 		// go an intermediate state, otherwise start auth
 		if o.InternalState.Is("enabled") {
diff --git a/internal/bbsim/devices/pon.go b/internal/bbsim/devices/pon.go
index 0e67e51..374b1b9 100644
--- a/internal/bbsim/devices/pon.go
+++ b/internal/bbsim/devices/pon.go
@@ -28,7 +28,7 @@
 	// BBSIM Internals
 	ID     uint32
 	NumOnu int
-	Onus   []Onu
+	Onus   []*Onu
 	Olt    OltDevice
 
 	// PON Attributes
@@ -41,7 +41,7 @@
 func (p PonPort) getOnuBySn(sn *openolt.SerialNumber) (*Onu, error) {
 	for _, onu := range p.Onus {
 		if bytes.Equal(onu.SerialNumber.VendorSpecific, sn.VendorSpecific) {
-			return &onu, nil
+			return onu, nil
 		}
 	}
 	return nil, errors.New(fmt.Sprintf("Cannot find Onu with serial number %d in PonPort %d", sn, p.ID))
@@ -50,7 +50,7 @@
 func (p PonPort) getOnuById(id uint32) (*Onu, error) {
 	for _, onu := range p.Onus {
 		if onu.ID == id {
-			return &onu, nil
+			return onu, nil
 		}
 	}
 	return nil, errors.New(fmt.Sprintf("Cannot find Onu with id %d in PonPort %d", id, p.ID))
diff --git a/internal/bbsim/responders/dhcp/dhcp.go b/internal/bbsim/responders/dhcp/dhcp.go
index 64e83dd..c7ae75c 100644
--- a/internal/bbsim/responders/dhcp/dhcp.go
+++ b/internal/bbsim/responders/dhcp/dhcp.go
@@ -194,7 +194,7 @@
 	return 0, errors.New("Failed to extract MsgType from dhcp")
 }
 
-func sendDHCPPktIn(msg bbsim.ByteMsg, stream openolt.Openolt_EnableIndicationServer) error {
+func sendDHCPPktIn(msg bbsim.ByteMsg, portNo uint32, stream bbsim.Stream) error {
 	// FIXME unify sendDHCPPktIn and sendEapolPktIn methods
 	gemid, err := GetGemPortId(msg.IntfId, msg.OnuId)
 	if err != nil {
@@ -205,7 +205,11 @@
 		return err
 	}
 	data := &openolt.Indication_PktInd{PktInd: &openolt.PacketIndication{
-		IntfType: "pon", IntfId: msg.IntfId, GemportId: uint32(gemid), Pkt: msg.Bytes,
+		IntfType:  "pon",
+		IntfId:    msg.IntfId,
+		GemportId: uint32(gemid),
+		Pkt:       msg.Bytes,
+		PortNo:    portNo,
 	}}
 
 	if err := stream.Send(&openolt.Indication{Data: data}); err != nil {
@@ -215,7 +219,7 @@
 	return nil
 }
 
-func sendDHCPRequest(ponPortId uint32, onuId uint32, serialNumber string, onuHwAddress net.HardwareAddr, cTag int, stream openolt.Openolt_EnableIndicationServer) error {
+func sendDHCPRequest(ponPortId uint32, onuId uint32, serialNumber string, portNo uint32, onuHwAddress net.HardwareAddr, cTag int, stream openolt.Openolt_EnableIndicationServer) error {
 	dhcp := createDHCPReq(ponPortId, onuId)
 	pkt, err := serializeDHCPPacket(ponPortId, onuId, onuHwAddress, dhcp)
 
@@ -232,7 +236,7 @@
 		Bytes:  pkt,
 	}
 
-	if err := sendDHCPPktIn(msg, stream); err != nil {
+	if err := sendDHCPPktIn(msg, portNo, stream); err != nil {
 		return err
 	}
 	dhcpLogger.WithFields(log.Fields{
@@ -255,7 +259,7 @@
 	return nil
 }
 
-func SendDHCPDiscovery(ponPortId uint32, onuId uint32, serialNumber string, onuStateMachine *fsm.FSM, onuHwAddress net.HardwareAddr, cTag int, stream openolt.Openolt_EnableIndicationServer) error {
+func SendDHCPDiscovery(ponPortId uint32, onuId uint32, serialNumber string, portNo uint32, onuStateMachine *fsm.FSM, onuHwAddress net.HardwareAddr, cTag int, stream bbsim.Stream) error {
 	dhcp := createDHCPDisc(ponPortId, onuId)
 	pkt, err := serializeDHCPPacket(ponPortId, onuId, onuHwAddress, dhcp)
 	if err != nil {
@@ -271,7 +275,7 @@
 		Bytes:  pkt,
 	}
 
-	if err := sendDHCPPktIn(msg, stream); err != nil {
+	if err := sendDHCPPktIn(msg, portNo, stream); err != nil {
 		if err := updateDhcpFailed(onuId, ponPortId, serialNumber, onuStateMachine); err != nil {
 			return err
 		}
@@ -293,7 +297,7 @@
 	return nil
 }
 
-func HandleNextPacket(onuId uint32, ponPortId uint32, serialNumber string, onuHwAddress net.HardwareAddr, cTag int, onuStateMachine *fsm.FSM, pkt gopacket.Packet, stream openolt.Openolt_EnableIndicationServer) error {
+func HandleNextPacket(onuId uint32, ponPortId uint32, serialNumber string, portNo uint32, onuHwAddress net.HardwareAddr, cTag int, onuStateMachine *fsm.FSM, pkt gopacket.Packet, stream openolt.Openolt_EnableIndicationServer) error {
 
 	dhcpLayer, err := getDhcpLayer(pkt)
 	if err != nil {
@@ -322,7 +326,7 @@
 
 	if dhcpLayer.Operation == layers.DHCPOpReply {
 		if dhcpMessageType == layers.DHCPMsgTypeOffer {
-			if err := sendDHCPRequest(ponPortId, onuId, serialNumber, onuHwAddress, cTag, stream); err != nil {
+			if err := sendDHCPRequest(ponPortId, onuId, serialNumber, portNo, onuHwAddress, cTag, stream); err != nil {
 				dhcpLogger.WithFields(log.Fields{
 					"OnuId":  onuId,
 					"IntfId": ponPortId,
diff --git a/internal/bbsim/responders/dhcp/dhcp_test.go b/internal/bbsim/responders/dhcp/dhcp_test.go
index 0733cc0..6aa8060 100644
--- a/internal/bbsim/responders/dhcp/dhcp_test.go
+++ b/internal/bbsim/responders/dhcp/dhcp_test.go
@@ -27,7 +27,6 @@
 )
 
 // MOCKS
-var calledSend = 0
 
 var dhcpStateMachine = fsm.NewFSM(
 	"dhcp_started",
@@ -42,49 +41,55 @@
 
 type mockStreamSuccess struct {
 	grpc.ServerStream
+	CallCount int
+	Calls     map[int]*openolt.PacketIndication
+	fail      bool
 }
 
-func (s mockStreamSuccess) Send(ind *openolt.Indication) error {
-	calledSend++
+func (s *mockStreamSuccess) Send(ind *openolt.Indication) error {
+	s.CallCount++
+	if s.fail {
+		return errors.New("fake-error")
+	}
+	s.Calls[s.CallCount] = ind.GetPktInd()
 	return nil
 }
 
-type mockStreamError struct {
-	grpc.ServerStream
-}
-
-func (s mockStreamError) Send(ind *openolt.Indication) error {
-	calledSend++
-	return errors.New("stream-error")
-}
-
 // TESTS
 
 func TestSendDHCPDiscovery(t *testing.T) {
-	calledSend = 0
 	dhcpStateMachine.SetState("dhcp_started")
 
+	var onuId uint32 = 1
+	var gemPortId uint16 = 1
+	var ponPortId uint32 = 0
+	var serialNumber = "BBSM00000001"
+	var mac = net.HardwareAddr{0x2e, 0x60, 0x70, 0x13, byte(ponPortId), byte(onuId)}
+	var portNo uint32 = 16
+
 	// Save current function and restore at the end:
 	old := GetGemPortId
 	defer func() { GetGemPortId = old }()
 
 	GetGemPortId = func(intfId uint32, onuId uint32) (uint16, error) {
-		return 1, nil
+		return gemPortId, nil
 	}
 
-	var onuId uint32 = 1
-	var ponPortId uint32 = 0
-	var serialNumber string = "BBSM00000001"
-	var mac = net.HardwareAddr{0x2e, 0x60, 0x70, 0x13, byte(ponPortId), byte(onuId)}
+	stream := &mockStreamSuccess{
+		Calls: make(map[int]*openolt.PacketIndication),
+		fail:  false,
+	}
 
-	stream := mockStreamSuccess{}
-
-	if err := SendDHCPDiscovery(ponPortId, onuId, serialNumber, dhcpStateMachine, mac, 1, stream); err != nil {
+	if err := SendDHCPDiscovery(ponPortId, onuId, serialNumber, portNo, dhcpStateMachine, mac, 1, stream); err != nil {
 		t.Errorf("SendDHCPDiscovery returned an error: %v", err)
 		t.Fail()
 	}
 
-	assert.Equal(t, calledSend, 1)
+	assert.Equal(t, stream.CallCount, 1)
+	assert.Equal(t, stream.Calls[1].PortNo, portNo)
+	assert.Equal(t, stream.Calls[1].IntfId, ponPortId)
+	assert.Equal(t, stream.Calls[1].IntfType, "pon")
+	assert.Equal(t, stream.Calls[1].GemportId, uint32(gemPortId))
 
 	assert.Equal(t, dhcpStateMachine.Current(), "dhcp_discovery_sent")
 }
diff --git a/internal/bbsim/responders/eapol/eapol.go b/internal/bbsim/responders/eapol/eapol.go
index 6f2642e..f31a068 100644
--- a/internal/bbsim/responders/eapol/eapol.go
+++ b/internal/bbsim/responders/eapol/eapol.go
@@ -36,7 +36,7 @@
 var eapolVersion uint8 = 1
 var GetGemPortId = omci.GetGemPortId
 
-func sendEapolPktIn(msg bbsim.ByteMsg, stream openolt.Openolt_EnableIndicationServer) {
+func sendEapolPktIn(msg bbsim.ByteMsg, portNo uint32, stream openolt.Openolt_EnableIndicationServer) {
 	// FIXME unify sendDHCPPktIn and sendEapolPktIn methods
 	gemid, err := omci.GetGemPortId(msg.IntfId, msg.OnuId)
 	if err != nil {
@@ -47,7 +47,11 @@
 		return
 	}
 	data := &openolt.Indication_PktInd{PktInd: &openolt.PacketIndication{
-		IntfType: "pon", IntfId: msg.IntfId, GemportId: uint32(gemid), Pkt: msg.Bytes,
+		IntfType:  "pon",
+		IntfId:    msg.IntfId,
+		GemportId: uint32(gemid),
+		Pkt:       msg.Bytes,
+		PortNo:    portNo,
 	}}
 
 	if err := stream.Send(&openolt.Indication{Data: data}); err != nil {
@@ -129,7 +133,7 @@
 	return nil
 }
 
-func SendEapStart(onuId uint32, ponPortId uint32, serialNumber string, macAddress net.HardwareAddr, onuStateMachine *fsm.FSM, stream openolt.Openolt_EnableIndicationServer) error {
+func SendEapStart(onuId uint32, ponPortId uint32, serialNumber string, portNo uint32, macAddress net.HardwareAddr, onuStateMachine *fsm.FSM, stream bbsim.Stream) error {
 
 	// send the packet (hacked together)
 	gemId, err := GetGemPortId(ponPortId, onuId)
@@ -170,6 +174,7 @@
 			IntfId:    ponPortId,
 			GemportId: uint32(gemId),
 			Pkt:       msg,
+			PortNo:    portNo,
 		},
 	}
 
@@ -187,9 +192,12 @@
 		return err
 	}
 
-	if err != nil {
-
-	}
+	eapolLogger.WithFields(log.Fields{
+		"OnuId":  onuId,
+		"IntfId": ponPortId,
+		"OnuSn":  serialNumber,
+		"PortNo": portNo,
+	}).Debugf("Sent EapStart packet")
 
 	if err := onuStateMachine.Event("eap_start_sent"); err != nil {
 		eapolLogger.WithFields(log.Fields{
@@ -202,12 +210,21 @@
 	return nil
 }
 
-func HandleNextPacket(onuId uint32, ponPortId uint32, serialNumber string, onuStateMachine *fsm.FSM, recvpkt gopacket.Packet, stream openolt.Openolt_EnableIndicationServer) {
+func HandleNextPacket(onuId uint32, ponPortId uint32, serialNumber string, portNo uint32, onuStateMachine *fsm.FSM, recvpkt gopacket.Packet, stream openolt.Openolt_EnableIndicationServer) {
+
 	eap, err := extractEAP(recvpkt)
 	if err != nil {
 		eapolLogger.Errorf("%s", err)
 	}
 
+	log.WithFields(log.Fields{
+		"eap.Code": eap.Code,
+		"eap.Type": eap.Type,
+		"OnuId":    onuId,
+		"IntfId":   ponPortId,
+		"OnuSn":    serialNumber,
+	}).Tracef("HandleNextPacket")
+
 	if eap.Code == layers.EAPCodeRequest && eap.Type == layers.EAPTypeIdentity {
 		reseap := createEAPIdentityResponse(eap.Id)
 		pkt := createEAPOLPkt(reseap, onuId, ponPortId)
@@ -218,12 +235,13 @@
 			Bytes:  pkt,
 		}
 
-		sendEapolPktIn(msg, stream)
+		sendEapolPktIn(msg, portNo, stream)
 		eapolLogger.WithFields(log.Fields{
 			"OnuId":  onuId,
 			"IntfId": ponPortId,
 			"OnuSn":  serialNumber,
-		}).Infof("Sent EAPIdentityResponse packet")
+			"PortNo": portNo,
+		}).Debugf("Sent EAPIdentityResponse packet")
 		if err := onuStateMachine.Event("eap_response_identity_sent"); err != nil {
 			eapolLogger.WithFields(log.Fields{
 				"OnuId":  onuId,
@@ -244,12 +262,13 @@
 			Bytes:  pkt,
 		}
 
-		sendEapolPktIn(msg, stream)
+		sendEapolPktIn(msg, portNo, stream)
 		eapolLogger.WithFields(log.Fields{
 			"OnuId":  onuId,
 			"IntfId": ponPortId,
 			"OnuSn":  serialNumber,
-		}).Infof("Sent EAPChallengeResponse packet")
+			"PortNo": portNo,
+		}).Debugf("Sent EAPChallengeResponse packet")
 		if err := onuStateMachine.Event("eap_response_challenge_sent"); err != nil {
 			eapolLogger.WithFields(log.Fields{
 				"OnuId":  onuId,
@@ -262,7 +281,8 @@
 			"OnuId":  onuId,
 			"IntfId": ponPortId,
 			"OnuSn":  serialNumber,
-		}).Infof("Received EAPSuccess packet")
+			"PortNo": portNo,
+		}).Debugf("Received EAPSuccess packet")
 		if err := onuStateMachine.Event("eap_response_success_received"); err != nil {
 			eapolLogger.WithFields(log.Fields{
 				"OnuId":  onuId,
diff --git a/internal/bbsim/responders/eapol/eapol_test.go b/internal/bbsim/responders/eapol/eapol_test.go
index 6a80317..f32c6f8 100644
--- a/internal/bbsim/responders/eapol/eapol_test.go
+++ b/internal/bbsim/responders/eapol/eapol_test.go
@@ -27,7 +27,6 @@
 )
 
 // MOCKS
-var calledSend = 0
 
 var eapolStateMachine = fsm.NewFSM(
 	"auth_started",
@@ -41,28 +40,33 @@
 	fsm.Callbacks{},
 )
 
-type mockStreamSuccess struct {
+// params for the function under test
+var onuId uint32 = 1
+var gemPortId uint16 = 1
+var ponPortId uint32 = 0
+var serialNumber string = "BBSM00000001"
+var macAddress = net.HardwareAddr{0x01, 0x80, 0xC2, 0x00, 0x00, 0x03}
+var portNo uint32 = 16
+
+type mockStream struct {
 	grpc.ServerStream
+	CallCount int
+	Calls     map[int]*openolt.PacketIndication
+	fail      bool
 }
 
-func (s mockStreamSuccess) Send(ind *openolt.Indication) error {
-	calledSend++
+func (s *mockStream) Send(ind *openolt.Indication) error {
+	s.CallCount++
+	if s.fail {
+		return errors.New("fake-error")
+	}
+	s.Calls[s.CallCount] = ind.GetPktInd()
 	return nil
 }
 
-type mockStreamError struct {
-	grpc.ServerStream
-}
-
-func (s mockStreamError) Send(ind *openolt.Indication) error {
-	calledSend++
-	return errors.New("stream-error")
-}
-
 // TESTS
 
 func TestSendEapStartSuccess(t *testing.T) {
-	calledSend = 0
 	eapolStateMachine.SetState("auth_started")
 
 	// Save current function and restore at the end:
@@ -70,31 +74,30 @@
 	defer func() { GetGemPortId = old }()
 
 	GetGemPortId = func(intfId uint32, onuId uint32) (uint16, error) {
-		return 1, nil
+		return gemPortId, nil
 	}
 
-	// params for the function under test
-	var onuId uint32 = 1
-	var ponPortId uint32 = 0
-	var serialNumber string = "BBSM00000001"
+	stream := &mockStream{
+		Calls: make(map[int]*openolt.PacketIndication),
+		fail:  false,
+	}
 
-	var macAddress = net.HardwareAddr{0x01, 0x80, 0xC2, 0x00, 0x00, 0x03}
-
-	stream := mockStreamSuccess{}
-
-	if err := SendEapStart(onuId, ponPortId, serialNumber, macAddress, eapolStateMachine, stream); err != nil {
+	if err := SendEapStart(onuId, ponPortId, serialNumber, portNo, macAddress, eapolStateMachine, stream); err != nil {
 		t.Errorf("SendEapStart returned an error: %v", err)
 		t.Fail()
 	}
 
-	assert.Equal(t, calledSend, 1)
+	assert.Equal(t, stream.CallCount, 1)
+	assert.Equal(t, stream.Calls[1].PortNo, portNo)
+	assert.Equal(t, stream.Calls[1].IntfId, ponPortId)
+	assert.Equal(t, stream.Calls[1].IntfType, "pon")
+	assert.Equal(t, stream.Calls[1].GemportId, uint32(gemPortId))
 
 	assert.Equal(t, eapolStateMachine.Current(), "eap_start_sent")
 
 }
 
 func TestSendEapStartFailNoGemPort(t *testing.T) {
-	calledSend = 0
 	eapolStateMachine.SetState("auth_started")
 
 	// Save current function and restore at the end:
@@ -105,16 +108,14 @@
 		return 0, errors.New("no-gem-port")
 	}
 
-	// params for the function under test
-	var onuId uint32 = 1
-	var ponPortId uint32 = 0
-	var serialNumber string = "BBSM00000001"
-
 	var macAddress = net.HardwareAddr{0x01, 0x80, 0xC2, 0x00, 0x00, 0x03}
 
-	stream := mockStreamSuccess{}
+	stream := &mockStream{
+		Calls: make(map[int]*openolt.PacketIndication),
+		fail:  false,
+	}
 
-	err := SendEapStart(onuId, ponPortId, serialNumber, macAddress, eapolStateMachine, stream)
+	err := SendEapStart(onuId, ponPortId, serialNumber, portNo, macAddress, eapolStateMachine, stream)
 	if err == nil {
 		t.Errorf("SendEapStart did not return an error")
 		t.Fail()
@@ -126,7 +127,7 @@
 }
 
 func TestSendEapStartFailStreamError(t *testing.T) {
-	calledSend = 0
+
 	eapolStateMachine.SetState("auth_started")
 
 	// Save current function and restore at the end:
@@ -137,21 +138,18 @@
 		return 1, nil
 	}
 
-	// params for the function under test
-	var onuId uint32 = 1
-	var ponPortId uint32 = 0
-	var serialNumber = "BBSM00000001"
-	var macAddress = net.HardwareAddr{0x01, 0x80, 0xC2, 0x00, 0x00, 0x03}
+	stream := &mockStream{
+		Calls: make(map[int]*openolt.PacketIndication),
+		fail:  true,
+	}
 
-	stream := mockStreamError{}
-
-	err := SendEapStart(onuId, ponPortId, serialNumber, macAddress, eapolStateMachine, stream)
+	err := SendEapStart(onuId, ponPortId, serialNumber, portNo, macAddress, eapolStateMachine, stream)
 	if err == nil {
 		t.Errorf("SendEapStart did not return an error")
 		t.Fail()
 	}
 
-	assert.Equal(t, err.Error(), "stream-error")
+	assert.Equal(t, err.Error(), "fake-error")
 
 	assert.Equal(t, eapolStateMachine.Current(), "auth_failed")
 }
diff --git a/internal/bbsim/types/interfaces.go b/internal/bbsim/types/interfaces.go
new file mode 100644
index 0000000..c408697
--- /dev/null
+++ b/internal/bbsim/types/interfaces.go
@@ -0,0 +1,24 @@
+/*
+ * 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 types
+
+import "github.com/opencord/voltha-protos/go/openolt"
+
+// represent a gRPC stream
+type Stream interface {
+	Send(*openolt.Indication) error
+}
diff --git a/internal/bbsim/types/types.go b/internal/bbsim/types/types.go
index aa3740d..e5d75f0 100644
--- a/internal/bbsim/types/types.go
+++ b/internal/bbsim/types/types.go
@@ -20,7 +20,7 @@
 	"github.com/google/gopacket"
 )
 
-// deprecated
+// deprecated, use OnuPacketOutMessage instead
 type ByteMsg struct {
 	IntfId uint32
 	OnuId  uint32
diff --git a/internal/bbsimctl/commands/onu.go b/internal/bbsimctl/commands/onu.go
index 8fe4ea8..4f222f2 100644
--- a/internal/bbsimctl/commands/onu.go
+++ b/internal/bbsimctl/commands/onu.go
@@ -31,7 +31,7 @@
 )
 
 const (
-	DEFAULT_ONU_DEVICE_HEADER_FORMAT = "table{{ .PonPortID }}\t{{ .ID }}\t{{ .SerialNumber }}\t{{ .STag }}\t{{ .CTag }}\t{{ .OperState }}\t{{ .InternalState }}"
+	DEFAULT_ONU_DEVICE_HEADER_FORMAT = "table{{ .PonPortID }}\t{{ .ID }}\t{{ .PortNo }}\t{{ .SerialNumber }}\t{{ .STag }}\t{{ .CTag }}\t{{ .OperState }}\t{{ .InternalState }}"
 )
 
 type OnuSnString string