[VOL-4111] Moving Services under the UNI struct
Controlling the UNI state via OMCI Set Messages
Upgraded APIs to reflect the new format

Change-Id: I3a6c166205fad4a381e562ab3b873d03b633303e
diff --git a/internal/bbsim/devices/uni_port.go b/internal/bbsim/devices/uni_port.go
index 78640e3..da16dcc 100644
--- a/internal/bbsim/devices/uni_port.go
+++ b/internal/bbsim/devices/uni_port.go
@@ -18,21 +18,53 @@
 
 import (
 	"fmt"
+	"github.com/google/gopacket/layers"
 	"github.com/looplab/fsm"
+	"github.com/opencord/bbsim/internal/bbsim/packetHandlers"
+	bbsimTypes "github.com/opencord/bbsim/internal/bbsim/types"
+	"github.com/opencord/bbsim/internal/common"
 	omcilib "github.com/opencord/bbsim/internal/common/omci"
 	log "github.com/sirupsen/logrus"
+	"net"
 )
 
-const maxUniPorts = 4
+var uniLogger = log.WithFields(log.Fields{
+	"module": "UNI",
+})
+
+const (
+	maxUniPorts = 4
+
+	UniStateUp   = "up"
+	UniStateDown = "down"
+
+	uniTxEnable  = "enable"
+	uniTxDisable = "disable"
+)
+
+type UniPortIf interface {
+	StorePortNo(portNo uint32)
+	UpdateStream(stream bbsimTypes.Stream)
+	Enable() error
+	Disable() error
+
+	HandlePackets()                  // start listening on the PacketCh
+	HandleAuth()                     // Sends the EapoStart packet
+	HandleDhcp(pbit uint8, cTag int) // Sends the DHCPDiscover packet
+}
 
 type UniPort struct {
 	ID        uint32
 	MeId      omcilib.EntityID
+	PortNo    uint32
 	OperState *fsm.FSM
 	Onu       *Onu
+	Services  []ServiceIf
+	logger    *log.Entry
+	PacketCh  chan bbsimTypes.OnuPacketMessage // handle packets
 }
 
