[SEBA-836] BBSim Reflector

Change-Id: Ib4ae5a2c24880dc62209bebb81188eca5f57865d
diff --git a/internal/bbsim/devices/onu.go b/internal/bbsim/devices/onu.go
index 7d36bfd..f4ef84b 100644
--- a/internal/bbsim/devices/onu.go
+++ b/internal/bbsim/devices/onu.go
@@ -17,13 +17,17 @@
 package devices
 
 import (
+	"context"
 	"fmt"
+	"github.com/cboling/omci"
 	"github.com/google/gopacket/layers"
 	"github.com/looplab/fsm"
 	"github.com/opencord/bbsim/internal/bbsim/packetHandlers"
 	"github.com/opencord/bbsim/internal/bbsim/responders/dhcp"
 	"github.com/opencord/bbsim/internal/bbsim/responders/eapol"
-	omci "github.com/opencord/omci-sim"
+	"github.com/opencord/bbsim/internal/common"
+	omcilib "github.com/opencord/bbsim/internal/common/omci"
+	omcisim "github.com/opencord/omci-sim"
 	"github.com/opencord/voltha-protos/go/openolt"
 	log "github.com/sirupsen/logrus"
 	"net"
@@ -50,23 +54,35 @@
 	SerialNumber *openolt.SerialNumber
 
 	Channel chan Message // this Channel is to track state changes OMCI messages, EAPOL and DHCP packets
+
+	// OMCI params
+	tid        uint16
+	hpTid      uint16
+	seqNumber  uint16
+	HasGemPort bool
+
+	DoneChannel chan bool // this channel is used to signal once the onu is complete (when the struct is used by BBR)
 }
 
 func (o Onu) Sn() string {
-	return onuSnToString(o.SerialNumber)
+	return common.OnuSnToString(o.SerialNumber)
 }
 
 func CreateONU(olt OltDevice, pon PonPort, id uint32, sTag int, cTag int) *Onu {
 
 	o := Onu{
-		ID:        id,
-		PonPortID: pon.ID,
-		PonPort:   pon,
-		STag:      sTag,
-		CTag:      cTag,
-		HwAddress: net.HardwareAddr{0x2e, 0x60, 0x70, 0x13, byte(pon.ID), byte(id)},
-		PortNo:    0,
-		Channel:   make(chan Message, 2048),
+		ID:          id,
+		PonPortID:   pon.ID,
+		PonPort:     pon,
+		STag:        sTag,
+		CTag:        cTag,
+		HwAddress:   net.HardwareAddr{0x2e, 0x60, 0x70, 0x13, byte(pon.ID), byte(id)},
+		PortNo:      0,
+		Channel:     make(chan Message, 2048),
+		tid:         0x1,
+		hpTid:       0x8000,
+		seqNumber:   0,
+		DoneChannel: make(chan bool, 1),
 	}
 	o.SerialNumber = o.NewSN(olt.ID, pon.ID, o.ID)
 
@@ -102,6 +118,10 @@
 			{Name: "dhcp_request_sent", Src: []string{"dhcp_discovery_sent"}, Dst: "dhcp_request_sent"},
 			{Name: "dhcp_ack_received", Src: []string{"dhcp_request_sent"}, Dst: "dhcp_ack_received"},
 			{Name: "dhcp_failed", Src: []string{"dhcp_started", "dhcp_discovery_sent", "dhcp_request_sent"}, Dst: "dhcp_failed"},
+			// BBR States
+			// TODO add start OMCI state
+			{Name: "send_eapol_flow", Src: []string{"created"}, Dst: "eapol_flow_sent"},
+			{Name: "send_dhcp_flow", Src: []string{"eapol_flow_sent"}, Dst: "dhcp_flow_sent"},
 		},
 		fsm.Callbacks{
 			"enter_state": func(e *fsm.Event) {
@@ -164,6 +184,18 @@
 					"OnuSn":  o.Sn(),
 				}).Errorf("ONU failed to DHCP!")
 			},
+			"enter_eapol_flow_sent": func(e *fsm.Event) {
+				msg := Message{
+					Type: SendEapolFlow,
+				}
+				o.Channel <- msg
+			},
+			"enter_dhcp_flow_sent": func(e *fsm.Event) {
+				msg := Message{
+					Type: SendDhcpFlow,
+				}
+				o.Channel <- msg
+			},
 		},
 	)
 	return &o
@@ -177,7 +209,7 @@
 	}).Debugf("Changing ONU InternalState from %s to %s", src, dst)
 }
 
