[VOL-4466]  support multi onu sw section download

Change-Id: Iee29866f22a1b3de65aa257fdfaf46112d058c01
diff --git a/internal/pkg/core/device_handler.go b/internal/pkg/core/device_handler.go
index 822d83e..2bdbbb1 100644
--- a/internal/pkg/core/device_handler.go
+++ b/internal/pkg/core/device_handler.go
@@ -1148,6 +1148,83 @@
 // 	return olterrors.NewErrInvalidValue(log.Fields{"inter-adapter-message-type": msg.Header.Type}, nil)
 // }
 
+// ProxyOmciRequests sends the proxied OMCI message to the target device
+func (dh *DeviceHandler) ProxyOmciRequests(ctx context.Context, omciMsgs *ia.OmciMessages) error {
+	if omciMsgs.GetProxyAddress() == nil {
+		onuDevice, err := dh.getDeviceFromCore(ctx, omciMsgs.ChildDeviceId)
+		if err != nil {
+			return olterrors.NewErrNotFound("onu", log.Fields{
+				"parent-device-id": dh.device.Id,
+				"child-device-id":  omciMsgs.ChildDeviceId}, err)
+		}
+		logger.Debugw(ctx, "device-retrieved-from-core", log.Fields{"onu-device-proxy-address": onuDevice.ProxyAddress})
+		if err := dh.sendProxyOmciRequests(log.WithSpanFromContext(context.Background(), ctx), onuDevice, omciMsgs); err != nil {
+			return olterrors.NewErrCommunication("send-failed", log.Fields{
+				"parent-device-id": dh.device.Id,
+				"child-device-id":  omciMsgs.ChildDeviceId}, err)
+		}
+	} else {
+		logger.Debugw(ctx, "proxy-address-found-in-omci-message", log.Fields{"onu-device-proxy-address": omciMsgs.ProxyAddress})
+		if err := dh.sendProxyOmciRequests(log.WithSpanFromContext(context.Background(), ctx), nil, omciMsgs); err != nil {
+			return olterrors.NewErrCommunication("send-failed", log.Fields{
+				"parent-device-id": dh.device.Id,
+				"child-device-id":  omciMsgs.ChildDeviceId}, err)
+		}
+	}
+	return nil
+}
+
+func (dh *DeviceHandler) sendProxyOmciRequests(ctx context.Context, onuDevice *voltha.Device, omciMsgs *ia.OmciMessages) error {
+	var intfID uint32
+	var onuID uint32
+	var connectStatus common.ConnectStatus_Types
+	if onuDevice != nil {
+		intfID = onuDevice.ProxyAddress.GetChannelId()
+		onuID = onuDevice.ProxyAddress.GetOnuId()
+		connectStatus = onuDevice.ConnectStatus
+	} else {
+		intfID = omciMsgs.GetProxyAddress().GetChannelId()
+		onuID = omciMsgs.GetProxyAddress().GetOnuId()
+		connectStatus = omciMsgs.GetConnectStatus()
+	}
+	if connectStatus != voltha.ConnectStatus_REACHABLE {
+		logger.Debugw(ctx, "onu-not-reachable--cannot-send-omci", log.Fields{"intf-id": intfID, "onu-id": onuID})
+
+		return olterrors.NewErrCommunication("unreachable", log.Fields{
+			"intf-id": intfID,
+			"onu-id":  onuID}, nil)
+	}
+
+	// TODO: OpenOLT Agent oop.OmciMsg expects a hex encoded string for OMCI packets rather than the actual bytes.
+	//  Fix this in the agent and then we can pass byte array as Pkt: omciMsg.Message.
+
+	onuSecOmciMsgList := omciMsgs.GetMessages()
+
+	for _, onuSecOmciMsg := range onuSecOmciMsgList {
+
+		var omciMessage *oop.OmciMsg
+		hexPkt := make([]byte, hex.EncodedLen(len(onuSecOmciMsg)))
+		hex.Encode(hexPkt, onuSecOmciMsg)
+		omciMessage = &oop.OmciMsg{IntfId: intfID, OnuId: onuID, Pkt: hexPkt}
+
+		// TODO: Below logging illustrates the "stringify" of the omci Pkt.
+		//  once above is fixed this log line can change to just use hex.EncodeToString(omciMessage.Pkt)
+		//https://jira.opencord.org/browse/VOL-4604
+		transid := extractOmciTransactionID(onuSecOmciMsg)
+		logger.Debugw(ctx, "sent-omci-msg", log.Fields{"intf-id": intfID, "onu-id": onuID,
+			"omciTransactionID": transid, "omciMsg": string(omciMessage.Pkt)})
+
+		_, err := dh.Client.OmciMsgOut(log.WithSpanFromContext(context.Background(), ctx), omciMessage)
+		if err != nil {
+			return olterrors.NewErrCommunication("omci-send-failed", log.Fields{
+				"intf-id": intfID,
+				"onu-id":  onuID,
+				"message": omciMessage}, err)
+		}
+	}
+	return nil
+}
+
 // ProxyOmciMessage sends the proxied OMCI message to the target device
 func (dh *DeviceHandler) ProxyOmciMessage(ctx context.Context, omciMsg *ia.OmciMessage) error {
 	logger.Debugw(ctx, "proxy-omci-message", log.Fields{"parent-device-id": omciMsg.ParentDeviceId, "child-device-id": omciMsg.ChildDeviceId, "proxy-address": omciMsg.ProxyAddress, "connect-status": omciMsg.ConnectStatus})
@@ -1199,6 +1276,7 @@
 
 	// TODO: OpenOLT Agent oop.OmciMsg expects a hex encoded string for OMCI packets rather than the actual bytes.
 	//  Fix this in the agent and then we can pass byte array as Pkt: omciMsg.Message.
+	// https://jira.opencord.org/browse/VOL-4604
 	var omciMessage *oop.OmciMsg
 	hexPkt := make([]byte, hex.EncodedLen(len(omciMsg.Message)))
 	hex.Encode(hexPkt, omciMsg.Message)
diff --git a/internal/pkg/core/device_handler_test.go b/internal/pkg/core/device_handler_test.go
index 108fd4c..f95856e 100644
--- a/internal/pkg/core/device_handler_test.go
+++ b/internal/pkg/core/device_handler_test.go
@@ -42,6 +42,7 @@
 	ofp "github.com/opencord/voltha-protos/v5/go/openflow_13"
 	oop "github.com/opencord/voltha-protos/v5/go/openolt"
 	"github.com/opencord/voltha-protos/v5/go/voltha"
+	"github.com/stretchr/testify/assert"
 )
 
 const (
@@ -646,6 +647,89 @@
 	}
 }
 
