[VOL-5378]-voltctl implementation to fetch PPPoe IA and DHCP RA stats

Change-Id: I617531684bfcf8cbac7846d5b21b63c03b6a7086
Signed-off-by: Akash Soni <akash.soni@radisys.com>
diff --git a/internal/pkg/commands/devices.go b/internal/pkg/commands/devices.go
index c9410d2..d45d663 100644
--- a/internal/pkg/commands/devices.go
+++ b/internal/pkg/commands/devices.go
@@ -413,6 +413,28 @@
 	} `positional-args:"yes"`
 }
 
+type GetOffloadAppStats struct {
+	ListOutputOptions
+	Args struct {
+		OltId    DeviceId                                                 `positional-arg-name:"OLT_DEVICE_ID" required:"yes"`
+		StatsFor extension.GetOffloadedAppsStatisticsRequest_OffloadedApp `positional-arg-name:"OFFLOADED_APP" required:"yes"`
+	} `positional-args:"yes"`
+}
+
+type SetOffloadAppState struct {
+	Args struct {
+		OltId  DeviceId                   `positional-arg-name:"OLT_DEVICE_ID" required:"yes"`
+		Config extension.AppOffloadConfig `json:"config" required:"yes"`
+	} `positional-args:"yes"`
+}
+
+type SetOnuOffloadState struct {
+	Args struct {
+		OnuDeviceId string                                       `positional-arg-name:"ONU_DEVICE_ID" required:"yes"`
+		PerUniInfo  []extension.AppOffloadOnuConfig_PerUniConfig `json:"per_uni_info" required:"yes"`
+	} `positional-args:"yes"`
+}
+
 type GetOnuEthernetFrameExtendedPmCounters struct {
 	ListOutputOptions
 	Reset bool `long:"reset" description:"Reset the counters"`
@@ -527,7 +549,12 @@
 		OnuOmciActiveAlarms     GetOnuOmciActiveAlarms                `command:"onu_omci_active_alarms"`
 		PonRxPower              PonRxPower                            `command:"pon_rx_power"`
 		OnuDistance             GetOnuDistance                        `command:"onu_distance"`
+		OffloadAppStats         GetOffloadAppStats                    `command:"offload_app_stats"`
 	} `command:"getextval"`
+	SetExtVal struct {
+		OffloadAppStatsSet SetOffloadAppState `command:"set_offload_app_stats"`
+		OnuOffloadStatsSet SetOnuOffloadState `command:"set_onu_offload_stats"`
+	} `command:"setextval"`
 }
 
 var deviceOpts = DeviceOpts{}
@@ -2119,6 +2146,149 @@
 	return nil
 }
 