-func (o Onu) processOnuMessages(stream openolt.Openolt_EnableIndicationServer) {
+func (o Onu) ProcessOnuMessages(stream openolt.Openolt_EnableIndicationServer, client openolt.OpenoltClient) {
 	onuLogger.WithFields(log.Fields{
 		"onuID": o.ID,
 		"onuSN": o.Sn(),
@@ -211,20 +243,51 @@
 			// 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.PortNo, o.InternalState, msg.Packet, stream)
-			} else if packetHandlers.IsDhcpPacket(pkt) {
+			msg, _ := message.Data.(OnuPacketMessage)
+
+			log.WithFields(log.Fields{
+				"IntfId":  msg.IntfId,
+				"OnuId":   msg.OnuId,
+				"pktType": msg.Type,
+			}).Trace("Received OnuPacketOut Message")
+
+			if msg.Type == packetHandlers.EAPOL {
+				eapol.HandleNextPacket(msg.OnuId, msg.IntfId, o.Sn(), o.PortNo, o.InternalState, msg.Packet, stream, client)
+			} else if msg.Type == packetHandlers.DHCP {
 				// 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.PortNo, o.HwAddress, o.CTag, o.InternalState, msg.Packet, stream)
 			}
+		case OnuPacketIn:
+			// NOTE we only receive BBR packets here.
+			// Eapol.HandleNextPacket can handle both BBSim and BBr cases so the call is the same
+			// in the DHCP case VOLTHA only act as a proxy, the behaviour is completely different thus we have a dhcp.HandleNextBbrPacket
+			msg, _ := message.Data.(OnuPacketMessage)
+
+			log.WithFields(log.Fields{
+				"IntfId":  msg.IntfId,
+				"OnuId":   msg.OnuId,
+				"pktType": msg.Type,
+			}).Trace("Received OnuPacketIn Message")
+
+			if msg.Type == packetHandlers.EAPOL {
+				eapol.HandleNextPacket(msg.OnuId, msg.IntfId, o.Sn(), o.PortNo, o.InternalState, msg.Packet, stream, client)
+			} else if msg.Type == packetHandlers.DHCP {
+				dhcp.HandleNextBbrPacket(o.ID, o.PonPortID, o.Sn(), o.STag, o.HwAddress, o.DoneChannel, msg.Packet, client)
+			}
 		case DyingGaspIndication:
 			msg, _ := message.Data.(DyingGaspIndicationMessage)
 			o.sendDyingGaspInd(msg, stream)
+		case OmciIndication:
+			// TODO handle me!
+			// here https://gerrit.opencord.org/#/c/15521/11/internal/bbr/onu.go in startOmci
+			msg, _ := message.Data.(OmciIndicationMessage)
+			o.handleOmci(msg, client)
+		case SendEapolFlow:
+			o.sendEapolFlow(client)
+		case SendDhcpFlow:
+			o.sendDhcpFlow(client)
 		default:
 			onuLogger.Warnf("Received unknown message data %v for type %v in OLT Channel", message.Data, message.Type)
 		}
@@ -232,7 +295,7 @@
 }
 
 func (o Onu) processOmciMessages(stream openolt.Openolt_EnableIndicationServer) {
-	ch := omci.GetChannel()
+	ch := omcisim.GetChannel()
 
 	onuLogger.WithFields(log.Fields{
 		"onuID": o.ID,
@@ -241,7 +304,7 @@
 
 	for message := range ch {
 		switch message.Type {
-		case omci.GemPortAdded:
+		case omcisim.GemPortAdded:
 			log.WithFields(log.Fields{
 				"OnuId":  message.Data.OnuId,
 				"IntfId": message.Data.IntfId,
@@ -359,7 +422,7 @@
 	}).Tracef("Received OMCI message")
 
 	var omciInd openolt.OmciIndication
-	respPkt, err := omci.OmciSim(o.PonPortID, o.ID, HexDecode(msg.omciMsg.Pkt))
+	respPkt, err := omcisim.OmciSim(o.PonPortID, o.ID, HexDecode(msg.omciMsg.Pkt))
 	if err != nil {
 		onuLogger.WithFields(log.Fields{
 			"IntfId":       o.PonPortID,
@@ -381,7 +444,7 @@
 			"SerialNumber": o.Sn(),
 			"omciPacket":   omciInd.Pkt,
 			"msg":          msg,
-		}).Errorf("send omci indication failed: %v", err)
+		}).Errorf("send omcisim indication failed: %v", err)
 		return
 	}
 	onuLogger.WithFields(log.Fields{
@@ -395,10 +458,10 @@
 	// 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
+	// - refactor the omcisim-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 {
+	if o.PortNo == 0 || portNo < o.PortNo {
 		o.PortNo = portNo
 	}
 }
@@ -459,3 +522,193 @@
 	onuLogger.Tracef("Omci decoded: %x.", p)
 	return p
 }
+
+// BBR methods
+
+func sendOmciMsg(pktBytes []byte, intfId uint32, onuId uint32, sn *openolt.SerialNumber, msgType string, client openolt.OpenoltClient) {
+	omciMsg := openolt.OmciMsg{
+		IntfId: intfId,
+		OnuId:  onuId,
+		Pkt:    pktBytes,
+	}
+
+	if _, err := client.OmciMsgOut(context.Background(), &omciMsg); err != nil {
+		log.WithFields(log.Fields{
+			"IntfId":       intfId,
+			"OnuId":        onuId,
+			"SerialNumber": common.OnuSnToString(sn),
+			"Pkt":          omciMsg.Pkt,
+		}).Fatalf("Failed to send MIB Reset")
+	}
+	log.WithFields(log.Fields{
+		"IntfId":       intfId,
+		"OnuId":        onuId,
+		"SerialNumber": common.OnuSnToString(sn),
+		"Pkt":          omciMsg.Pkt,
+	}).Tracef("Sent OMCI message %s", msgType)
+}
+
+func (onu *Onu) getNextTid(highPriority ...bool) uint16 {
+	var next uint16
+	if len(highPriority) > 0 && highPriority[0] {
+		next = onu.hpTid
+		onu.hpTid += 1
+		if onu.hpTid < 0x8000 {
+			onu.hpTid = 0x8000
+		}
+	} else {
+		next = onu.tid
+		onu.tid += 1
+		if onu.tid >= 0x8000 {
+			onu.tid = 1
+		}
+	}
+	return next
+}
+
+// TODO move this method in responders/omcisim
+func (o *Onu) StartOmci(client openolt.OpenoltClient) {
+	mibReset, _ := omcilib.CreateMibResetRequest(o.getNextTid(false))
+	sendOmciMsg(mibReset, o.PonPortID, o.ID, o.SerialNumber, "mibReset", client)
+}
+
+func (o *Onu) handleOmci(msg OmciIndicationMessage, client openolt.OpenoltClient) {
+	msgType, packet := omcilib.DecodeOmci(msg.OmciInd.Pkt)
+
+	log.WithFields(log.Fields{
+		"IntfId":  msg.OmciInd.IntfId,
+		"OnuId":   msg.OmciInd.OnuId,
+		"OnuSn":   common.OnuSnToString(o.SerialNumber),
+		"Pkt":     msg.OmciInd.Pkt,
+		"msgType": msgType,
+	}).Trace("ONU Receveives OMCI Msg")
+	switch msgType {
+	default:
+		log.Fatalf("unexpected frame: %v", packet)
+	case omci.MibResetResponseType:
+		mibUpload, _ := omcilib.CreateMibUploadRequest(o.getNextTid(false))
+		sendOmciMsg(mibUpload, o.PonPortID, o.ID, o.SerialNumber, "mibUpload", client)
+	case omci.MibUploadResponseType:
+		mibUploadNext, _ := omcilib.CreateMibUploadNextRequest(o.getNextTid(false), o.seqNumber)
+		sendOmciMsg(mibUploadNext, o.PonPortID, o.ID, o.SerialNumber, "mibUploadNext", client)
+	case omci.MibUploadNextResponseType:
+		o.seqNumber++
+
+		if o.seqNumber > 290 {
+			// NOTE we are done with the MIB Upload (290 is the number of messages the omci-sim library will respond to)
+			galEnet, _ := omcilib.CreateGalEnetRequest(o.getNextTid(false))
+			sendOmciMsg(galEnet, o.PonPortID, o.ID, o.SerialNumber, "CreateGalEnetRequest", client)
+		} else {
+			mibUploadNext, _ := omcilib.CreateMibUploadNextRequest(o.getNextTid(false), o.seqNumber)
+			sendOmciMsg(mibUploadNext, o.PonPortID, o.ID, o.SerialNumber, "mibUploadNext", client)
+		}
+	case omci.CreateResponseType:
+		// NOTE Creating a GemPort,
+		// BBsim actually doesn't care about the values, so we can do we want with the parameters
+		// In the same way we can create a GemPort even without setting up UNIs/TConts/...
+		// but we need the GemPort to trigger the state change
+
+		if !o.HasGemPort {
+			// NOTE this sends a CreateRequestType and BBSim replies with a CreateResponseType
+			// thus we send this request only once
+			gemReq, _ := omcilib.CreateGemPortRequest(o.getNextTid(false))
+			sendOmciMsg(gemReq, o.PonPortID, o.ID, o.SerialNumber, "CreateGemPortRequest", client)
+			o.HasGemPort = true
+		} else {
+			if err := o.InternalState.Event("send_eapol_flow"); err != nil {
+				onuLogger.WithFields(log.Fields{
+					"OnuId":  o.ID,
+					"IntfId": o.PonPortID,
+					"OnuSn":  o.Sn(),
+				}).Errorf("Error while transitioning ONU State %v", err)
+			}
+		}
+
+	}
+}
+
+func (o *Onu) sendEapolFlow(client openolt.OpenoltClient) {
+
+	classifierProto := openolt.Classifier{
+		EthType: uint32(layers.EthernetTypeEAPOL),
+		OVid:    4091,
+	}
+
+	actionProto := openolt.Action{}
+
+	downstreamFlow := openolt.Flow{
+		AccessIntfId:  int32(o.PonPortID),
+		OnuId:         int32(o.ID),
+		UniId:         int32(0x101), // FIXME do not hardcode this
+		FlowId:        uint32(o.ID),
+		FlowType:      "downstream",
+		AllocId:       int32(0),
+		NetworkIntfId: int32(0),
+		GemportId:     int32(1), // FIXME use the same value as CreateGemPortRequest PortID, do not hardcode
+		Classifier:    &classifierProto,
+		Action:        &actionProto,
+		Priority:      int32(100),
+		Cookie:        uint64(o.ID),
+		PortNo:        uint32(o.ID), // NOTE we are using this to map an incoming packetIndication to an ONU
+	}
+
+	if _, err := client.FlowAdd(context.Background(), &downstreamFlow); err != nil {
+		log.WithFields(log.Fields{
+			"IntfId":       o.PonPortID,
+			"OnuId":        o.ID,
+			"FlowId":       downstreamFlow.FlowId,
+			"PortNo":       downstreamFlow.PortNo,
+			"SerialNumber": common.OnuSnToString(o.SerialNumber),
+		}).Fatalf("Failed to EAPOL Flow")
+	}
+	log.WithFields(log.Fields{
+		"IntfId":       o.PonPortID,
+		"OnuId":        o.ID,
+		"FlowId":       downstreamFlow.FlowId,
+		"PortNo":       downstreamFlow.PortNo,
+		"SerialNumber": common.OnuSnToString(o.SerialNumber),
+	}).Info("Sent EAPOL Flow")
+}
+
+func (o *Onu) sendDhcpFlow(client openolt.OpenoltClient) {
+	classifierProto := openolt.Classifier{
+		EthType: uint32(layers.EthernetTypeIPv4),
+		SrcPort: uint32(68),
+		DstPort: uint32(67),
+	}
+
+	actionProto := openolt.Action{}
+
+	downstreamFlow := openolt.Flow{
+		AccessIntfId:  int32(o.PonPortID),
+		OnuId:         int32(o.ID),
+		UniId:         int32(0x101), // FIXME do not hardcode this
+		FlowId:        uint32(o.ID),
+		FlowType:      "downstream",
+		AllocId:       int32(0),
+		NetworkIntfId: int32(0),
+		GemportId:     int32(1), // FIXME use the same value as CreateGemPortRequest PortID, do not hardcode
+		Classifier:    &classifierProto,
+		Action:        &actionProto,
+		Priority:      int32(100),
+		Cookie:        uint64(o.ID),
+		PortNo:        uint32(o.ID), // NOTE we are using this to map an incoming packetIndication to an ONU
+	}
+
+	if _, err := client.FlowAdd(context.Background(), &downstreamFlow); err != nil {
+		log.WithFields(log.Fields{
+			"IntfId":       o.PonPortID,
+			"OnuId":        o.ID,
+			"FlowId":       downstreamFlow.FlowId,
+			"PortNo":       downstreamFlow.PortNo,
+			"SerialNumber": common.OnuSnToString(o.SerialNumber),
+		}).Fatalf("Failed to send DHCP Flow")
+	}
+	log.WithFields(log.Fields{
+		"IntfId":       o.PonPortID,
+		"OnuId":        o.ID,
+		"FlowId":       downstreamFlow.FlowId,
+		"PortNo":       downstreamFlow.PortNo,
+		"SerialNumber": common.OnuSnToString(o.SerialNumber),
+	}).Info("Sent DHCP Flow")
+}