[SEBA-434] Replacing omci-sim with omci-lib-go

Change-Id: I499afc9ec49bb483467ea93bd6ce3077dd0ccc6e
diff --git a/internal/bbsim/devices/onu.go b/internal/bbsim/devices/onu.go
index b85c804..822ec6b 100644
--- a/internal/bbsim/devices/onu.go
+++ b/internal/bbsim/devices/onu.go
@@ -20,20 +20,24 @@
 	"context"
 	"encoding/hex"
 	"fmt"
+	pb "github.com/opencord/bbsim/api/bbsim"
+	"github.com/opencord/bbsim/internal/bbsim/alarmsim"
+	bbsim "github.com/opencord/bbsim/internal/bbsim/types"
+	me "github.com/opencord/omci-lib-go/generated"
+	"strconv"
+
 	"github.com/opencord/bbsim/internal/bbsim/packetHandlers"
 	"github.com/opencord/bbsim/internal/bbsim/responders/dhcp"
 	"github.com/opencord/bbsim/internal/bbsim/responders/eapol"
 	"net"
-
 	"time"
 
-	"github.com/cboling/omci"
 	"github.com/google/gopacket/layers"
 	"github.com/jpillora/backoff"
 	"github.com/looplab/fsm"
 	"github.com/opencord/bbsim/internal/common"
 	omcilib "github.com/opencord/bbsim/internal/common/omci"
-	omcisim "github.com/opencord/omci-sim"
+	"github.com/opencord/omci-lib-go"
 	"github.com/opencord/voltha-protos/v4/go/openolt"
 	"github.com/opencord/voltha-protos/v4/go/tech_profile"
 	log "github.com/sirupsen/logrus"
@@ -72,7 +76,7 @@
 	OperState    *fsm.FSM
 	SerialNumber *openolt.SerialNumber
 
-	Channel chan Message // this Channel is to track state changes OMCI messages, EAPOL and DHCP packets
+	Channel chan bbsim.Message // this Channel is to track state changes OMCI messages, EAPOL and DHCP packets
 
 	// OMCI params
 	tid       uint16