+func (options *GetOffloadAppStats) Execute(args []string) error {
+	// Establish a connection to the gRPC server
+	conn, err := NewConnection()
+	if err != nil {
+		return err
+	}
+	defer conn.Close()
+
+	client := extension.NewExtensionClient(conn)
+
+	// Build the request
+	singleGetValReq := &extension.SingleGetValueRequest{
+		TargetId: string(options.Args.OltId),
+		Request: &extension.GetValueRequest{
+			Request: &extension.GetValueRequest_OffloadedAppsStats{
+				OffloadedAppsStats: &extension.GetOffloadedAppsStatisticsRequest{
+					StatsFor: options.Args.StatsFor,
+				},
+			},
+		},
+	}
+
+	// Set a context with timeout
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().Grpc.Timeout)
+	defer cancel()
+
+	// Perform the gRPC call
+	rv, err := client.GetExtValue(ctx, singleGetValReq)
+	if err != nil {
+		Error.Printf("Error getting value for device ID %s, err=%s\n", options.Args.OltId, ErrorToString(err))
+		return err
+	}
+
+	// Check response status
+	if rv.GetResponse().GetStatus() != extension.GetValueResponse_OK {
+		return fmt.Errorf("failed to get offloaded app stats: %v", rv.GetResponse().GetErrReason().String())
+	}
+
+	// Process the response data
+	stats, formatStr := buildOffloadAppStatsOutputFormat(rv.GetResponse().GetOffloadedAppsStats())
+	outputFormat := CharReplacer.Replace(options.Format)
+	if outputFormat == "" {
+		outputFormat = GetCommandOptionWithDefault("device-get-offload-app-stats", "format", formatStr)
+	}
+
+	// Generate and display the output
+	result := CommandResult{
+		Format:    format.Format(outputFormat),
+		OutputAs:  toOutputType(options.OutputAs),
+		NameLimit: options.NameLimit,
+		Data:      stats,
+	}
+	GenerateOutput(&result)
+
+	return nil
+}
+
+func (options *SetOffloadAppState) Execute(args []string) error {
+	conn, err := NewConnection()
+	if err != nil {
+		return fmt.Errorf("failed to establish gRPC connection: %v", err)
+	}
+	defer conn.Close()
+
+	client := extension.NewExtensionClient(conn)
+
+	// Build the AppOffloadConfig request
+	setValueRequest := &extension.SetValueRequest{
+		Request: &extension.SetValueRequest_AppOffloadConfig{
+			AppOffloadConfig: &options.Args.Config,
+		},
+	}
+
+	singleSetValReq := &extension.SingleSetValueRequest{
+		TargetId: string(options.Args.OltId),
+		Request:  setValueRequest,
+	}
+
+	// Make gRPC call
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().Grpc.Timeout)
+	defer cancel()
+
+	resp, err := client.SetExtValue(ctx, singleSetValReq)
+	if err != nil {
+		return fmt.Errorf("failed to set AppOffloadConfig: %v", err)
+	}
+
+	if resp.Response.Status != extension.SetValueResponse_OK {
+		return fmt.Errorf("failed with status %v: %s", resp.Response.Status, resp.Response.ErrReason)
+	}
+
+	fmt.Printf("AppOffloadConfig successfully set for OLT ID: %s\n", options.Args.OltId)
+	return nil
+}
+
+func (options *SetOnuOffloadState) Execute(args []string) error {
+	conn, err := NewConnection()
+	if err != nil {
+		return fmt.Errorf("failed to establish gRPC connection: %v", err)
+	}
+	defer conn.Close()
+
+	client := extension.NewExtensionClient(conn)
+
+	// Convert PerUniInfo to []*extension.AppOffloadOnuConfig_PerUniConfig
+	var perUniInfo []*extension.AppOffloadOnuConfig_PerUniConfig
+	for i := range options.Args.PerUniInfo {
+		perUniInfo = append(perUniInfo, &options.Args.PerUniInfo[i])
+	}
+	// Build the AppOffloadOnuConfig request
+	onuConfig := &extension.AppOffloadOnuConfig{
+		OnuDeviceId: options.Args.OnuDeviceId,
+		PerUniInfo:  perUniInfo,
+	}
+
+	setValueRequest := &extension.SetValueRequest{
+		Request: &extension.SetValueRequest_AppOffloadOnuConfig{
+			AppOffloadOnuConfig: onuConfig,
+		},
+	}
+
+	singleSetValReq := &extension.SingleSetValueRequest{
+		TargetId: options.Args.OnuDeviceId,
+		Request:  setValueRequest,
+	}
+
+	// Make gRPC call
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().Grpc.Timeout)
+	defer cancel()
+
+	resp, err := client.SetExtValue(ctx, singleSetValReq)
+	if err != nil {
+		return fmt.Errorf("failed to set AppOffloadOnuConfig: %v", err)
+	}
+
+	if resp.Response.Status != extension.SetValueResponse_OK {
+		return fmt.Errorf("failed with status %v: %s", resp.Response.Status, resp.Response.ErrReason)
+	}
+
+	fmt.Printf("AppOffloadOnuConfig successfully set for ONU ID: %s\n", options.Args.OnuDeviceId)
+	return nil
+}
+
 func (options *GetOnuEthernetFrameExtendedPmCounters) Execute(args []string) error {
 	conn, err := NewConnection()
 	if err != nil {
diff --git a/internal/pkg/commands/stats.go b/internal/pkg/commands/stats.go
index 1fe9c4c..26697ac 100644
--- a/internal/pkg/commands/stats.go
+++ b/internal/pkg/commands/stats.go
@@ -17,9 +17,10 @@
 
 import (
 	"fmt"
+	"strings"
+
 	"github.com/opencord/voltctl/pkg/model"
 	"github.com/opencord/voltha-protos/v5/go/extension"
-	"strings"
 )
 
 type tagBuilder struct {
@@ -193,6 +194,73 @@
 	return onuStats, tagBuilder.buildOutputString()
 }
 
+func buildOffloadAppStatsOutputFormat(stats *extension.GetOffloadedAppsStatisticsResponse) (map[string]interface{}, string) {
+	formatStr := "table" // Default format
+
+	if stats == nil {
+		return map[string]interface{}{
+			"error": "No stats available in response",
+		}, formatStr
+	}
+
+	switch data := stats.GetStats().(type) {
+	case *extension.GetOffloadedAppsStatisticsResponse_Dhcpv4RaStats:
+		return map[string]interface{}{
+			"in_bad_packets_from_client":           data.Dhcpv4RaStats.InBadPacketsFromClient,
+			"in_bad_packets_from_server":           data.Dhcpv4RaStats.InBadPacketsFromServer,
+			"in_packets_from_client":               data.Dhcpv4RaStats.InPacketsFromClient,
+			"in_packets_from_server":               data.Dhcpv4RaStats.InPacketsFromServer,
+			"out_packets_to_server":                data.Dhcpv4RaStats.OutPacketsToServer,
+			"out_packets_to_client":                data.Dhcpv4RaStats.OutPacketsToClient,
+			"option_82_inserted_packets_to_server": data.Dhcpv4RaStats.Option_82InsertedPacketsToServer,
+			"option_82_removed_packets_to_client":  data.Dhcpv4RaStats.Option_82RemovedPacketsToClient,
+			"option_82_not_inserted_to_server":     data.Dhcpv4RaStats.Option_82NotInsertedToServer,
+			"additional_stats":                     convertMapStringToInterface(data.Dhcpv4RaStats.AdditionalStats),
+		}, formatStr
+
+	case *extension.GetOffloadedAppsStatisticsResponse_Dhcpv6RaStats:
+		return map[string]interface{}{
+			"in_bad_packets_from_client":                data.Dhcpv6RaStats.InBadPacketsFromClient,
+			"in_bad_packets_from_server":                data.Dhcpv6RaStats.InBadPacketsFromServer,
+			"option_17_inserted_packets_to_server":      data.Dhcpv6RaStats.Option_17InsertedPacketsToServer,
+			"option_17_removed_packets_to_client":       data.Dhcpv6RaStats.Option_17RemovedPacketsToClient,
+			"option_18_inserted_packets_to_server":      data.Dhcpv6RaStats.Option_18InsertedPacketsToServer,
+			"option_18_removed_packets_to_client":       data.Dhcpv6RaStats.Option_18RemovedPacketsToClient,
+			"option_37_inserted_packets_to_server":      data.Dhcpv6RaStats.Option_18InsertedPacketsToServer,
+			"option_37_removed_packets_to_client":       data.Dhcpv6RaStats.Option_18RemovedPacketsToClient,
+			"outgoing_mtu_exceeded_packets_from_client": data.Dhcpv6RaStats.OutgoingMtuExceededPacketsFromClient,
+			"additional_stats":                          convertMapStringToInterface(data.Dhcpv6RaStats.AdditionalStats),
+		}, formatStr
+
+	case *extension.GetOffloadedAppsStatisticsResponse_PppoeIaStats:
+		return map[string]interface{}{
+			"in_error_packets_from_client":                   data.PppoeIaStats.InErrorPacketsFromClient,
+			"in_error_packets_from_server":                   data.PppoeIaStats.InErrorPacketsFromServer,
+			"in_packets_from_client":                         data.PppoeIaStats.InPacketsFromClient,
+			"in_packets_from_server":                         data.PppoeIaStats.InPacketsFromServer,
+			"out_packets_to_server":                          data.PppoeIaStats.OutPacketsToServer,
+			"out_packets_to_client":                          data.PppoeIaStats.OutPacketsToClient,
+			"vendor_specific_tag_inserted_packets_to_server": data.PppoeIaStats.VendorSpecificTagInsertedPacketsToServer,
+			"vendor_specific_tag_removed_packets_to_client":  data.PppoeIaStats.VendorSpecificTagRemovedPacketsToClient,
+			"outgoing_mtu_exceeded_packets_from_client":      data.PppoeIaStats.OutgoingMtuExceededPacketsFromClient,
+			"additional_stats":                               convertMapStringToInterface(data.PppoeIaStats.AdditionalStats),
+		}, formatStr
+
+	default:
+		return map[string]interface{}{
+			"error": "Unsupported or unknown stats type",
+		}, formatStr
+	}
+}
+
+func convertMapStringToInterface(input map[string]string) map[string]interface{} {
+	result := make(map[string]interface{}, len(input))
+	for key, value := range input {
+		result[key] = value
+	}
+	return result
+}
+
 /*
  * Construct a template format string based on the fields required by the
  * results.