[VOL-2778] Introducing Service definition in order to support the TT workflow

Change-Id: Ib171502e8940b5d0b219620a4503f7095d376d7a
diff --git a/internal/bbsim/devices/services.go b/internal/bbsim/devices/services.go
new file mode 100644
index 0000000..c7940c9
--- /dev/null
+++ b/internal/bbsim/devices/services.go
@@ -0,0 +1,299 @@
+/*
+ * 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 (
+	"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"
+	bbsimTypes "github.com/opencord/bbsim/internal/bbsim/types"
+	log "github.com/sirupsen/logrus"
+	"net"
+)
+
+var serviceLogger = log.WithFields(log.Fields{
+	"module": "SERVICE",
+})
+
+type ServiceIf interface {
+	HandlePackets(stream bbsimTypes.Stream)        // start listening on the PacketCh
+	HandleAuth(stream bbsimTypes.Stream)           // Sends the EapoStart packet
+	HandleDhcp(stream bbsimTypes.Stream, cTag int) // Sends the DHCPDiscover packet
+}
+
+type Service struct {
+	Name                string
+	HwAddress           net.HardwareAddr
+	Onu                 *Onu
+	CTag                int
+	STag                int
+	NeedsEapol          bool
+	NeedsDhcp           bool
+	NeedsIgmp           bool
+	TechnologyProfileID int
+	UniTagMatch         int
+	ConfigureMacAddress bool
+	UsPonCTagPriority   int
+	UsPonSTagPriority   int
+	DsPonCTagPriority   int
+	DsPonSTagPriority   int
+
+	// state
+	GemPort    uint32
+	EapolState *fsm.FSM
+	DHCPState  *fsm.FSM
+	PacketCh   chan OnuPacketMessage
+}
+
+func NewService(name string, hwAddress net.HardwareAddr, onu *Onu, cTag int, sTag int,
+	needsEapol bool, needsDchp bool, needsIgmp bool, tpID int, uniTagMatch int, configMacAddress bool,
+	usPonCTagPriority int, usPonSTagPriority int, dsPonCTagPriority int, dsPonSTagPriority int) (*Service, error) {
+
+	service := Service{
+		Name:                name,
+		HwAddress:           hwAddress,
+		Onu:                 onu,
+		CTag:                cTag,
+		STag:                sTag,
+		NeedsEapol:          needsEapol,
+		NeedsDhcp:           needsDchp,
+		NeedsIgmp:           needsIgmp,
+		TechnologyProfileID: tpID,
+		UniTagMatch:         uniTagMatch,
+		ConfigureMacAddress: configMacAddress,
+		UsPonCTagPriority:   usPonCTagPriority,
+		UsPonSTagPriority:   usPonSTagPriority,
+		DsPonCTagPriority:   dsPonCTagPriority,
+		DsPonSTagPriority:   dsPonSTagPriority,
+		PacketCh:            make(chan OnuPacketMessage),
+	}
+
+	service.EapolState = fsm.NewFSM(
+		"created",
+		fsm.Events{
+			{Name: "start_auth", Src: []string{"created", "eap_start_sent", "eap_response_identity_sent", "eap_response_challenge_sent", "eap_response_success_received", "auth_failed"}, Dst: "auth_started"},
+			{Name: "eap_start_sent", Src: []string{"auth_started"}, Dst: "eap_start_sent"},
+			{Name: "eap_response_identity_sent", Src: []string{"eap_start_sent"}, Dst: "eap_response_identity_sent"},
+			{Name: "eap_response_challenge_sent", Src: []string{"eap_response_identity_sent"}, Dst: "eap_response_challenge_sent"},
+			{Name: "eap_response_success_received", Src: []string{"eap_response_challenge_sent"}, Dst: "eap_response_success_received"},
+			{Name: "auth_failed", Src: []string{"auth_started", "eap_start_sent", "eap_response_identity_sent", "eap_response_challenge_sent"}, Dst: "auth_failed"},
+		},
+		fsm.Callbacks{
+			"enter_state": func(e *fsm.Event) {
+				service.logStateChange("EapolState", e.Src, e.Dst)
+			},
+		},
+	)
+
+	service.DHCPState = fsm.NewFSM(
+		"created",
+		fsm.Events{
+			{Name: "start_dhcp", Src: []string{"created", "eap_response_success_received", "dhcp_discovery_sent", "dhcp_request_sent", "dhcp_ack_received", "dhcp_failed"}, Dst: "dhcp_started"},
+			{Name: "dhcp_discovery_sent", Src: []string{"dhcp_started"}, Dst: "dhcp_discovery_sent"},
+			{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"},
+		},
+		fsm.Callbacks{
+			"enter_state": func(e *fsm.Event) {
+				service.logStateChange("DHCPState", e.Src, e.Dst)
+			},
+		},
+	)
+
+	return &service, nil
+}
+
+func (s *Service) HandleAuth(stream bbsimTypes.Stream) {
+
+	if !s.NeedsEapol {
+		serviceLogger.WithFields(log.Fields{
+			"OnuId":      s.Onu.ID,
+			"IntfId":     s.Onu.PonPortID,
+			"OnuSn":      s.Onu.Sn(),
+			"Name":       s.Name,
+			"NeedsEapol": s.NeedsEapol,
+		}).Debug("Won't start authentication as EAPOL is not required")
+		return
+	}
+
+	// TODO check if the EAPOL flow was received before starting auth
+
+	if err := s.EapolState.Event("start_auth"); err != nil {
+		serviceLogger.WithFields(log.Fields{
+			"OnuId":  s.Onu.ID,
+			"IntfId": s.Onu.PonPortID,
+			"OnuSn":  s.Onu.Sn(),
+			"Name":   s.Name,
+			"err":    err.Error(),
+		}).Error("Can't start auth for this Service")
+	} else {
+		if err := s.handleEapolStart(stream); err != nil {
+			serviceLogger.WithFields(log.Fields{
+				"OnuId":  s.Onu.ID,
+				"IntfId": s.Onu.PonPortID,
+				"OnuSn":  s.Onu.Sn(),
+				"Name":   s.Name,
+				"err":    err,
+			}).Error("Error while sending EapolStart packet")
+			_ = s.EapolState.Event("auth_failed")
+		}
+	}
+}
+
+func (s *Service) HandleDhcp(stream bbsimTypes.Stream, cTag int) {
+
+	// FIXME start dhcp only for the Service that matches the tag
+	if s.CTag != cTag {
+		serviceLogger.WithFields(log.Fields{
+			"OnuId":  s.Onu.ID,
+			"IntfId": s.Onu.PonPortID,
+			"OnuSn":  s.Onu.Sn(),
+			"Name":   s.Name,
+		}).Debug("DHCP flow is not for this service, ignoring")
+		return
+	}
+
+	// NOTE since we're matching the flow tag, this may not be required
+	if !s.NeedsDhcp {
+		serviceLogger.WithFields(log.Fields{
+			"OnuId":     s.Onu.ID,
+			"IntfId":    s.Onu.PonPortID,
+			"OnuSn":     s.Onu.Sn(),
+			"Name":      s.Name,
+			"NeedsDhcp": s.NeedsDhcp,
+		}).Debug("Won't start DHCP as it is not required")
+		return
+	}
+
+	// TODO check if the EAPOL flow was received before starting auth
+
+	if err := s.DHCPState.Event("start_dhcp"); err != nil {
+		serviceLogger.WithFields(log.Fields{
+			"OnuId":  s.Onu.ID,
+			"IntfId": s.Onu.PonPortID,
+			"OnuSn":  s.Onu.Sn(),
+			"Name":   s.Name,
+			"err":    err.Error(),
+		}).Error("Can't start DHCP for this Service")
+	} else {
+		if err := s.handleDHCPStart(stream); err != nil {
+			serviceLogger.WithFields(log.Fields{
+				"OnuId":  s.Onu.ID,
+				"IntfId": s.Onu.PonPortID,
+				"OnuSn":  s.Onu.Sn(),
+				"Name":   s.Name,
+				"err":    err,
+			}).Error("Error while sending DHCPDiscovery packet")
+			_ = s.DHCPState.Event("dhcp_failed")
+		}
+	}
+}
+
+func (s *Service) handleEapolStart(stream bbsimTypes.Stream) error {
+
+	serviceLogger.WithFields(log.Fields{
+		"OnuId":   s.Onu.ID,
+		"IntfId":  s.Onu.PonPortID,
+		"OnuSn":   s.Onu.Sn(),
+		"GemPort": s.GemPort,
+		"Name":    s.Name,
+	}).Debugf("handleEapolStart")
+
+	if err := eapol.SendEapStart(s.Onu.ID, s.Onu.PonPortID, s.Onu.Sn(), s.Onu.PortNo,
+		s.HwAddress, s.GemPort, s.EapolState, stream); err != nil {
+		serviceLogger.WithFields(log.Fields{
+			"OnuId":   s.Onu.ID,
+			"IntfId":  s.Onu.PonPortID,
+			"OnuSn":   s.Onu.Sn(),
+			"GemPort": s.GemPort,
+			"Name":    s.Name,
+		}).Error("handleEapolStart")
+		return err
+	}
+	return nil
+}
+
+func (s *Service) handleDHCPStart(stream bbsimTypes.Stream) error {
+
+	serviceLogger.WithFields(log.Fields{
+		"OnuId":     s.Onu.ID,
+		"IntfId":    s.Onu.PonPortID,
+		"OnuSn":     s.Onu.Sn(),
+		"Name":      s.Name,
+		"GemPortId": s.GemPort,
+	}).Debugf("HandleDHCPStart")
+
+	if err := dhcp.SendDHCPDiscovery(s.Onu.PonPort.Olt.ID, s.Onu.PonPortID, s.Onu.ID, int(s.CTag), s.GemPort,
+		s.Onu.Sn(), s.Onu.PortNo, s.DHCPState, s.HwAddress, stream); err != nil {
+		serviceLogger.WithFields(log.Fields{
+			"OnuId":     s.Onu.ID,
+			"IntfId":    s.Onu.PonPortID,
+			"OnuSn":     s.Onu.Sn(),
+			"Name":      s.Name,
+			"GemPortId": s.GemPort,
+		}).Error("HandleDHCPStart")
+		return err
+	}
+	return nil
+}
+
+func (s *Service) HandlePackets(stream bbsimTypes.Stream) {
+	serviceLogger.WithFields(log.Fields{
+		"OnuId":     s.Onu.ID,
+		"IntfId":    s.Onu.PonPortID,
+		"OnuSn":     s.Onu.Sn(),
+		"GemPortId": s.GemPort,
+		"Name":      s.Name,
+	}).Debug("Listening on Service Packet Channel")
+
+	defer func() {
+		serviceLogger.WithFields(log.Fields{
+			"OnuId":     s.Onu.ID,
+			"IntfId":    s.Onu.PonPortID,
+			"OnuSn":     s.Onu.Sn(),
+			"GemPortId": s.GemPort,
+			"Name":      s.Name,
+		}).Debug("Done Listening on Service Packet Channel")
+	}()
+
+	for msg := range s.PacketCh {
+		serviceLogger.WithFields(log.Fields{
+			"OnuId":       s.Onu.ID,
+			"IntfId":      s.Onu.PonPortID,
+			"OnuSn":       s.Onu.Sn(),
+			"Name":        s.Name,
+			"messageType": msg.Type,
+		}).Debug("Received message on Service Packet Channel")
+
+		if msg.Type == packetHandlers.EAPOL {
+			eapol.HandleNextPacket(msg.OnuId, msg.IntfId, s.GemPort, s.Onu.Sn(), s.Onu.PortNo, s.EapolState, msg.Packet, stream, nil)
+		} else if msg.Type == packetHandlers.DHCP {
+			_ = dhcp.HandleNextPacket(s.Onu.PonPort.Olt.ID, s.Onu.ID, s.Onu.PonPortID, s.Onu.Sn(), s.Onu.PortNo, s.CTag, s.GemPort, s.HwAddress, s.DHCPState, msg.Packet, stream)
+		}
+	}
+}
+
+func (s *Service) logStateChange(stateMachine string, src string, dst string) {
+	serviceLogger.WithFields(log.Fields{
+		"OnuId":  s.Onu.ID,
+		"IntfId": s.Onu.PonPortID,
+		"OnuSn":  s.Onu.Sn(),
+		"Name":   s.Name,
+	}).Debugf("Changing Service.%s InternalState from %s to %s", stateMachine, src, dst)
+}