@@ -143,7 +147,7 @@
 			},
 			"enter_initialized": func(e *fsm.Event) {
 				// create new channel for ProcessOnuMessages Go routine
-				o.Channel = make(chan Message, 2048)
+				o.Channel = make(chan bbsim.Message, 2048)
 
 				if err := o.OperState.Event("enable"); err != nil {
 					onuLogger.WithFields(log.Fields{
@@ -159,22 +163,21 @@
 				}
 			},
 			"enter_discovered": func(e *fsm.Event) {
-				msg := Message{
-					Type: OnuDiscIndication,
-					Data: OnuDiscIndicationMessage{
-						Onu:       &o,
-						OperState: UP,
+				msg := bbsim.Message{
+					Type: bbsim.OnuDiscIndication,
+					Data: bbsim.OnuDiscIndicationMessage{
+						OperState: bbsim.UP,
 					},
 				}
 				o.Channel <- msg
 			},
 			"enter_enabled": func(event *fsm.Event) {
-				msg := Message{
-					Type: OnuIndication,
-					Data: OnuIndicationMessage{
+				msg := bbsim.Message{
+					Type: bbsim.OnuIndication,
+					Data: bbsim.OnuIndicationMessage{
 						OnuSN:     o.SerialNumber,
 						PonPortID: o.PonPortID,
-						OperState: UP,
+						OperState: bbsim.UP,
 					},
 				}
 				o.Channel <- msg
@@ -201,12 +204,12 @@
 				}
 
 				// send the OnuIndication DOWN event
-				msg := Message{
-					Type: OnuIndication,
-					Data: OnuIndicationMessage{
+				msg := bbsim.Message{
+					Type: bbsim.OnuIndication,
+					Data: bbsim.OnuIndicationMessage{
 						OnuSN:     o.SerialNumber,
 						PonPortID: o.PonPortID,
-						OperState: DOWN,
+						OperState: bbsim.DOWN,
 					},
 				}
 				o.Channel <- msg
@@ -223,14 +226,14 @@
 			},
 			// BBR states
 			"enter_eapol_flow_sent": func(e *fsm.Event) {
-				msg := Message{
-					Type: SendEapolFlow,
+				msg := bbsim.Message{
+					Type: bbsim.SendEapolFlow,
 				}
 				o.Channel <- msg
 			},
 			"enter_dhcp_flow_sent": func(e *fsm.Event) {
-				msg := Message{
-					Type: SendDhcpFlow,
+				msg := bbsim.Message{
+					Type: bbsim.SendDhcpFlow,
 				}
 				o.Channel <- msg
 			},
@@ -280,26 +283,45 @@
 			}).Tracef("Received message on ONU Channel")
 
 			switch message.Type {
-			case OnuDiscIndication:
-				msg, _ := message.Data.(OnuDiscIndicationMessage)
+			case bbsim.OnuDiscIndication:
+				msg, _ := message.Data.(bbsim.OnuDiscIndicationMessage)
 				// NOTE we need to slow down and send ONU Discovery Indication in batches to better emulate a real scenario
 				time.Sleep(o.DiscoveryDelay)
 				o.sendOnuDiscIndication(msg, stream)
-			case OnuIndication:
-				msg, _ := message.Data.(OnuIndicationMessage)
+			case bbsim.OnuIndication:
+				msg, _ := message.Data.(bbsim.OnuIndicationMessage)
 				o.sendOnuIndication(msg, stream)
-			case OMCI:
-				msg, _ := message.Data.(OmciMessage)
-				o.handleOmciMessage(msg, stream)
-			case FlowAdd:
-				msg, _ := message.Data.(OnuFlowUpdateMessage)
+			case bbsim.OMCI:
+				msg, _ := message.Data.(bbsim.OmciMessage)
+				o.handleOmciRequest(msg, stream)
+			case bbsim.UniStatusAlarm:
+				msg, _ := message.Data.(bbsim.UniStatusAlarmMessage)
+				pkt := omcilib.CreateUniStatusAlarm(msg.AdminState, msg.EntityID)
+				if err := o.sendOmciIndication(pkt, 0, stream); err != nil {
+					onuLogger.WithFields(log.Fields{
+						"IntfId":       o.PonPortID,
+						"SerialNumber": o.Sn(),
+						"omciPacket":   pkt,
+						"adminState":   msg.AdminState,
+						"entityID":     msg.EntityID,
+					}).Errorf("failed-to-send-UNI-Link-Alarm: %v", err)
+				}
+				onuLogger.WithFields(log.Fields{
+					"IntfId":       o.PonPortID,
+					"SerialNumber": o.Sn(),
+					"omciPacket":   pkt,
+					"adminState":   msg.AdminState,
+					"entityID":     msg.EntityID,
+				}).Trace("UNI-Link-alarm-sent")
+			case bbsim.FlowAdd:
+				msg, _ := message.Data.(bbsim.OnuFlowUpdateMessage)
 				o.handleFlowAdd(msg)
-			case FlowRemoved:
-				msg, _ := message.Data.(OnuFlowUpdateMessage)
+			case bbsim.FlowRemoved:
+				msg, _ := message.Data.(bbsim.OnuFlowUpdateMessage)
 				o.handleFlowRemove(msg)
-			case OnuPacketOut:
+			case bbsim.OnuPacketOut:
 
-				msg, _ := message.Data.(OnuPacketMessage)
+				msg, _ := message.Data.(bbsim.OnuPacketMessage)
 
 				onuLogger.WithFields(log.Fields{
 					"IntfId":  msg.IntfId,
@@ -333,11 +355,11 @@
 					}
 				}
 
-			case OnuPacketIn:
+			case bbsim.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)
+				msg, _ := message.Data.(bbsim.OnuPacketMessage)
 
 				log.WithFields(log.Fields{
 					"IntfId":  msg.IntfId,
@@ -351,12 +373,12 @@
 					_ = dhcp.HandleNextBbrPacket(o.ID, o.PonPortID, o.Sn(), o.DoneChannel, msg.Packet, client)
 				}
 				// BBR specific messages
-			case OmciIndication:
-				msg, _ := message.Data.(OmciIndicationMessage)
-				o.handleOmci(msg, client)
-			case SendEapolFlow:
+			case bbsim.OmciIndication:
+				msg, _ := message.Data.(bbsim.OmciIndicationMessage)
+				o.handleOmciResponse(msg, client)
+			case bbsim.SendEapolFlow:
 				o.sendEapolFlow(client)
-			case SendDhcpFlow:
+			case bbsim.SendDhcpFlow:
 				o.sendDhcpFlow(client)
 			default:
 				onuLogger.Warnf("Received unknown message data %v for type %v in OLT Channel", message.Data, message.Type)
@@ -369,42 +391,6 @@
 	}).Debug("Stopped handling ONU Indication Channel")
 }
 
-func (o *Onu) processOmciMessage(message omcisim.OmciChMessage, stream openolt.Openolt_EnableIndicationServer) {
-	switch message.Type {
-	case omcisim.UniLinkUp, omcisim.UniLinkDown:
-		onuLogger.WithFields(log.Fields{
-			"OnuId":  message.Data.OnuId,
-			"IntfId": message.Data.IntfId,
-			"Type":   message.Type,
-		}).Debug("UNI Link Alarm")
-		// TODO send to OLT
-
-		omciInd := openolt.OmciIndication{
-			IntfId: message.Data.IntfId,
-			OnuId:  message.Data.OnuId,
-			Pkt:    message.Packet,
-		}
-
-		omci := &openolt.Indication_OmciInd{OmciInd: &omciInd}
-		if err := stream.Send(&openolt.Indication{Data: omci}); err != nil {
-			onuLogger.WithFields(log.Fields{
-				"IntfId":       o.PonPortID,
-				"SerialNumber": o.Sn(),
-				"Type":         message.Type,
-				"omciPacket":   omciInd.Pkt,
-			}).Errorf("Failed to send UNI Link Alarm: %v", err)
-			return
-		}
-
-		onuLogger.WithFields(log.Fields{
-			"IntfId":       o.PonPortID,
-			"SerialNumber": o.Sn(),
-			"Type":         message.Type,
-			"omciPacket":   omciInd.Pkt,
-		}).Debug("UNI Link alarm sent")
-	}
-}
-
 func (o Onu) NewSN(oltid int, intfid uint32, onuid uint32) *openolt.SerialNumber {
 
 	sn := new(openolt.SerialNumber)
@@ -416,10 +402,10 @@
 	return sn
 }
 
-func (o *Onu) sendOnuDiscIndication(msg OnuDiscIndicationMessage, stream openolt.Openolt_EnableIndicationServer) {
+func (o *Onu) sendOnuDiscIndication(msg bbsim.OnuDiscIndicationMessage, stream openolt.Openolt_EnableIndicationServer) {
 	discoverData := &openolt.Indication_OnuDiscInd{OnuDiscInd: &openolt.OnuDiscIndication{
-		IntfId:       msg.Onu.PonPortID,
-		SerialNumber: msg.Onu.SerialNumber,
+		IntfId:       o.PonPortID,
+		SerialNumber: o.SerialNumber,
 	}}
 
 	if err := stream.Send(&openolt.Indication{Data: discoverData}); err != nil {
@@ -428,11 +414,11 @@
 	}
 
 	onuLogger.WithFields(log.Fields{
-		"IntfId": msg.Onu.PonPortID,
-		"OnuSn":  msg.Onu.Sn(),
+		"IntfId": o.PonPortID,
+		"OnuSn":  o.Sn(),
 		"OnuId":  o.ID,
 	}).Debug("Sent Indication_OnuDiscInd")
-	publishEvent("ONU-discovery-indication-sent", int32(msg.Onu.PonPortID), int32(o.ID), msg.Onu.Sn())
+	publishEvent("ONU-discovery-indication-sent", int32(o.PonPortID), int32(o.ID), o.Sn())
 
 	// after DiscoveryRetryDelay check if the state is the same and in case send a new OnuDiscIndication
 	go func(delay time.Duration) {
@@ -443,7 +429,7 @@
 	}(o.DiscoveryRetryDelay)
 }
 
-func (o *Onu) sendOnuIndication(msg OnuIndicationMessage, stream openolt.Openolt_EnableIndicationServer) {
+func (o *Onu) sendOnuIndication(msg bbsim.OnuIndicationMessage, stream openolt.Openolt_EnableIndicationServer) {
 	// NOTE voltha returns an ID, but if we use that ID then it complains:
 	// expected_onu_id: 1, received_onu_id: 1024, event: ONU-id-mismatch, can happen if both voltha and the olt rebooted
 	// so we're using the internal ID that is 1
@@ -471,17 +457,136 @@
 
 }
 
-func (o *Onu) publishOmciEvent(msg OmciMessage) {
+func (o *Onu) HandleShutdownONU() error {
+
+	dyingGasp := pb.ONUAlarmRequest{
+		AlarmType:    "DYING_GASP",
+		SerialNumber: o.Sn(),
+		Status:       "on",
+	}
+
+	if err := alarmsim.SimulateOnuAlarm(&dyingGasp, o.ID, o.PonPortID, o.PonPort.Olt.channel); err != nil {
+		onuLogger.WithFields(log.Fields{
+			"OnuId":  o.ID,
+			"IntfId": o.PonPortID,
+			"OnuSn":  o.Sn(),
+		}).Errorf("Cannot send Dying Gasp: %s", err.Error())
+		return err
+	}
+
+	losReq := pb.ONUAlarmRequest{
+		AlarmType:    "ONU_ALARM_LOS",
+		SerialNumber: o.Sn(),
+		Status:       "on",
+	}
+
+	if err := alarmsim.SimulateOnuAlarm(&losReq, o.ID, o.PonPortID, o.PonPort.Olt.channel); err != nil {
+		onuLogger.WithFields(log.Fields{
+			"OnuId":  o.ID,
+			"IntfId": o.PonPortID,
+			"OnuSn":  o.Sn(),
+		}).Errorf("Cannot send LOS: %s", err.Error())
+
+		return err
+	}
+
+	// TODO if it's the last ONU on the PON, then send a PON LOS
+
+	if err := o.InternalState.Event("disable"); err != nil {
+		onuLogger.WithFields(log.Fields{
+			"OnuId":  o.ID,
+			"IntfId": o.PonPortID,
+			"OnuSn":  o.Sn(),
+		}).Errorf("Cannot shutdown ONU: %s", err.Error())
+		return err
+	}
+
+	return nil
+}
+
+func (o *Onu) HandlePowerOnONU() error {
+	intitalState := o.InternalState.Current()
+
+	// initialize the ONU
+	if intitalState == "created" || intitalState == "disabled" {
+		if err := o.InternalState.Event("initialize"); err != nil {
+			onuLogger.WithFields(log.Fields{
+				"OnuId":  o.ID,
+				"IntfId": o.PonPortID,
+				"OnuSn":  o.Sn(),
+			}).Errorf("Cannot poweron ONU: %s", err.Error())
+			return err
+		}
+	}
+
+	// turn off the LOS Alarm
+	losReq := pb.ONUAlarmRequest{
+		AlarmType:    "ONU_ALARM_LOS",
+		SerialNumber: o.Sn(),
+		Status:       "off",
+	}
+
+	if err := alarmsim.SimulateOnuAlarm(&losReq, o.ID, o.PonPortID, o.PonPort.Olt.channel); err != nil {
+		onuLogger.WithFields(log.Fields{
+			"OnuId":  o.ID,
+			"IntfId": o.PonPortID,
+			"OnuSn":  o.Sn(),
+		}).Errorf("Cannot send LOS: %s", err.Error())
+		return err
+	}
+
+	// Send a ONU Discovery indication
+	if err := o.InternalState.Event("discover"); err != nil {
+		onuLogger.WithFields(log.Fields{
+			"OnuId":  o.ID,
+			"IntfId": o.PonPortID,
+			"OnuSn":  o.Sn(),
+		}).Errorf("Cannot poweron ONU: %s", err.Error())
+		return err
+	}
+
+	// move o directly to enable state only when its a powercycle case
+	// in case of first time o poweron o will be moved to enable on
+	// receiving ActivateOnu request from openolt adapter
+	if intitalState == "disabled" {
+		if err := o.InternalState.Event("enable"); err != nil {
+			onuLogger.WithFields(log.Fields{
+				"OnuId":  o.ID,
+				"IntfId": o.PonPortID,
+				"OnuSn":  o.Sn(),
+			}).Errorf("Cannot enable ONU: %s", err.Error())
+			return err
+		}
+	}
+
+	return nil
+}
+
+func (o *Onu) SetAlarm(alarmType string, status string) error {
+	alarmReq := pb.ONUAlarmRequest{
+		AlarmType:    alarmType,
+		SerialNumber: o.Sn(),
+		Status:       status,
+	}
+
+	err := alarmsim.SimulateOnuAlarm(&alarmReq, o.ID, o.PonPortID, o.PonPort.Olt.channel)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func (o *Onu) publishOmciEvent(msg bbsim.OmciMessage) {
 	if olt.PublishEvents {
-		_, _, msgType, _, _, _, err := omcisim.ParsePkt(HexDecode(msg.omciMsg.Pkt))
+		_, omciMsg, err := omcilib.ParseOpenOltOmciPacket(msg.OmciMsg.Pkt)
 		if err != nil {
 			log.Errorf("error in getting msgType %v", err)
 			return
 		}
-		if msgType == omcisim.MibUpload {
+		if omciMsg.MessageType == omci.MibUploadRequestType {
 			o.seqNumber = 0
 			publishEvent("MIB-upload-received", int32(o.PonPortID), int32(o.ID), common.OnuSnToString(o.SerialNumber))
-		} else if msgType == omcisim.MibUploadNext {
+		} else if omciMsg.MessageType == omci.MibUploadNextRequestType {
 			o.seqNumber++
 			if o.seqNumber > 290 {
 				publishEvent("MIB-upload-done", int32(o.PonPortID), int32(o.ID), common.OnuSnToString(o.SerialNumber))
@@ -491,8 +596,8 @@
 }
 
 // Create a TestResponse packet and send it
-func (o *Onu) sendTestResult(msg OmciMessage, stream openolt.Openolt_EnableIndicationServer) error {
-	resp, err := omcilib.BuildTestResult(HexDecode(msg.omciMsg.Pkt))
+func (o *Onu) sendTestResult(msg bbsim.OmciMessage, stream openolt.Openolt_EnableIndicationServer) error {
+	resp, err := omcilib.BuildTestResult(msg.OmciMsg.Pkt)
 	if err != nil {
 		return err
 	}
@@ -504,12 +609,6 @@
 
 	omci := &openolt.Indication_OmciInd{OmciInd: &omciInd}
 	if err := stream.Send(&openolt.Indication{Data: omci}); err != nil {
-		onuLogger.WithFields(log.Fields{
-			"IntfId":       o.PonPortID,
-			"SerialNumber": o.Sn(),
-			"omciPacket":   omciInd.Pkt,
-			"msg":          msg,
-		}).Errorf("send TestResult omcisim indication failed: %v", err)
 		return err
 	}
 	onuLogger.WithFields(log.Fields{
@@ -521,57 +620,151 @@
 	return nil
 }
 
-func (o *Onu) handleOmciMessage(msg OmciMessage, stream openolt.Openolt_EnableIndicationServer) {
+// handleOmciRequest is responsible to parse the OMCI packets received from the openolt adapter
+// and generate the appropriate response to it
+func (o *Onu) handleOmciRequest(msg bbsim.OmciMessage, stream openolt.Openolt_EnableIndicationServer) {
+
+	omciPkt, omciMsg, err := omcilib.ParseOpenOltOmciPacket(msg.OmciMsg.Pkt)
+	if err != nil {
+		log.WithFields(log.Fields{
+			"IntfId":       o.PonPortID,
+			"SerialNumber": o.Sn(),
+			"omciPacket":   msg.OmciMsg.Pkt,
+		}).Error("cannot-parse-OMCI-packet")
+	}
 
 	onuLogger.WithFields(log.Fields{
+		"omciMsgType":  omciMsg.MessageType,
+		"transCorrId":  strconv.FormatInt(int64(omciMsg.TransactionID), 16),
+		"DeviceIdent":  omciMsg.DeviceIdentifier,
 		"IntfId":       o.PonPortID,
 		"SerialNumber": o.Sn(),
-		"omciPacket":   msg.omciMsg.Pkt,
-	}).Tracef("Received OMCI message")
+	}).Trace("omci-message-decoded")
+
+	var responsePkt []byte
+	switch omciMsg.MessageType {
+	case omci.MibResetRequestType:
+		responsePkt, _ = omcilib.CreateMibResetResponse(omciMsg.TransactionID)
+	case omci.MibUploadRequestType:
+		responsePkt, _ = omcilib.CreateMibUploadResponse(omciMsg.TransactionID)
+	case omci.MibUploadNextRequestType:
+		responsePkt, _ = omcilib.CreateMibUploadNextResponse(omciPkt, omciMsg)
+	case omci.GetRequestType:
+		responsePkt, _ = omcilib.CreateGetResponse(omciPkt, omciMsg)
+	case omci.SetRequestType:
+		responsePkt, _ = omcilib.CreateSetResponse(omciPkt, omciMsg)
+
+		msgObj, _ := omcilib.ParseSetRequest(omciPkt)
+		switch msgObj.EntityClass {
+		case me.PhysicalPathTerminationPointEthernetUniClassID:
+			// if we're Setting a PPTP state
+			// we need to send the appropriate alarm
+
+			if msgObj.EntityInstance == 257 {
+				// for now we're only caring about the first UNI
+				// NOTE that the EntityID for the UNI port is for now hardcoded in
+				// omci/mibpackets.go where the PhysicalPathTerminationPointEthernetUni
+				// are reported during the MIB Upload sequence
+				adminState := msgObj.Attributes["AdministrativeState"].(uint8)
+				msg := bbsim.Message{
+					Type: bbsim.UniStatusAlarm,
+					Data: bbsim.UniStatusAlarmMessage{
+						OnuSN:      o.SerialNumber,
+						OnuID:      o.ID,
+						AdminState: adminState,
+						EntityID:   msgObj.EntityInstance,
+					},
+				}
+				o.Channel <- msg
+			}
+		}
+	case omci.CreateRequestType:
+		responsePkt, _ = omcilib.CreateCreateResponse(omciPkt, omciMsg)
+	case omci.DeleteRequestType:
+		responsePkt, _ = omcilib.CreateDeleteResponse(omciPkt, omciMsg)
+	case omci.RebootRequestType:
+
+		responsePkt, _ = omcilib.CreateRebootResponse(omciPkt, omciMsg)
+
+		// powercycle the ONU
+		go func() {
+			// we run this in a separate goroutine so that
+			// the RebootRequestResponse is sent to VOLTHA
+			onuLogger.WithFields(log.Fields{
+				"IntfId":       o.PonPortID,
+				"SerialNumber": o.Sn(),
+			}).Debug("shutting-down-onu-for-omci-reboot")
+			_ = o.HandleShutdownONU()
+			time.Sleep(10 * time.Second)
+			onuLogger.WithFields(log.Fields{
+				"IntfId":       o.PonPortID,
+				"SerialNumber": o.Sn(),
+			}).Debug("power-on-onu-for-omci-reboot")
+			_ = o.HandlePowerOnONU()
+		}()
+	case omci.TestRequestType:
+
+		// Test message is special, it requires sending two packets:
+		//     first packet: TestResponse, says whether test was started successully, handled by omci-sim
+		//     second packet, TestResult, reports the result of running the self-test
+		// TestResult can come some time after a TestResponse
+		//     TODO: Implement some delay between the TestResponse and the TestResult
+		isTest, err := omcilib.IsTestRequest(msg.OmciMsg.Pkt)
+		if (err == nil) && (isTest) {
+			if sendErr := o.sendTestResult(msg, stream); sendErr != nil {
+				onuLogger.WithFields(log.Fields{
+					"IntfId":       o.PonPortID,
+					"SerialNumber": o.Sn(),
+					"omciPacket":   msg.OmciMsg.Pkt,
+					"msg":          msg,
+					"err":          sendErr,
+				}).Error("send-TestResult-indication-failed")
+			}
+		}
+
+	default:
+		log.WithFields(log.Fields{
+			"omciMsgType":  omciMsg.MessageType,
+			"transCorrId":  omciMsg.TransactionID,
+			"IntfId":       o.PonPortID,
+			"SerialNumber": o.Sn(),
+		}).Warnf("OMCI-message-not-supported")
+	}
+
+	if responsePkt != nil {
+		if err := o.sendOmciIndication(responsePkt, omciMsg.TransactionID, stream); err != nil {
+			onuLogger.WithFields(log.Fields{
+				"IntfId":       o.PonPortID,
+				"SerialNumber": o.Sn(),
+				"omciPacket":   responsePkt,
+				"omciMsgType":  omciMsg.MessageType,
+				"transCorrId":  omciMsg.TransactionID,
+			}).Errorf("failed-to-send-omci-message: %v", err)
+		}
+	}
 
 	o.publishOmciEvent(msg)
+}
 
-	var omciInd openolt.OmciIndication
-	respPkt, err := omcisim.OmciSim(o.PonPort.Olt.ID, o.PonPortID, o.ID, HexDecode(msg.omciMsg.Pkt))
-	if err != nil {
-		onuLogger.WithFields(log.Fields{
-			"IntfId":       o.PonPortID,
-			"SerialNumber": o.Sn(),
-			"omciPacket":   omciInd.Pkt,
-			"msg":          msg,
-		}).Errorf("Error handling OMCI message %v", msg)
-		return
+// sendOmciIndication takes an OMCI packet and sends it up to VOLTHA
+func (o *Onu) sendOmciIndication(responsePkt []byte, txId uint16, stream bbsim.Stream) error {
+	indication := &openolt.Indication_OmciInd{
+		OmciInd: &openolt.OmciIndication{
+			IntfId: o.PonPortID,
+			OnuId:  o.ID,
+			Pkt:    responsePkt,
+		},
 	}
-
-	omciInd.IntfId = o.PonPortID
-	omciInd.OnuId = o.ID
-	omciInd.Pkt = respPkt
-
-	omci := &openolt.Indication_OmciInd{OmciInd: &omciInd}
-	if err := stream.Send(&openolt.Indication{Data: omci}); err != nil {
-		onuLogger.WithFields(log.Fields{
-			"IntfId":       o.PonPortID,
-			"SerialNumber": o.Sn(),
-			"omciPacket":   omciInd.Pkt,
-			"msg":          msg,
-		}).Errorf("send omcisim indication failed: %v", err)
-		return
+	if err := stream.Send(&openolt.Indication{Data: indication}); err != nil {
+		return fmt.Errorf("failed-to-send-omci-message: %v", err)
 	}
 	onuLogger.WithFields(log.Fields{
 		"IntfId":       o.PonPortID,
 		"SerialNumber": o.Sn(),
-		"omciPacket":   omciInd.Pkt,
-	}).Tracef("Sent OMCI message")
-
-	// Test message is special, it requires sending two packets:
-	//     first packet: TestResponse, says whether test was started successully, handled by omci-sim
-	//     second packet, TestResult, reports the result of running the self-test
-	// TestResult can come some time after a TestResponse
-	//     TODO: Implement some delay between the TestResponse and the TestResult
-	isTest, err := omcilib.IsTestRequest(HexDecode(msg.omciMsg.Pkt))
-	if (err == nil) && (isTest) {
-		_ = o.sendTestResult(msg, stream)
-	}
+		"omciPacket":   indication.OmciInd.Pkt,
+		"transCorrId":  txId,
+	}).Trace("omci-message-sent")
+	return nil
 }
 
 func (o *Onu) storePortNumber(portNo uint32) {
@@ -602,7 +795,7 @@
 	o.ID = id
 }
 
-func (o *Onu) handleFlowAdd(msg OnuFlowUpdateMessage) {
+func (o *Onu) handleFlowAdd(msg bbsim.OnuFlowUpdateMessage) {
 	onuLogger.WithFields(log.Fields{
 		"Cookie":            msg.Flow.Cookie,
 		"DstPort":           msg.Flow.Classifier.DstPort,
@@ -667,7 +860,7 @@
 	}
 }
 
-func (o *Onu) handleFlowRemove(msg OnuFlowUpdateMessage) {
+func (o *Onu) handleFlowRemove(msg bbsim.OnuFlowUpdateMessage) {
 	onuLogger.WithFields(log.Fields{
 		"IntfId":       o.PonPortID,
 		"OnuId":        o.ID,
@@ -700,19 +893,6 @@
 	}
 }
 
-// HexDecode converts the hex encoding to binary
-func HexDecode(pkt []byte) []byte {
-	p := make([]byte, len(pkt)/2)
-	for i, j := 0, 0; i < len(pkt); i, j = i+2, j+1 {
-		// Go figure this ;)
-		u := (pkt[i] & 15) + (pkt[i]>>6)*9
-		l := (pkt[i+1] & 15) + (pkt[i+1]>>6)*9
-		p[j] = u<<4 + l
-	}
-	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) {
@@ -762,24 +942,36 @@
 	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)
+// handleOmciResponse is used in BBR to generate the OMCI packets the openolt-adapter would send to the device
+func (o *Onu) handleOmciResponse(msg bbsim.OmciIndicationMessage, client openolt.OpenoltClient) {
+
+	// we need to encode the packet in HEX
+	pkt := make([]byte, len(msg.OmciInd.Pkt)*2)
+	hex.Encode(pkt, msg.OmciInd.Pkt)
+	packet, omciMsg, err := omcilib.ParseOpenOltOmciPacket(pkt)
+	if err != nil {
+		log.WithFields(log.Fields{
+			"IntfId":       o.PonPortID,
+			"SerialNumber": o.Sn(),
+			"omciPacket":   msg.OmciInd.Pkt,
+		}).Error("BBR Cannot parse OMCI packet")
+	}
 
 	log.WithFields(log.Fields{
 		"IntfId":  msg.OmciInd.IntfId,
 		"OnuId":   msg.OmciInd.OnuId,
-		"OnuSn":   common.OnuSnToString(o.SerialNumber),
+		"OnuSn":   o.Sn(),
 		"Pkt":     msg.OmciInd.Pkt,
-		"msgType": msgType,
+		"msgType": omciMsg.MessageType,
 	}).Trace("ONU Receives OMCI Msg")
-	switch msgType {
+	switch omciMsg.MessageType {
 	default:
 		log.WithFields(log.Fields{
 			"IntfId":  msg.OmciInd.IntfId,
 			"OnuId":   msg.OmciInd.OnuId,
-			"OnuSn":   common.OnuSnToString(o.SerialNumber),
+			"OnuSn":   o.Sn(),
 			"Pkt":     msg.OmciInd.Pkt,
-			"msgType": msgType,
+			"msgType": omciMsg.MessageType,
 		}).Fatalf("unexpected frame: %v", packet)
 	case omci.MibResetResponseType:
 		mibUpload, _ := omcilib.CreateMibUploadRequest(o.getNextTid(false))