diff --git a/config/config.yml b/config/config.yml index 12dc921e1..a450acc67 100644 --- a/config/config.yml +++ b/config/config.yml @@ -2,7 +2,7 @@ app: name: console repo: device-management-toolkit/console version: DEVELOPMENT - encryption_key: "" + encryption_key: "base64encodedkey12345678901234567890123456789012" allow_insecure_ciphers: false http: host: localhost diff --git a/go.mod b/go.mod index e908a9054..f8b85dbf0 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/device-management-toolkit/console go 1.25 -// replace github.com/device-management-toolkit/go-wsman-messages/v2 => ../go-wsman-messages +replace github.com/device-management-toolkit/go-wsman-messages/v2 => ../go-wsman-messages require ( github.com/Masterminds/squirrel v1.5.4 diff --git a/go.sum b/go.sum index daf616cbc..435b3e7f8 100644 --- a/go.sum +++ b/go.sum @@ -35,8 +35,6 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/device-management-toolkit/go-wsman-messages/v2 v2.35.0 h1:J5bQ12tjspC9MT5FORsykBXPcnGDB2p4cZME9zeIE3U= -github.com/device-management-toolkit/go-wsman-messages/v2 v2.35.0/go.mod h1:qlR1/nrV6QPse0918YSJ2bjMY8VNkm6qnNU0ioqQEsQ= github.com/dhui/dktest v0.4.6 h1:+DPKyScKSEp3VLtbMDHcUq6V5Lm5zfZZVb0Sk7Ahom4= github.com/dhui/dktest v0.4.6/go.mod h1:JHTSYDtKkvFNFHJKqCzVzqXecyv+tKt8EzceOmQOgbU= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= diff --git a/internal/controller/httpapi/v1/devicemanagement.go b/internal/controller/httpapi/v1/devicemanagement.go index bd5f474b5..87014f392 100644 --- a/internal/controller/httpapi/v1/devicemanagement.go +++ b/internal/controller/httpapi/v1/devicemanagement.go @@ -62,5 +62,9 @@ func NewAmtRoutes(handler *gin.RouterGroup, d devices.Feature, amt amtexplorer.F // KVM display settings h.GET("kvm/displays/:guid", r.getKVMDisplays) h.PUT("kvm/displays/:guid", r.setKVMDisplays) + + // Network link preference + h.POST("network/linkPreference/:guid", r.setLinkPreference) } } + diff --git a/internal/controller/httpapi/v1/linkpreference.go b/internal/controller/httpapi/v1/linkpreference.go new file mode 100644 index 000000000..15f131ecc --- /dev/null +++ b/internal/controller/httpapi/v1/linkpreference.go @@ -0,0 +1,45 @@ +package v1 + +import ( + "errors" + "net/http" + + "github.com/gin-gonic/gin" + + "github.com/device-management-toolkit/console/internal/entity/dto/v1" + "github.com/device-management-toolkit/console/internal/usecase/devices/wsman" +) + +// setLinkPreference sets the link preference (ME or Host) on a device's WiFi port. +func (r *deviceManagementRoutes) setLinkPreference(c *gin.Context) { + guid := c.Param("guid") + + var req dto.LinkPreferenceRequest + if err := c.ShouldBindJSON(&req); err != nil { + ErrorResponse(c, err) + + return + } + + response, err := r.d.SetLinkPreference(c.Request.Context(), guid, req) + if err != nil { + r.l.Error(err, "http - v1 - setLinkPreference") + // Map specific errors to HTTP status codes (matching MPS implementation) + if errors.Is(err, wsman.ErrNoWiFiPort) { + c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) + return + } + ErrorResponse(c, err) + + return + } + + // Map AMT return value to HTTP status code (matching MPS implementation) + // MPS logic: null -> 404, -1 or non-zero -> 400, 0 -> 200 + httpStatus := http.StatusOK + if response.ReturnValue == -1 || response.ReturnValue != 0 { + httpStatus = http.StatusBadRequest + } + + c.JSON(httpStatus, response) +} diff --git a/internal/entity/dto/v1/linkpreference.go b/internal/entity/dto/v1/linkpreference.go new file mode 100644 index 000000000..e9179f8f1 --- /dev/null +++ b/internal/entity/dto/v1/linkpreference.go @@ -0,0 +1,64 @@ +package dto + +// LinkPreferenceRequest represents the request to set link preference on a device. +type LinkPreferenceRequest struct { + LinkPreference int `json:"linkPreference" binding:"required,min=1,max=2"` // 1 for ME, 2 for HOST + Timeout int `json:"timeout" binding:"required,min=0"` // Timeout in seconds +} + +// LinkPreferenceResponse represents the response from setting link preference. +type LinkPreferenceResponse struct { + ReturnValue int `json:"returnValue"` + ReturnValueStr string `json:"returnValueStr"` +} + +// LinkPreference enumeration values +const ( + LinkPreferenceME = 1 // Management Engine + LinkPreferenceHost = 2 // Host +) + +// Return value constants for SetLinkPreference +const ( + ReturnValueSuccess = 0 + ReturnValueNotSupported = 1 + ReturnValueUnknownFailed = 2 + ReturnValueTimeout = 3 + ReturnValueFailed = 4 + ReturnValueInvalidParameter = 5 + ReturnValueInUse = 6 + ReturnValueTransitionStarted = 4096 + ReturnValueInvalidStateTransition = 4097 + ReturnValueTimeoutParameterNotSupport = 4098 + ReturnValueBusy = 4099 +) + +// GetReturnValueString returns a human-readable string for the return value. +func GetReturnValueString(returnValue int) string { + switch returnValue { + case ReturnValueSuccess: + return "SUCCESS" + case ReturnValueNotSupported: + return "NOT_SUPPORTED" + case ReturnValueUnknownFailed: + return "UNKNOWN_FAILED" + case ReturnValueTimeout: + return "TIMEOUT" + case ReturnValueFailed: + return "FAILED" + case ReturnValueInvalidParameter: + return "INVALID_PARAMETER" + case ReturnValueInUse: + return "IN_USE" + case ReturnValueTransitionStarted: + return "TRANSITION_STARTED" + case ReturnValueInvalidStateTransition: + return "INVALID_STATE_TRANSITION" + case ReturnValueTimeoutParameterNotSupport: + return "TIMEOUT_PARAMETER_NOT_SUPPORT" + case ReturnValueBusy: + return "BUSY" + default: + return "UNKNOWN" + } +} diff --git a/internal/usecase/devices/interfaces.go b/internal/usecase/devices/interfaces.go index c3441fd21..207df2508 100644 --- a/internal/usecase/devices/interfaces.go +++ b/internal/usecase/devices/interfaces.go @@ -86,5 +86,7 @@ type ( // KVM Screen Settings (IPS_ScreenSettingData) GetKVMScreenSettings(c context.Context, guid string) (dto.KVMScreenSettings, error) SetKVMScreenSettings(c context.Context, guid string, req dto.KVMScreenSettingsRequest) (dto.KVMScreenSettings, error) + // Link Preference (AMT_EthernetPortSettings) + SetLinkPreference(c context.Context, guid string, req dto.LinkPreferenceRequest) (dto.LinkPreferenceResponse, error) } ) diff --git a/internal/usecase/devices/linkpreference.go b/internal/usecase/devices/linkpreference.go new file mode 100644 index 000000000..2b4df3f1e --- /dev/null +++ b/internal/usecase/devices/linkpreference.go @@ -0,0 +1,41 @@ +package devices + +import ( + "context" + + dto "github.com/device-management-toolkit/console/internal/entity/dto/v1" +) + +// SetLinkPreference sets the link preference (ME or Host) on a device's WiFi port. +func (uc *UseCase) SetLinkPreference(c context.Context, guid string, req dto.LinkPreferenceRequest) (dto.LinkPreferenceResponse, error) { + item, err := uc.repo.GetByID(c, guid, "") + if err != nil { + return dto.LinkPreferenceResponse{}, err + } + + if item == nil || item.GUID == "" { + return dto.LinkPreferenceResponse{}, ErrNotFound + } + + // Validate link preference value + if req.LinkPreference != dto.LinkPreferenceME && req.LinkPreference != dto.LinkPreferenceHost { + return dto.LinkPreferenceResponse{}, ErrValidationUseCase.Wrap("SetLinkPreference", "validate link preference", "linkPreference must be 1 (ME) or 2 (Host)") + } + + // Validate timeout + if req.Timeout < 0 { + return dto.LinkPreferenceResponse{}, ErrValidationUseCase.Wrap("SetLinkPreference", "validate timeout", "timeout must be non-negative") + } + + device, _ := uc.device.SetupWsmanClient(*item, false, true) + + returnValue, err := device.SetLinkPreference(req.LinkPreference, req.Timeout) + if err != nil { + return dto.LinkPreferenceResponse{}, err + } + + return dto.LinkPreferenceResponse{ + ReturnValue: returnValue, + ReturnValueStr: dto.GetReturnValueString(returnValue), + }, nil +} diff --git a/internal/usecase/devices/wsman/interfaces.go b/internal/usecase/devices/wsman/interfaces.go index cf345a66d..35295a012 100644 --- a/internal/usecase/devices/wsman/interfaces.go +++ b/internal/usecase/devices/wsman/interfaces.go @@ -71,4 +71,5 @@ type Management interface { GetIPSKVMRedirectionSettingData() (kvmredirection.Response, error) SetIPSKVMRedirectionSettingData(data *kvmredirection.KVMRedirectionSettingsRequest) (kvmredirection.Response, error) DeleteCertificate(instanceID string) error + SetLinkPreference(linkPreference, timeout int) (int, error) } diff --git a/internal/usecase/devices/wsman/message.go b/internal/usecase/devices/wsman/message.go index b104bfc34..e13f5704d 100644 --- a/internal/usecase/devices/wsman/message.go +++ b/internal/usecase/devices/wsman/message.go @@ -81,8 +81,22 @@ var ( // ErrCIRADeviceNotConnected is returned when a CIRA device is not connected or not found. ErrCIRADeviceNotConnected = errors.New("CIRA device not connected/not found") + // ErrWsmanMessage is used for wrapping wsman message errors. + ErrWsmanMessage = &wsmanError{} + // ErrNoWiFiPort is returned when no WiFi port is found on the device. + ErrNoWiFiPort = errors.New("no WiFi port found (PhysicalConnectionType=3)") ) +type wsmanError struct{} + +func (e *wsmanError) Error() string { + return "wsman message error" +} + +func (e *wsmanError) Wrap(operation, context, message string) error { + return errors.New(operation + ": " + context + ": " + message) +} + type ConnectionEntry struct { WsmanMessages wsman.Messages IsCIRA bool @@ -1917,3 +1931,44 @@ func (c *ConnectionEntry) GetIPSKVMRedirectionSettingData() (kvmredirection.Resp func (c *ConnectionEntry) SetIPSKVMRedirectionSettingData(req *kvmredirection.KVMRedirectionSettingsRequest) (kvmredirection.Response, error) { return c.WsmanMessages.IPS.KVMRedirectionSettingData.Put(req) } + +// SetLinkPreference sets the link preference (ME or Host) on the WiFi port. +// linkPreference: 1 for ME, 2 for Host +// timeout: timeout in seconds +// Returns the return value from the AMT device or an error. +func (c *ConnectionEntry) SetLinkPreference(linkPreference, timeout int) (int, error) { + // Get all ethernet port settings to find WiFi port + enumResponse, err := c.WsmanMessages.AMT.EthernetPortSettings.Enumerate() + if err != nil { + return -1, err + } + + pullResponse, err := c.WsmanMessages.AMT.EthernetPortSettings.Pull(enumResponse.Body.EnumerateResponse.EnumerationContext) + if err != nil { + return -1, err + } + + // Find WiFi port (PhysicalConnectionType = 3) + var wifiInstanceID string + + for i := range pullResponse.Body.PullResponse.EthernetPortItems { + port := &pullResponse.Body.PullResponse.EthernetPortItems[i] + // PhysicalConnectionType: 3 = Wireless LAN + if port.PhysicalConnectionType == 3 { + wifiInstanceID = port.InstanceID + break + } + } + + if wifiInstanceID == "" { + return -1, ErrNoWiFiPort + } + + // Call SetLinkPreference on the WiFi port + response, err := c.WsmanMessages.AMT.EthernetPortSettings.SetLinkPreference(linkPreference, timeout, wifiInstanceID) + if err != nil { + return -1, err + } + + return response.Body.SetLinkPreferenceResponse.ReturnValue, nil +}