-func NewUniPort(ID uint32, onu *Onu) (*UniPort, error) {
+func NewUniPort(ID uint32, onu *Onu, nextCtag map[string]int, nextStag map[string]int) (*UniPort, error) {
 
 	// IDs starts from 0, thus the maximum UNI supported is maxUniPorts - 1
 	if ID > (maxUniPorts - 1) {
@@ -45,12 +77,228 @@
 		MeId: omcilib.GenerateUniPortEntityId(ID + 1),
 	}
 
-	uni.OperState = getOperStateFSM(func(e *fsm.Event) {
-		onuLogger.WithFields(log.Fields{
-			"ID":    uni.ID,
-			"OnuSn": onu.Sn(),
-		}).Debugf("changing-uni-operstate-from-%s-to-%s", e.Src, e.Dst)
+	uni.logger = uniLogger.WithFields(log.Fields{
+		"UniId": uni.ID,
+		"OnuSn": onu.Sn(),
 	})
 
+	uni.OperState = fsm.NewFSM(
+		"down",
+		fsm.Events{
+			{Name: uniTxEnable, Src: []string{UniStateDown}, Dst: UniStateUp},
+			{Name: uniTxDisable, Src: []string{UniStateUp}, Dst: UniStateDown},
+		},
+		fsm.Callbacks{
+			"enter_state": func(e *fsm.Event) {
+				uni.logger.Debugf("changing-uni-operstate-from-%s-to-%s", e.Src, e.Dst)
+			},
+			fmt.Sprintf("enter_%s", UniStateUp): func(e *fsm.Event) {
+				msg := bbsimTypes.Message{
+					Type: bbsimTypes.UniStatusAlarm,
+					Data: bbsimTypes.UniStatusAlarmMessage{
+						OnuSN:          uni.Onu.SerialNumber,
+						OnuID:          uni.Onu.ID,
+						AdminState:     0,
+						EntityID:       uni.MeId.ToUint16(),
+						RaiseOMCIAlarm: false, // never raise an LOS when enabling a UNI
+					},
+				}
+				uni.Onu.Channel <- msg
+				go uni.HandlePackets()
+				for _, s := range uni.Services {
+					s.Initialize(uni.Onu.PonPort.Olt.OpenoltStream)
+				}
+			},
+			fmt.Sprintf("enter_%s", UniStateDown): func(e *fsm.Event) {
+				msg := bbsimTypes.Message{
+					Type: bbsimTypes.UniStatusAlarm,
+					Data: bbsimTypes.UniStatusAlarmMessage{
+						OnuSN:          uni.Onu.SerialNumber,
+						OnuID:          uni.Onu.ID,
+						AdminState:     1,
+						EntityID:       uni.MeId.ToUint16(),
+						RaiseOMCIAlarm: true, // raise an LOS when disabling a UNI
+					},
+				}
+				uni.Onu.Channel <- msg
+				for _, s := range uni.Services {
+					s.Disable()
+				}
+			},
+		},
+	)
+
+	for k, s := range common.Services {
+
+		// find the correct cTag for this service
+		if _, ok := nextCtag[s.Name]; !ok {
+			// it's the first time we iterate over this service,
+			// so we start from the config value
+			nextCtag[s.Name] = s.CTag
+		} else {
+			// we have a previous value, so we check it
+			// if Allocation is unique, we increment,
+			// otherwise (shared) we do nothing
+			if s.CTagAllocation == common.TagAllocationUnique.String() {
+				nextCtag[s.Name] = nextCtag[s.Name] + 1
+
+				// the max valid value for a tag is 4096
+				// check we're not going over
+				if nextCtag[s.Name] > 4096 {
+					uni.logger.WithFields(log.Fields{
+						"cTag":    nextCtag[s.Name],
+						"Service": s.Name,
+					}).Fatal("c-tag-limit-reached-too-many-subscribers")
+				}
+			}
+		}
+
+		// find the correct sTag for this service
+		if _, ok := nextStag[s.Name]; !ok {
+			nextStag[s.Name] = s.STag
+		} else {
+			if s.STagAllocation == common.TagAllocationUnique.String() {
+				nextStag[s.Name] = nextStag[s.Name] + 1
+
+				// the max valid value for a tag is 4096
+				// check we're not going over
+				if nextStag[s.Name] > 4096 {
+					uni.logger.WithFields(log.Fields{
+						"cTag":    nextCtag[s.Name],
+						"Service": s.Name,
+					}).Fatal("s-tag-limit-reached-too-many-subscribers")
+				}
+			}
+		}
+
+		mac := net.HardwareAddr{0x2e, byte(olt.ID), byte(onu.PonPortID), byte(onu.ID), byte(uni.ID), byte(k)}
+		service, err := NewService(uint32(k), s.Name, mac, &uni, nextCtag[s.Name], nextStag[s.Name],
+			s.NeedsEapol, s.NeedsDhcp, s.NeedsIgmp, s.TechnologyProfileID, s.UniTagMatch,
+			s.ConfigureMacAddress, s.UsPonCTagPriority, s.UsPonSTagPriority, s.DsPonCTagPriority, s.DsPonSTagPriority)
+
+		if err != nil {
+			oltLogger.WithFields(log.Fields{
+				"Err": err.Error(),
+			}).Fatal("Can't create Service")
+		}
+
+		uni.Services = append(uni.Services, service)
+	}
+
+	uni.PacketCh = make(chan bbsimTypes.OnuPacketMessage)
+
 	return &uni, nil
 }
+
+func (u *UniPort) StorePortNo(portNo uint32) {
+	u.PortNo = portNo
+	u.logger.WithFields(log.Fields{
+		"PortNo": portNo,
+	}).Debug("logical-port-number-added-to-uni")
+}
+
+func (u *UniPort) UpdateStream(stream bbsimTypes.Stream) {
+	for _, service := range u.Services {
+		service.UpdateStream(stream)
+	}
+}
+
+func (u *UniPort) Enable() error {
+	return u.OperState.Event(uniTxEnable)
+}
+
+func (u *UniPort) Disable() error {
+	return u.OperState.Event(uniTxDisable)
+}
+
+// this method simply forwards the packet to the correct service
+func (u *UniPort) HandlePackets() {
+	u.logger.Debug("listening-on-uni-packet-channel")
+
+	defer func() {
+		u.logger.Debug("done-listening-on-uni-packet-channel")
+	}()
+
+	for msg := range u.PacketCh {
+		u.logger.WithFields(log.Fields{
+			"messageType": msg.Type,
+		}).Trace("received-message-on-uni-packet-channel")
+
+		if msg.Type == packetHandlers.EAPOL || msg.Type == packetHandlers.DHCP {
+			service, err := u.findServiceByMacAddress(msg.MacAddress)
+			if err != nil {
+				u.logger.WithFields(log.Fields{"err": err}).Error("cannot-process-uni-pkt")
+				continue
+			}
+			service.PacketCh <- msg
+		} else if msg.Type == packetHandlers.IGMP {
+			//IGMP packets don't refer to any Mac Address, thus
+			//if it's an IGMP packet we assume we have a single IGMP service
+			for _, s := range u.Services {
+				service := s.(*Service)
+
+				if service.NeedsIgmp {
+					service.PacketCh <- msg
+				}
+			}
+		}
+	}
+}
+
+func (u *UniPort) HandleAuth() {
+	for _, s := range u.Services {
+		s.HandleAuth()
+	}
+}
+
+func (u *UniPort) HandleDhcp(pbit uint8, cTag int) {
+	for _, s := range u.Services {
+		s.HandleDhcp(pbit, cTag)
+	}
+}
+
+func (u *UniPort) addGemPortToService(gemport uint32, ethType uint32, oVlan uint32, iVlan uint32) {
+	for _, s := range u.Services {
+		if service, ok := s.(*Service); ok {
+			// EAPOL is a strange case, as packets are untagged
+			// but we assume we will have a single service requiring EAPOL
+			if ethType == uint32(layers.EthernetTypeEAPOL) && service.NeedsEapol {
+				service.GemPort = gemport
+			}
+
+			// For DHCP services we single tag the outgoing packets,
+			// thus the flow only contains the CTag and we can use that to match the service
+			if ethType == uint32(layers.EthernetTypeIPv4) && service.NeedsDhcp && service.CTag == int(oVlan) {
+				service.GemPort = gemport
+			}
+
+			// for dataplane services match both C and S tags
+			if service.CTag == int(iVlan) && service.STag == int(oVlan) {
+				service.GemPort = gemport
+			}
+
+			// for loggin purpose only
+			if service.GemPort == gemport {
+				// TODO move to Trace level
+				u.logger.WithFields(log.Fields{
+					"OnuId":  service.UniPort.Onu.ID,
+					"IntfId": service.UniPort.Onu.PonPortID,
+					"OnuSn":  service.UniPort.Onu.Sn(),
+					"Name":   service.Name,
+					"PortNo": service.UniPort.PortNo,
+					"UniId":  service.UniPort.ID,
+				}).Debug("gem-port-added-to-service")
+			}
+		}
+	}
+}
+
+func (u *UniPort) findServiceByMacAddress(macAddress net.HardwareAddr) (*Service, error) {
+	for _, s := range u.Services {
+		service := s.(*Service)
+		if service.HwAddress.String() == macAddress.String() {
+			return service, nil
+		}
+	}
+	return nil, fmt.Errorf("cannot-find-service-with-mac-address-%s", macAddress.String())
+}