[VOL-3880] Correctly reporting software image status in OMCI Get
[VOL-3900] OMCI ONU Software Image Download

Change-Id: I8d91be832f3a89404d0af0dd98e6b53359e6a738
diff --git a/internal/common/omci/get.go b/internal/common/omci/get.go
index e47dc0c..5172f60 100644
--- a/internal/common/omci/get.go
+++ b/internal/common/omci/get.go
@@ -45,7 +45,7 @@
 	return msgObj, nil
 }
 
-func CreateGetResponse(omciPkt gopacket.Packet, omciMsg *omci.OMCI, onuSn *openolt.SerialNumber, mds uint8) ([]byte, error) {
+func CreateGetResponse(omciPkt gopacket.Packet, omciMsg *omci.OMCI, onuSn *openolt.SerialNumber, mds uint8, activeImageEntityId uint16, committedImageEntityId uint16) ([]byte, error) {
 
 	msgObj, err := ParseGetRequest(omciPkt)
 
@@ -66,7 +66,7 @@
 	case me.OnuGClassID:
 		response = createOnugResponse(msgObj.AttributeMask, msgObj.EntityInstance, onuSn)
 	case me.SoftwareImageClassID:
-		response = createSoftwareImageResponse(msgObj.AttributeMask, msgObj.EntityInstance)
+		response = createSoftwareImageResponse(msgObj.AttributeMask, msgObj.EntityInstance, activeImageEntityId, committedImageEntityId)
 	case me.IpHostConfigDataClassID:
 		response = createIpHostResponse(msgObj.AttributeMask, msgObj.EntityInstance)
 	case me.UniGClassID:
@@ -200,9 +200,24 @@
 	//}
 }
 
-func createSoftwareImageResponse(attributeMask uint16, entityInstance uint16) *omci.GetResponse {
+func createSoftwareImageResponse(attributeMask uint16, entityInstance uint16, activeImageEntityId uint16, committedImageEntityId uint16) *omci.GetResponse {
+
+	omciLogger.WithFields(log.Fields{
+		"EntityInstance": entityInstance,
+	}).Info("received-get-software-image-request")
+
+	// Only one image can be active and committed
+	committed := 0
+	active := 0
+	if entityInstance == activeImageEntityId {
+		active = 1
+	}
+	if entityInstance == committedImageEntityId {
+		committed = 1
+	}
+
 	// NOTE that we need send the response for the correct ME Instance or the adapter won't process it
-	return &omci.GetResponse{
+	res := &omci.GetResponse{
 		MeBasePacket: omci.MeBasePacket{
 			EntityClass:    me.SoftwareImageClassID,
 			EntityInstance: entityInstance,
@@ -210,8 +225,8 @@
 		Attributes: me.AttributeValueMap{
 			"ManagedEntityId": 0,
 			"Version":         toOctets("00000000000001", 14),
-			"IsCommitted":     1,
-			"IsActive":        1,
+			"IsCommitted":     committed,
+			"IsActive":        active,
 			"IsValid":         1,
 			"ProductCode":     toOctets("product-code", 25),
 			"ImageHash":       toOctets("broadband-sim", 16),
@@ -219,6 +234,15 @@
 		Result:        me.Success,
 		AttributeMask: attributeMask,
 	}
+
+	omciLogger.WithFields(log.Fields{
+		"omciMessage": res,
+		"entityId":    entityInstance,
+		"active":      active,
+		"committed":   committed,
+	}).Info("Reporting SoftwareImage")
+
+	return res
 }
 
 func createIpHostResponse(attributeMask uint16, entityInstance uint16) *omci.GetResponse {
diff --git a/internal/common/omci/image.go b/internal/common/omci/image.go
new file mode 100644
index 0000000..8480ecb
--- /dev/null
+++ b/internal/common/omci/image.go
@@ -0,0 +1,309 @@
+/*
+ * 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 omci
+
+import (
+	"encoding/hex"
+	"errors"
+	"github.com/google/gopacket"
+	"github.com/opencord/omci-lib-go"
+	me "github.com/opencord/omci-lib-go/generated"
+	log "github.com/sirupsen/logrus"
+	"math"
+	"strconv"
+)
+
+func ParseStartSoftwareDownloadRequest(omciPkt gopacket.Packet) (*omci.StartSoftwareDownloadRequest, error) {
+	msgLayer := omciPkt.Layer(omci.LayerTypeStartSoftwareDownloadRequest)
+	if msgLayer == nil {
+		err := "omci Msg layer could not be detected for LayerTypeStartSoftwareDownloadRequest"
+		omciLogger.Error(err)
+		return nil, errors.New(err)
+	}
+	msgObj, msgOk := msgLayer.(*omci.StartSoftwareDownloadRequest)
+	if !msgOk {
+		err := "omci Msg layer could not be assigned for LayerTypeStartSoftwareDownloadRequest"
+		omciLogger.Error(err)
+		return nil, errors.New(err)
+	}
+	return msgObj, nil
+}
+
+func ParseDownloadSectionRequest(omciPkt gopacket.Packet) (*omci.DownloadSectionRequest, error) {
+	msgLayer := omciPkt.Layer(omci.LayerTypeDownloadSectionRequest)
+	if msgLayer == nil {
+		err := "omci Msg layer could not be detected for LayerTypeDownloadSectionRequest"
+		omciLogger.Error(err)
+		return nil, errors.New(err)
+	}
+	msgObj, msgOk := msgLayer.(*omci.DownloadSectionRequest)
+	if !msgOk {
+		err := "omci Msg layer could not be assigned for LayerTypeDownloadSectionRequest"
+		omciLogger.Error(err)
+		return nil, errors.New(err)
+	}
+	return msgObj, nil
+}
+
+func ParseEndSoftwareDownloadRequest(omciPkt gopacket.Packet) (*omci.EndSoftwareDownloadRequest, error) {
+	msgLayer := omciPkt.Layer(omci.LayerTypeEndSoftwareDownloadRequest)
+	if msgLayer == nil {
+		err := "omci Msg layer could not be detected for LayerTypeEndSoftwareDownloadRequest"
+		omciLogger.Error(err)
+		return nil, errors.New(err)
+	}
+	msgObj, msgOk := msgLayer.(*omci.EndSoftwareDownloadRequest)
+	if !msgOk {
+		err := "omci Msg layer could not be assigned for LayerTypeEndSoftwareDownloadRequest"
+		omciLogger.Error(err)
+		return nil, errors.New(err)
+	}
+	return msgObj, nil
+}
+
+func ParseActivateSoftwareRequest(omciPkt gopacket.Packet) (*omci.ActivateSoftwareRequest, error) {
+	msgLayer := omciPkt.Layer(omci.LayerTypeActivateSoftwareRequest)
+	if msgLayer == nil {
+		err := "omci Msg layer could not be detected for LayerTypeActivateSoftwareRequest"
+		omciLogger.Error(err)
+		return nil, errors.New(err)
+	}
+	msgObj, msgOk := msgLayer.(*omci.ActivateSoftwareRequest)
+	if !msgOk {
+		err := "omci Msg layer could not be assigned for LayerTypeActivateSoftwareRequest"
+		omciLogger.Error(err)
+		return nil, errors.New(err)
+	}
+	return msgObj, nil
+}
+
+func ParseCommitSoftwareRequest(omciPkt gopacket.Packet) (*omci.CommitSoftwareRequest, error) {
+	msgLayer := omciPkt.Layer(omci.LayerTypeCommitSoftwareRequest)
+	if msgLayer == nil {
+		err := "omci Msg layer could not be detected for LayerTypeCommitSoftwareRequest"
+		omciLogger.Error(err)
+		return nil, errors.New(err)
+	}
+	msgObj, msgOk := msgLayer.(*omci.CommitSoftwareRequest)
+	if !msgOk {
+		err := "omci Msg layer could not be assigned for LayerTypeCommitSoftwareRequest"
+		omciLogger.Error(err)
+		return nil, errors.New(err)
+	}
+	return msgObj, nil
+}
+
+func CreateStartSoftwareDownloadResponse(omciPkt gopacket.Packet, omciMsg *omci.OMCI) ([]byte, error) {
+	responeCode := me.Success
+	msgObj, err := ParseStartSoftwareDownloadRequest(omciPkt)
+	if err != nil {
+		responeCode = me.ProcessingError
+	}
+
+	omciLogger.WithFields(log.Fields{
+		"OmciMsgType":          omciMsg.MessageType,
+		"TransCorrId":          omciMsg.TransactionID,
+		"EntityInstance":       msgObj.EntityInstance,
+		"WindowSize":           msgObj.WindowSize,
+		"ImageSize":            msgObj.ImageSize,
+		"NumberOfCircuitPacks": msgObj.NumberOfCircuitPacks,
+		"CircuitPacks":         msgObj.CircuitPacks,
+	}).Debug("received-start-software-download-request")
+
+	response := &omci.StartSoftwareDownloadResponse{
+		MeBasePacket: omci.MeBasePacket{
+			EntityClass:    msgObj.EntityClass,
+			EntityInstance: msgObj.EntityInstance,
+		},
+		WindowSize:        msgObj.WindowSize,
+		Result:            responeCode,
+		NumberOfInstances: 0,                        // NOTE this can't be bigger than 0 this we can populate downloadResults
+		MeResults:         []omci.DownloadResults{}, // FIXME downloadResults is not exported
+	}
+
+	pkt, err := Serialize(omci.StartSoftwareDownloadResponseType, response, omciMsg.TransactionID)
+	if err != nil {
+		omciLogger.WithFields(log.Fields{
+			"Err": err,
+		}).Error("cannot-Serialize-CreateResponse")
+		return nil, err
+	}
+
+	log.WithFields(log.Fields{
+		"TxID": strconv.FormatInt(int64(omciMsg.TransactionID), 16),
+		"pkt":  hex.EncodeToString(pkt),
+	}).Trace("omci-start-software-download-response")
+
+	return pkt, nil
+}
+
+func CreateDownloadSectionResponse(omciPkt gopacket.Packet, omciMsg *omci.OMCI) ([]byte, error) {
+
+	msgObj, err := ParseDownloadSectionRequest(omciPkt)
+
+	if err != nil {
+		return nil, err
+	}
+
+	omciLogger.WithFields(log.Fields{
+		"OmciMsgType":    omciMsg.MessageType,
+		"TransCorrId":    omciMsg.TransactionID,
+		"EntityInstance": msgObj.EntityInstance,
+		"SectionNumber":  msgObj.SectionNumber,
+	}).Debug("received-download-section-request")
+
+	response := &omci.DownloadSectionResponse{
+		MeBasePacket: omci.MeBasePacket{
+			EntityClass:    msgObj.EntityClass,
+			EntityInstance: msgObj.EntityInstance,
+		},
+		Result:        me.Success,
+		SectionNumber: msgObj.SectionNumber,
+	}
+
+	pkt, err := Serialize(omci.DownloadSectionResponseType, response, omciMsg.TransactionID)
+	if err != nil {
+		return nil, err
+	}
+
+	log.WithFields(log.Fields{
+		"TxID": strconv.FormatInt(int64(omciMsg.TransactionID), 16),
+		"pkt":  hex.EncodeToString(pkt),
+	}).Trace("omci-download-section-with-response-response")
+
+	return pkt, nil
+}
+
+func CreateEndSoftwareDownloadResponse(omciPkt gopacket.Packet, omciMsg *omci.OMCI, status me.Results) ([]byte, error) {
+
+	msgObj, err := ParseEndSoftwareDownloadRequest(omciPkt)
+
+	if err != nil {
+		return nil, err
+	}
+
+	omciLogger.WithFields(log.Fields{
+		"OmciMsgType":       omciMsg.MessageType,
+		"TransCorrId":       omciMsg.TransactionID,
+		"EntityInstance":    msgObj.EntityInstance,
+		"Crc32":             msgObj.CRC32,
+		"ImageSize":         msgObj.ImageSize,
+		"NumberOfInstances": msgObj.NumberOfInstances,
+		"ImageInstances":    msgObj.ImageInstances,
+	}).Debug("received-end-software-download-request")
+
+	response := &omci.EndSoftwareDownloadResponse{
+		MeBasePacket: omci.MeBasePacket{
+			EntityClass:    msgObj.EntityClass,
+			EntityInstance: msgObj.EntityInstance,
+		},
+		Result:            status,
+		NumberOfInstances: 0, // NOTE this can't be bigger than 0 this we can populate downloadResults
+		//MeResults: []omci.downloadResults{}, // FIXME downloadResults is not exported
+	}
+
+	pkt, err := Serialize(omci.EndSoftwareDownloadResponseType, response, omciMsg.TransactionID)
+	if err != nil {
+		return nil, err
+	}
+
+	log.WithFields(log.Fields{
+		"TxID": strconv.FormatInt(int64(omciMsg.TransactionID), 16),
+		"pkt":  hex.EncodeToString(pkt),
+	}).Trace("omci-end-software-download-response")
+
+	return pkt, nil
+}
+
+func CreateActivateSoftwareResponse(omciPkt gopacket.Packet, omciMsg *omci.OMCI) ([]byte, error) {
+
+	msgObj, err := ParseActivateSoftwareRequest(omciPkt)
+
+	if err != nil {
+		return nil, err
+	}
+
+	omciLogger.WithFields(log.Fields{
+		"OmciMsgType":    omciMsg.MessageType,
+		"TransCorrId":    omciMsg.TransactionID,
+		"EntityInstance": msgObj.EntityInstance,
+		"ActivateFlags":  msgObj.ActivateFlags,
+	}).Debug("received-activate-software-request")
+
+	response := &omci.ActivateSoftwareResponse{
+		MeBasePacket: omci.MeBasePacket{
+			EntityClass:    msgObj.EntityClass,
+			EntityInstance: msgObj.EntityInstance,
+		},
+		Result: me.Success,
+	}
+
+	pkt, err := Serialize(omci.ActivateSoftwareResponseType, response, omciMsg.TransactionID)
+	if err != nil {
+		return nil, err
+	}
+
+	log.WithFields(log.Fields{
+		"TxID": strconv.FormatInt(int64(omciMsg.TransactionID), 16),
+		"pkt":  hex.EncodeToString(pkt),
+	}).Trace("omci-activate-software-response")
+
+	return pkt, nil
+}
+
+func CreateCommitSoftwareResponse(omciPkt gopacket.Packet, omciMsg *omci.OMCI) ([]byte, error) {
+
+	msgObj, err := ParseCommitSoftwareRequest(omciPkt)
+
+	if err != nil {
+		return nil, err
+	}
+
+	omciLogger.WithFields(log.Fields{
+		"OmciMsgType":    omciMsg.MessageType,
+		"TransCorrId":    omciMsg.TransactionID,
+		"EntityInstance": msgObj.EntityInstance,
+	}).Debug("received-commit-software-request")
+
+	response := &omci.CommitSoftwareResponse{
+		MeBasePacket: omci.MeBasePacket{
+			EntityClass:    msgObj.EntityClass,
+			EntityInstance: msgObj.EntityInstance,
+		},
+	}
+
+	pkt, err := Serialize(omci.CommitSoftwareResponseType, response, omciMsg.TransactionID)
+	if err != nil {
+		return nil, err
+	}
+
+	log.WithFields(log.Fields{
+		"TxID": strconv.FormatInt(int64(omciMsg.TransactionID), 16),
+		"pkt":  hex.EncodeToString(pkt),
+	}).Trace("omci-commit-software-response")
+
+	return pkt, nil
+}
+
+func ComputeDownloadSectionsCount(pkt gopacket.Packet) int {
+	msgObj, err := ParseStartSoftwareDownloadRequest(pkt)
+	if err != nil {
+		omciLogger.Error("cannot-parse-start-software-download-request")
+	}
+
+	return int(math.Ceil(float64(msgObj.ImageSize) / float64(msgObj.WindowSize)))
+}
diff --git a/internal/common/omci/image_test.go b/internal/common/omci/image_test.go
new file mode 100644
index 0000000..ad07eac
--- /dev/null
+++ b/internal/common/omci/image_test.go
@@ -0,0 +1,93 @@
+/*
+ * 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 omci
+
+import (
+	"github.com/google/gopacket"
+	"github.com/opencord/omci-lib-go"
+	me "github.com/opencord/omci-lib-go/generated"
+	"gotest.tools/assert"
+	"testing"
+)
+
+func omciToStartSoftwareDownloadResponse(t *testing.T, omciPkt *gopacket.Packet) *omci.StartSoftwareDownloadResponse {
+	msgLayer := (*omciPkt).Layer(omci.LayerTypeStartSoftwareDownloadResponse)
+	if msgLayer == nil {
+		t.Fatal("omci Msg layer could not be detected for StartSoftwareDownloadResponse")
+	}
+	msgObj, msgOk := msgLayer.(*omci.StartSoftwareDownloadResponse)
+	if !msgOk {
+		t.Fatal("omci Msg layer could not be assigned for StartSoftwareDownloadResponse")
+	}
+	return msgObj
+}
+
+func TestCreateStartSoftwareDownloadResponse(t *testing.T) {
+	omciReq := &omci.StartSoftwareDownloadRequest{
+		MeBasePacket: omci.MeBasePacket{
+			EntityClass:    me.SoftwareImageClassID,
+			EntityInstance: 1,
+		},
+		ImageSize:            32768,
+		NumberOfCircuitPacks: 1,
+		WindowSize:           31,
+		CircuitPacks:         []uint16{0},
+	}
+
+	omciReqPkt, err := Serialize(omci.StartSoftwareDownloadRequestType, omciReq, 66)
+	if err != nil {
+		t.Fatal(err.Error())
+	}
+
+	omciReqPkt, _ = HexEncode(omciReqPkt)
+
+	// start test
+	pkt, msg, _ := ParseOpenOltOmciPacket(omciReqPkt)
+	responsePkt, err := CreateStartSoftwareDownloadResponse(pkt, msg)
+	assert.NilError(t, err)
+
+	omciResponseMsg, omciResponsePkt := omciBytesToMsg(t, responsePkt)
+	assert.Equal(t, omciResponseMsg.MessageType, omci.StartSoftwareDownloadResponseType)
+
+	getResponseLayer := omciToStartSoftwareDownloadResponse(t, omciResponsePkt)
+
+	assert.Equal(t, getResponseLayer.Result, me.Success)
+}
+
+func TestComputeDownloadSectionsCount(t *testing.T) {
+	omciReq := &omci.StartSoftwareDownloadRequest{
+		MeBasePacket: omci.MeBasePacket{
+			EntityClass:    me.SoftwareImageClassID,
+			EntityInstance: 1,
+		},
+		ImageSize:            32768,
+		NumberOfCircuitPacks: 1,
+		WindowSize:           31,
+		CircuitPacks:         []uint16{0},
+	}
+
+	omciReqPkt, err := Serialize(omci.StartSoftwareDownloadRequestType, omciReq, 66)
+	if err != nil {
+		t.Fatal(err.Error())
+	}
+
+	omciReqPkt, _ = HexEncode(omciReqPkt)
+	pkt, _, _ := ParseOpenOltOmciPacket(omciReqPkt)
+
+	count := ComputeDownloadSectionsCount(pkt)
+	assert.Equal(t, count, 1058)
+}
diff --git a/internal/common/omci/mib_test.go b/internal/common/omci/mib_test.go
index f569bce..a4ffbb7 100644
--- a/internal/common/omci/mib_test.go
+++ b/internal/common/omci/mib_test.go
@@ -60,7 +60,7 @@
 
 func createTestMibUploadNextArgs(t *testing.T, tid uint16, seqNumber uint16) mibArgs {
 	mibUploadNext, _ := CreateMibUploadNextRequest(tid, seqNumber)
-	mibUploadNext = hexDecode(mibUploadNext)
+	mibUploadNext = HexDecode(mibUploadNext)
 	mibUploadNextMsg, mibUploadNextPkt := omciBytesToMsg(t, mibUploadNext)
 
 	return mibArgs{
diff --git a/internal/common/omci/omci_base.go b/internal/common/omci/omci_base.go
index 7a9f499..c5caf41 100644
--- a/internal/common/omci/omci_base.go
+++ b/internal/common/omci/omci_base.go
@@ -29,7 +29,7 @@
 // ParseOpenOltOmciPacket receive an OMCI packet in the openolt format and returns
 // an OMCI Layer as per omci-lib-go
 func ParseOpenOltOmciPacket(pkt []byte) (gopacket.Packet, *omci.OMCI, error) {
-	rxMsg := hexDecode(pkt)
+	rxMsg := HexDecode(pkt)
 
 	// NOTE this is may not be needed, VOLTHA sends the correct message
 	if len(rxMsg) >= 44 {
@@ -63,8 +63,8 @@
 	return packet, parsed, nil
 }
 
-// hexDecode converts the hex encoding to binary
-func hexDecode(pkt []byte) []byte {
+// 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 ;)