[SEBA-817][SEBA-821]
Adding c/s tags and hw address in the onu struct
DHCP State machine completed
Cleaned up logs

Change-Id: Iadb1d3967befe1c402e302a552b67faa2701f5c5
diff --git a/internal/bbsim/responders/dhcp/dhcp.go b/internal/bbsim/responders/dhcp/dhcp.go
new file mode 100644
index 0000000..d2a3db7
--- /dev/null
+++ b/internal/bbsim/responders/dhcp/dhcp.go
@@ -0,0 +1,387 @@
+/*
+ * 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 dhcp
+
+import (
+	"errors"
+	"fmt"
+	"github.com/google/gopacket"
+	"github.com/google/gopacket/layers"
+	"github.com/looplab/fsm"
+	bbsim "github.com/opencord/bbsim/internal/bbsim/types"
+	omci "github.com/opencord/omci-sim"
+	"github.com/opencord/voltha-protos/go/openolt"
+	log "github.com/sirupsen/logrus"
+	"net"
+	"reflect"
+)
+
+var dhcpLogger = log.WithFields(log.Fields{
+	"module": "DHCP",
+})
+
+var defaultParamsRequestList = []layers.DHCPOpt{
+	layers.DHCPOptSubnetMask,
+	layers.DHCPOptBroadcastAddr,
+	layers.DHCPOptTimeOffset,
+	layers.DHCPOptRouter,
+	layers.DHCPOptDomainName,
+	layers.DHCPOptDNS,
+	layers.DHCPOptDomainSearch,
+	layers.DHCPOptHostname,
+	layers.DHCPOptNetBIOSTCPNS,
+	layers.DHCPOptNetBIOSTCPScope,
+	layers.DHCPOptInterfaceMTU,
+	layers.DHCPOptClasslessStaticRoute,
+	layers.DHCPOptNTPServers,
+}
+
+func createDefaultDHCPReq(intfId uint32, onuId uint32) layers.DHCPv4 {
+	return layers.DHCPv4{
+		Operation:    layers.DHCPOpRequest,
+		HardwareType: layers.LinkTypeEthernet,
+		HardwareLen:  6,
+		HardwareOpts: 0,
+		Xid:          onuId,
+		ClientHWAddr: net.HardwareAddr{0x2e, 0x60, 0x70, 0x13, byte(intfId), byte(onuId)},
+	}
+}
+
+func createDefaultOpts() []layers.DHCPOption {
+	hostname := []byte("bbsim.onf.org")
+	opts := []layers.DHCPOption{}
+	opts = append(opts, layers.DHCPOption{
+		Type:   layers.DHCPOptHostname,
+		Data:   hostname,
+		Length: uint8(len(hostname)),
+	})
+
+	bytes := []byte{}
+	for _, option := range defaultParamsRequestList {
+		bytes = append(bytes, byte(option))
+	}
+
+	opts = append(opts, layers.DHCPOption{
+		Type:   layers.DHCPOptParamsRequest,
+		Data:   bytes,
+		Length: uint8(len(bytes)),
+	})
+	return opts
+}
+
+func createDHCPDisc(intfId uint32, onuId uint32) *layers.DHCPv4 {
+	dhcpLayer := createDefaultDHCPReq(intfId, onuId)
+	defaultOpts := createDefaultOpts()
+	dhcpLayer.Options = append([]layers.DHCPOption{layers.DHCPOption{
+		Type:   layers.DHCPOptMessageType,
+		Data:   []byte{byte(layers.DHCPMsgTypeDiscover)},
+		Length: 1,
+	}}, defaultOpts...)
+
+	return &dhcpLayer
+}
+
+func createDHCPReq(intfId uint32, onuId uint32) *layers.DHCPv4 {
+	dhcpLayer := createDefaultDHCPReq(intfId, onuId)
+	defaultOpts := createDefaultOpts()
+
+	dhcpLayer.Options = append(defaultOpts, layers.DHCPOption{
+		Type:   layers.DHCPOptMessageType,
+		Data:   []byte{byte(layers.DHCPMsgTypeRequest)},
+		Length: 1,
+	})
+
+	data := []byte{182, 21, 0, 128}
+	dhcpLayer.Options = append(dhcpLayer.Options, layers.DHCPOption{
+		Type:   layers.DHCPOptServerID,
+		Data:   data,
+		Length: uint8(len(data)),
+	})
+
+	data = []byte{0xcd, 0x28, 0xcb, 0xcc, 0x00, 0x01, 0x00, 0x01,
+		0x23, 0xed, 0x11, 0xec, 0x4e, 0xfc, 0xcd, 0x28, 0xcb, 0xcc}
+	dhcpLayer.Options = append(dhcpLayer.Options, layers.DHCPOption{
+		Type:   layers.DHCPOptClientID,
+		Data:   data,
+		Length: uint8(len(data)),
+	})
+
+	data = []byte{182, 21, 0, byte(onuId)}
+	dhcpLayer.Options = append(dhcpLayer.Options, layers.DHCPOption{
+		Type:   layers.DHCPOptRequestIP,
+		Data:   data,
+		Length: uint8(len(data)),
+	})
+	return &dhcpLayer
+}
+
+func serializeDHCPPacket(intfId uint32, onuId uint32, srcMac net.HardwareAddr, dhcp *layers.DHCPv4) ([]byte, error) {
+	buffer := gopacket.NewSerializeBuffer()
+	options := gopacket.SerializeOptions{
+		ComputeChecksums: true,
+		FixLengths:       true,
+	}
+
+	ethernetLayer := &layers.Ethernet{
+		SrcMAC:       srcMac,
+		DstMAC:       net.HardwareAddr{0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
+		EthernetType: layers.EthernetTypeIPv4,
+	}
+
+	ipLayer := &layers.IPv4{
+		Version:  4,
+		TOS:      0x10,
+		TTL:      128,
+		SrcIP:    []byte{0, 0, 0, 0},
+		DstIP:    []byte{255, 255, 255, 255},
+		Protocol: layers.IPProtocolUDP,
+	}
+
+	udpLayer := &layers.UDP{
+		SrcPort: 68,
+		DstPort: 67,
+	}
+
+	udpLayer.SetNetworkLayerForChecksum(ipLayer)
+	if err := gopacket.SerializeLayers(buffer, options, ethernetLayer, ipLayer, udpLayer, dhcp); err != nil {
+		return nil, err
+	}
+
+	bytes := buffer.Bytes()
+	return bytes, nil
+}
+
+func getDhcpLayer(pkt gopacket.Packet) (*layers.DHCPv4, error) {
+	layerDHCP := pkt.Layer(layers.LayerTypeDHCPv4)
+	dhcp, _ := layerDHCP.(*layers.DHCPv4)
+	if dhcp == nil {
+		return nil, errors.New("Failed-to-extract-DHCP-layer")
+	}
+	return dhcp, nil
+}
+
+func getDhcpMessageType(dhcp *layers.DHCPv4) (layers.DHCPMsgType, error) {
+	for _, option := range dhcp.Options {
+		if option.Type == layers.DHCPOptMessageType {
+			if reflect.DeepEqual(option.Data, []byte{byte(layers.DHCPMsgTypeOffer)}) {
+				return layers.DHCPMsgTypeOffer, nil
+			} else if reflect.DeepEqual(option.Data, []byte{byte(layers.DHCPMsgTypeAck)}) {
+				return layers.DHCPMsgTypeAck, nil
+			} else if reflect.DeepEqual(option.Data, []byte{byte(layers.DHCPMsgTypeRelease)}) {
+				return layers.DHCPMsgTypeRelease, nil
+			} else {
+				msg := fmt.Sprintf("This type %x is not supported", option.Data)
+				return 0, errors.New(msg)
+			}
+		}
+	}
+	return 0, errors.New("Failed to extract MsgType from dhcp")
+}
+
+func sendDHCPPktIn(msg bbsim.ByteMsg, stream openolt.Openolt_EnableIndicationServer) error {
+	// FIXME unify sendDHCPPktIn and sendEapolPktIn methods
+	gemid, err := omci.GetGemPortId(msg.IntfId, msg.OnuId)
+	if err != nil {
+		dhcpLogger.WithFields(log.Fields{
+			"OnuId":  msg.OnuId,
+			"IntfId": msg.IntfId,
+		}).Errorf("Can't retrieve GemPortId: %s", err)
+		return err
+	}
+	data := &openolt.Indication_PktInd{PktInd: &openolt.PacketIndication{
+		IntfType: "pon", IntfId: msg.IntfId, GemportId: uint32(gemid), Pkt: msg.Bytes,
+	}}
+
+	if err := stream.Send(&openolt.Indication{Data: data}); err != nil {
+		dhcpLogger.Errorf("Fail to send DHCP PktInd indication. %v", err)
+		return err
+	}
+	return nil
+}
+
+func sendDHCPDiscovery(ponPortId uint32, onuId uint32, serialNumber string, onuHwAddress net.HardwareAddr, cTag int, stream openolt.Openolt_EnableIndicationServer) error {
+	dhcp := createDHCPDisc(ponPortId, onuId)
+	pkt, err := serializeDHCPPacket(ponPortId, onuId, onuHwAddress, dhcp)
+	if err != nil {
+		dhcpLogger.Errorf("Cannot serializeDHCPPacket: %s", err)
+		return err
+	}
+	// NOTE I don't think we need to tag the packet
+	//taggedPkt, err := packetHandlers.PushSingleTag(cTag, pkt)
+
+	msg := bbsim.ByteMsg{
+		IntfId: ponPortId,
+		OnuId:  onuId,
+		Bytes:  pkt,
+	}
+
+	if err := sendDHCPPktIn(msg, stream); err != nil {
+		return err
+	}
+	dhcpLogger.WithFields(log.Fields{
+		"OnuId":  onuId,
+		"IntfId": ponPortId,
+		"OnuSn":  serialNumber,
+	}).Infof("DHCPDiscovery Sent")
+	return nil
+}
+
+func sendDHCPRequest(ponPortId uint32, onuId uint32, serialNumber string, onuHwAddress net.HardwareAddr, cTag int, stream openolt.Openolt_EnableIndicationServer) error {
+	dhcp := createDHCPReq(ponPortId, onuId)
+	pkt, err := serializeDHCPPacket(ponPortId, onuId, onuHwAddress, dhcp)
+
+	if err != nil {
+		dhcpLogger.Errorf("Cannot serializeDHCPPacket: %s", err)
+		return err
+	}
+	// NOTE I don't think we need to tag the packet
+	//taggedPkt, err := packetHandlers.PushSingleTag(cTag, pkt)
+
+	msg := bbsim.ByteMsg{
+		IntfId: ponPortId,
+		OnuId:  onuId,
+		Bytes:  pkt,
+	}
+
+	if err := sendDHCPPktIn(msg, stream); err != nil {
+		return err
+	}
+	dhcpLogger.WithFields(log.Fields{
+		"OnuId":  onuId,
+		"IntfId": ponPortId,
+		"OnuSn":  serialNumber,
+	}).Infof("DHCPDiscovery Sent")
+	return nil
+}
+
+func CreateDHCPClient(onuId uint32, ponPortId uint32, serialNumber string, onuHwAddress net.HardwareAddr, cTag int, onuStateMachine *fsm.FSM, stream openolt.Openolt_EnableIndicationServer, pktOutCh chan *bbsim.ByteMsg) {
+	// NOTE pckOutCh is channel to listen on for packets received by VOLTHA
+	// the OLT device will publish messages on that channel
+
+	dhcpLogger.WithFields(log.Fields{
+		"OnuId":  onuId,
+		"IntfId": ponPortId,
+		"OnuSn":  serialNumber,
+	}).Infof("DHCP State Machine starting")
+
+	defer dhcpLogger.WithFields(log.Fields{
+		"OnuId":  onuId,
+		"IntfId": ponPortId,
+		"OnuSn":  serialNumber,
+	}).Infof("DHCP State machine completed")
+
+	// Send DHCP Discovery packet
+	if err := sendDHCPDiscovery(ponPortId, onuId, serialNumber, onuHwAddress, cTag, stream); err != nil {
+		dhcpLogger.WithFields(log.Fields{
+			"OnuId":  onuId,
+			"IntfId": ponPortId,
+			"OnuSn":  serialNumber,
+		}).Errorf("Can't send DHCP Discovery: %s", err)
+		if err := onuStateMachine.Event("dhcp_failed"); err != nil {
+			dhcpLogger.WithFields(log.Fields{
+				"OnuId":  onuId,
+				"IntfId": ponPortId,
+				"OnuSn":  serialNumber,
+			}).Errorf("Error while transitioning ONU State %v", err)
+		}
+		return
+	}
+
+	if err := onuStateMachine.Event("dhcp_discovery_sent"); err != nil {
+		dhcpLogger.WithFields(log.Fields{
+			"OnuId":  onuId,
+			"IntfId": ponPortId,
+			"OnuSn":  serialNumber,
+		}).Errorf("Error while transitioning ONU State %v", err)
+	}
+
+	dhcpLogger.WithFields(log.Fields{
+		"OnuId":  onuId,
+		"IntfId": ponPortId,
+		"OnuSn":  serialNumber,
+	}).Infof("Listening on dhcpPktOutCh")
+
+	for msg := range pktOutCh {
+		dhcpLogger.Tracef("Received DHCP message %v", msg)
+
+		pkt := gopacket.NewPacket(msg.Bytes, layers.LayerTypeEthernet, gopacket.Default)
+		dhcpLayer, err := getDhcpLayer(pkt)
+		if err != nil {
+			dhcpLogger.WithFields(log.Fields{
+				"OnuId":  onuId,
+				"IntfId": ponPortId,
+				"OnuSn":  serialNumber,
+			}).Errorf("Can't get DHCP Layer from Packet: %v", err)
+			continue
+		}
+		dhcpMessageType, err := getDhcpMessageType(dhcpLayer)
+		if err != nil {
+			dhcpLogger.WithFields(log.Fields{
+				"OnuId":  onuId,
+				"IntfId": ponPortId,
+				"OnuSn":  serialNumber,
+			}).Errorf("Can't get DHCP Message Type from DHCP Layer: %v", err)
+			continue
+		}
+
+		if dhcpLayer.Operation == layers.DHCPOpReply {
+			if dhcpMessageType == layers.DHCPMsgTypeOffer {
+				if err := sendDHCPRequest(ponPortId, onuId, serialNumber, onuHwAddress, cTag, stream); err != nil {
+					dhcpLogger.WithFields(log.Fields{
+						"OnuId":  onuId,
+						"IntfId": ponPortId,
+						"OnuSn":  serialNumber,
+					}).Errorf("Can't send DHCP Request: %s", err)
+					if err := onuStateMachine.Event("dhcp_failed"); err != nil {
+						dhcpLogger.WithFields(log.Fields{
+							"OnuId":  onuId,
+							"IntfId": ponPortId,
+							"OnuSn":  serialNumber,
+						}).Errorf("Error while transitioning ONU State %v", err)
+					}
+					return
+				}
+				if err := onuStateMachine.Event("dhcp_request_sent"); err != nil {
+					dhcpLogger.WithFields(log.Fields{
+						"OnuId":  onuId,
+						"IntfId": ponPortId,
+						"OnuSn":  serialNumber,
+					}).Errorf("Error while transitioning ONU State %v", err)
+				}
+
+			} else if dhcpMessageType == layers.DHCPMsgTypeAck {
+				// NOTE once the ack is received we don't need to do anything but change the state
+				if err := onuStateMachine.Event("dhcp_ack_received"); err != nil {
+					dhcpLogger.WithFields(log.Fields{
+						"OnuId":  onuId,
+						"IntfId": ponPortId,
+						"OnuSn":  serialNumber,
+					}).Errorf("Error while transitioning ONU State %v", err)
+				}
+				return
+			}
+			// NOTE do we need to care about DHCPMsgTypeRelease??
+		} else {
+			dhcpLogger.WithFields(log.Fields{
+				"OnuId":  onuId,
+				"IntfId": ponPortId,
+				"OnuSn":  serialNumber,
+			}).Warnf("Unsupported DHCP Operation: %s", dhcpLayer.Operation.String())
+			continue
+		}
+	}
+}