+func TestDeviceHandler_ProxyOmciRequests(t *testing.T) {
+	ctx := context.Background()
+	dh1 := newMockDeviceHandler()
+	dh2 := negativeDeviceHandler()
+	device1 := &voltha.Device{
+		Id:       "onu1",
+		Root:     false,
+		ParentId: "logical_device",
+		ProxyAddress: &voltha.Device_ProxyAddress{
+			DeviceId:       "onu1",
+			DeviceType:     "onu",
+			ChannelId:      1,
+			ChannelGroupId: 1,
+		},
+		ConnectStatus: 1,
+	}
+	device2 := device1
+	device2.ConnectStatus = 2
+
+	iaomciMsg1 := &ia.OmciMessages{
+		ConnectStatus: 1,
+		ProxyAddress: &voltha.Device_ProxyAddress{
+			DeviceId:       "onu2",
+			DeviceType:     "onu",
+			ChannelId:      1,
+			ChannelGroupId: 1,
+		},
+	}
+
+	iaomciMsg2 := &ia.OmciMessages{
+		ConnectStatus: 1,
+		ProxyAddress: &voltha.Device_ProxyAddress{
+			DeviceId:       "onu3",
+			DeviceType:     "onu",
+			ChannelId:      1,
+			ChannelGroupId: 1,
+		},
+	}
+
+	type args struct {
+		onuDevice *voltha.Device
+		omciMsg   *ia.OmciMessages
+	}
+	tests := []struct {
+		name          string
+		devicehandler *DeviceHandler
+		args          args
+	}{
+		{"sendProxiedMessage-1", dh1, args{onuDevice: device1, omciMsg: &ia.OmciMessages{}}},
+		{"sendProxiedMessage-2", dh1, args{onuDevice: device2, omciMsg: &ia.OmciMessages{}}},
+		{"sendProxiedMessage-3", dh1, args{onuDevice: nil, omciMsg: iaomciMsg1}},
+		{"sendProxiedMessage-4", dh1, args{onuDevice: nil, omciMsg: iaomciMsg2}},
+		{"sendProxiedMessage-5", dh2, args{onuDevice: nil, omciMsg: iaomciMsg2}},
+		{"sendProxiedMessage-6", dh2, args{onuDevice: device1, omciMsg: &ia.OmciMessages{}}},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			switch tt.name {
+			case "sendProxiedMessage-1":
+				err := tt.devicehandler.ProxyOmciRequests(ctx, tt.args.omciMsg)
+				assert.Contains(t, err.Error(), "no deviceID")
+			case "sendProxiedMessage-2":
+				err := tt.devicehandler.ProxyOmciRequests(ctx, tt.args.omciMsg)
+				assert.Contains(t, err.Error(), "no deviceID")
+			case "sendProxiedMessage-3":
+				err := tt.devicehandler.ProxyOmciRequests(ctx, tt.args.omciMsg)
+				assert.Contains(t, err.Error(), "failed-communication")
+			case "sendProxiedMessage-4":
+				err := tt.devicehandler.ProxyOmciRequests(ctx, tt.args.omciMsg)
+				assert.Contains(t, err.Error(), "failed-communication")
+			case "sendProxiedMessage-5":
+				err := tt.devicehandler.ProxyOmciRequests(ctx, tt.args.omciMsg)
+				assert.Contains(t, err.Error(), "failed-communication")
+			case "sendProxiedMessage-6":
+				err := tt.devicehandler.ProxyOmciRequests(ctx, tt.args.omciMsg)
+				assert.Contains(t, err.Error(), "no deviceID")
+
+			}
+
+		})
+	}
+}
+
 func TestDeviceHandler_SendPacketInToCore(t *testing.T) {
 	dh1 := newMockDeviceHandler()
 	dh2 := negativeDeviceHandler()
diff --git a/internal/pkg/core/openolt.go b/internal/pkg/core/openolt.go
index 650a310..3665cbd 100644
--- a/internal/pkg/core/openolt.go
+++ b/internal/pkg/core/openolt.go
@@ -374,6 +374,17 @@
  *  OLT Inter-adapter service
  */
 
+// ProxyOmciRequests proxies an onu sw download OMCI request from the child adapter
+func (oo *OpenOLT) ProxyOmciRequests(ctx context.Context, request *ia.OmciMessages) (*empty.Empty, error) {
+	if handler := oo.getDeviceHandler(request.ParentDeviceId); handler != nil {
+		if err := handler.ProxyOmciRequests(ctx, request); err != nil {
+			return nil, errors.New(err.Error())
+		}
+		return &empty.Empty{}, nil
+	}
+	return nil, olterrors.NewErrNotFound("no-device-handler", log.Fields{"parent-device-id": request.ParentDeviceId, "child-device-id": request.ChildDeviceId}, nil).Log()
+}
+
 // ProxyOmciRequest proxies an OMCI request from the child adapter
 func (oo *OpenOLT) ProxyOmciRequest(ctx context.Context, request *ia.OmciMessage) (*empty.Empty, error) {
 	logger.Debugw(ctx, "proxy-omci-request", log.Fields{"request": request})