diff --git a/src/amt/DeviceAction.ts b/src/amt/DeviceAction.ts index bbfcff5ae..5ed89aa76 100644 --- a/src/amt/DeviceAction.ts +++ b/src/amt/DeviceAction.ts @@ -660,4 +660,103 @@ export class DeviceAction { logger.silly(`putKVMRedirectionSettingData ${messages.COMPLETE}`) return result.Envelope.Body } + + async getEthernetPortSettings(): Promise< + Common.Models.Envelope> + > { + logger.silly(`getEthernetPortSettings ${messages.REQUEST}`) + let xmlRequestBody = this.amt.EthernetPortSettings.Enumerate() + const enumResponse = await this.ciraHandler.Enumerate(this.ciraSocket, xmlRequestBody) + if (enumResponse == null) { + logger.error(`getEthernetPortSettings failed. Reason: ${messages.ENUMERATION_RESPONSE_NULL}`) + return null + } + xmlRequestBody = this.amt.EthernetPortSettings.Pull(enumResponse.Envelope.Body.EnumerateResponse.EnumerationContext) + const pullResponse = await this.ciraHandler.Pull(this.ciraSocket, xmlRequestBody) + logger.silly(`getEthernetPortSettings ${messages.COMPLETE}`) + return pullResponse.Envelope + } + + /** + * Finds the first WiFi port by checking PhysicalConnectionType + * @returns Object with instanceID and full port settings, or null if none found + */ + async findWiFiPort(): Promise<{ instanceID: string; settings: any } | null> { + logger.silly('findWiFiPort: searching for WiFi port') + try { + const enumResult = await this.getEthernetPortSettings() + if (enumResult?.Body?.PullResponse?.Items == null) { + logger.error('findWiFiPort: No ethernet port settings found') + return null + } + + const settings: any = (enumResult.Body.PullResponse.Items as any).AMT_EthernetPortSettings + if (settings == null) { + logger.error('findWiFiPort: AMT_EthernetPortSettings not found in response') + return null + } + + const ports = Array.isArray(settings) ? settings : [settings] + + // Find the first port with PhysicalConnectionType = 3 (Wireless LAN) + const wifiPort = ports.find((port: any) => { + const connectionType = parseInt(port.PhysicalConnectionType, 10) + return connectionType === 3 + }) + + if (wifiPort == null) { + logger.error('findWiFiPort: No WiFi port found') + return null + } + + logger.silly(`findWiFiPort: Found WiFi port ${wifiPort.InstanceID}`) + return { + instanceID: wifiPort.InstanceID, + settings: wifiPort + } + } catch (err) { + logger.error(`findWiFiPort error: ${(err as Error).message}`) + return null + } + } + + async setEthernetLinkPreference( + linkPreference: AMT.Types.EthernetPortSettings.LinkPreference, + timeoutSeconds: number + ): Promise> { + logger.silly(`setEthernetLinkPreference ${messages.REQUEST}`) + + // Auto-detect the WiFi port + logger.silly('setEthernetLinkPreference: Auto-detecting WiFi port') + const wifiPortInfo = await this.findWiFiPort() + if (wifiPortInfo == null) { + const errorMsg = 'No WiFi port found on this device. SetLinkPreference requires a WiFi interface (PhysicalConnectionType=3).' + logger.error(`setEthernetLinkPreference: ${errorMsg}`) + return { + Header: {}, + Body: { + Fault: { + Code: { Value: 'NoWiFiPort' }, + Reason: { Text: errorMsg } + } + } + } as any + } + logger.info(`setEthernetLinkPreference: Auto-detected WiFi port: ${wifiPortInfo.instanceID}`) + + const xmlRequestBody = this.amt.EthernetPortSettings.SetLinkPreference(linkPreference, timeoutSeconds, wifiPortInfo.instanceID) + const result = await this.ciraHandler.Get(this.ciraSocket, xmlRequestBody) + + // Fetch the updated WiFi port settings after the operation + logger.silly('setEthernetLinkPreference: Fetching updated WiFi port settings') + const updatedWifiPortInfo = await this.findWiFiPort() + + logger.silly(`setEthernetLinkPreference ${messages.COMPLETE}`) + // Add the detected instanceID and updated WiFi port settings to the result for the handler to use + if (result?.Envelope != null) { + (result.Envelope as any)._detectedInstanceID = wifiPortInfo.instanceID; + (result.Envelope as any)._wifiPortSettings = updatedWifiPortInfo?.settings ?? wifiPortInfo.settings + } + return result.Envelope + } } diff --git a/src/amt/deviceAction.test.ts b/src/amt/deviceAction.test.ts index 78ae1b5be..34dcaaf62 100644 --- a/src/amt/deviceAction.test.ts +++ b/src/amt/deviceAction.test.ts @@ -627,4 +627,155 @@ describe('Device Action Tests', () => { expect(result).toEqual(putKVMRedirectionSettingDataResponse.Envelope.Body) }) }) + + describe('WiFi port validation and link preference', () => { + let getEthernetPortSettingsSpy: SpyInstance + + beforeEach(() => { + getEthernetPortSettingsSpy = spyOn(device, 'getEthernetPortSettings') + }) + + it('should find WiFi port automatically', async () => { + const mockEnumResponse = { + Body: { + PullResponse: { + Items: { + AMT_EthernetPortSettings: [ + { + InstanceID: 'Intel(r) AMT Ethernet Port Settings 0', + PhysicalConnectionType: '0', // Integrated LAN + ElementName: 'LAN Port' + }, + { + InstanceID: 'Intel(r) AMT Ethernet Port Settings 1', + PhysicalConnectionType: '3', // Wireless LAN + ElementName: 'WiFi Port' + } + ] + } + } + } + } + getEthernetPortSettingsSpy.mockResolvedValue(mockEnumResponse as any) + + const result = await device.findWiFiPort() + + expect(result).not.toBeNull() + expect(result?.instanceID).toBe('Intel(r) AMT Ethernet Port Settings 1') + expect(result?.settings.InstanceID).toBe('Intel(r) AMT Ethernet Port Settings 1') + expect(result?.settings.PhysicalConnectionType).toBe('3') + expect(result?.settings.ElementName).toBe('WiFi Port') + }) + + it('should return null when no WiFi port exists', async () => { + const mockEnumResponse = { + Body: { + PullResponse: { + Items: { + AMT_EthernetPortSettings: [ + { + InstanceID: 'Intel(r) AMT Ethernet Port Settings 0', + PhysicalConnectionType: '0' // Only LAN + }, + { + InstanceID: 'Intel(r) AMT Ethernet Port Settings 1', + PhysicalConnectionType: '1' // Only LAN + } + ] + } + } + } + } + getEthernetPortSettingsSpy.mockResolvedValue(mockEnumResponse as any) + + const result = await device.findWiFiPort() + + expect(result).toBeNull() + }) + + it('should auto-detect WiFi port when instanceID not provided', async () => { + const mockEnumResponse = { + Body: { + PullResponse: { + Items: { + AMT_EthernetPortSettings: [ + { + InstanceID: 'Intel(r) AMT Ethernet Port Settings 0', + PhysicalConnectionType: '0' + }, + { + InstanceID: 'Intel(r) AMT Ethernet Port Settings 1', + PhysicalConnectionType: '3' // WiFi + } + ] + } + } + } + } + getEthernetPortSettingsSpy.mockResolvedValue(mockEnumResponse as any) + getSpy.mockResolvedValue({ Envelope: { Body: { SetLinkPreference_OUTPUT: { ReturnValue: '0' } } } }) + + const result = await device.setEthernetLinkPreference(1, 300) + + expect(result?.Body?.Fault).toBeUndefined() + expect(getSpy).toHaveBeenCalled() + expect((result as any)._detectedInstanceID).toBe('Intel(r) AMT Ethernet Port Settings 1') + }) + + it('should return error when no WiFi port found and instanceID not provided', async () => { + const mockEnumResponse = { + Body: { + PullResponse: { + Items: { + AMT_EthernetPortSettings: { + InstanceID: 'Intel(r) AMT Ethernet Port Settings 0', + PhysicalConnectionType: '0' // Only LAN + } + } + } + } + } + getEthernetPortSettingsSpy.mockResolvedValue(mockEnumResponse as any) + + const result = await device.setEthernetLinkPreference(1, 300) + + expect(result?.Body?.Fault).toBeDefined() + expect(result?.Body?.Fault?.Code?.Value).toBe('NoWiFiPort') + expect(result?.Body?.Fault?.Reason?.Text).toContain('No WiFi port found') + }) + + it('should auto-detect WiFi port and call SetLinkPreference', async () => { + const mockEnumResponse = { + Body: { + PullResponse: { + Items: { + AMT_EthernetPortSettings: [ + { + InstanceID: 'Intel(r) AMT Ethernet Port Settings 0', + PhysicalConnectionType: '0', // Integrated LAN + ElementName: 'LAN Port' + }, + { + InstanceID: 'Intel(r) AMT Ethernet Port Settings 1', + PhysicalConnectionType: '3', // Wireless LAN + ElementName: 'WiFi Port' + } + ] + } + } + } + } + getEthernetPortSettingsSpy.mockResolvedValue(mockEnumResponse as any) + getSpy.mockResolvedValue({ Envelope: { Body: { SetLinkPreference_OUTPUT: { ReturnValue: '0' } } } }) + + const result = await device.setEthernetLinkPreference(1, 300) + + expect(result?.Body?.Fault).toBeUndefined() + expect(getSpy).toHaveBeenCalled() + expect((result as any)?._detectedInstanceID).toBe('Intel(r) AMT Ethernet Port Settings 1') + expect((result as any)?._wifiPortSettings).toBeDefined() + expect((result as any)?._wifiPortSettings.InstanceID).toBe('Intel(r) AMT Ethernet Port Settings 1') + expect((result as any)?._wifiPortSettings.PhysicalConnectionType).toBe('3') + }) + }) }) diff --git a/src/routes/amt/index.ts b/src/routes/amt/index.ts index 3fb6e78bf..7768ac3ac 100644 --- a/src/routes/amt/index.ts +++ b/src/routes/amt/index.ts @@ -38,6 +38,8 @@ import { validator } from './kvm/validator.js' import { get } from 'http' import { getScreenSettingData } from './kvm/get.js' import { setKVMRedirectionSettingData } from './kvm/set.js' +import { setLinkPreference } from './linkPreference.js' +import { linkPreferenceValidator } from './linkPreferenceValidator.js' const amtRouter: Router = Router() @@ -68,4 +70,7 @@ amtRouter.post('/certificates/:guid', certValidator(), validateMiddleware, ciraM amtRouter.get('/kvm/displays/:guid', ciraMiddleware, getScreenSettingData) amtRouter.put('/kvm/displays/:guid', validator(), ciraMiddleware, setKVMRedirectionSettingData) +// Link Preference (ME/HOST) +amtRouter.post('/network/linkPreference/:guid', linkPreferenceValidator(), validateMiddleware, ciraMiddleware, setLinkPreference) + export default amtRouter diff --git a/src/routes/amt/linkPreference.test.ts b/src/routes/amt/linkPreference.test.ts new file mode 100644 index 000000000..a2a056fd7 --- /dev/null +++ b/src/routes/amt/linkPreference.test.ts @@ -0,0 +1,198 @@ +/********************************************************************* + * Copyright (c) Intel Corporation 2025 + * SPDX-License-Identifier: Apache-2.0 + **********************************************************************/ + +import { type SpyInstance, spyOn } from 'jest-mock' +import { CIRAHandler } from '../../amt/CIRAHandler.js' +import { DeviceAction } from '../../amt/DeviceAction.js' +import { HttpHandler } from '../../amt/HttpHandler.js' +import { messages } from '../../logging/index.js' +import { createSpyObj } from '../../test/helper/jest.js' +import { ErrorResponse } from '../../utils/amtHelper.js' +import { MqttProvider } from '../../utils/MqttProvider.js' +import { setLinkPreference } from './linkPreference.js' + +describe('Link Preference', () => { + let req + let resSpy + let mqttSpy: SpyInstance + let setEthernetLinkPreferenceSpy: SpyInstance + let device: DeviceAction + + beforeEach(() => { + const handler = new CIRAHandler(new HttpHandler(), 'admin', 'P@ssw0rd') + device = new DeviceAction(handler, null) + req = { + params: { + guid: '123456' + }, + body: { + linkPreference: 1, + timeout: 300 + }, + query: {}, + deviceAction: device + } + resSpy = createSpyObj('Response', [ + 'status', + 'json', + 'end', + 'send' + ]) + resSpy.status.mockReturnThis() + resSpy.json.mockReturnThis() + resSpy.send.mockReturnThis() + mqttSpy = spyOn(MqttProvider, 'publishEvent') + // Mock with _detectedInstanceID and _wifiPortSettings to simulate auto-detection + setEthernetLinkPreferenceSpy = spyOn(device, 'setEthernetLinkPreference').mockResolvedValue({ + _detectedInstanceID: 'Intel(r) AMT Ethernet Port Settings 0', + _wifiPortSettings: { + InstanceID: 'Intel(r) AMT Ethernet Port Settings 0', + PhysicalConnectionType: '3', + ElementName: 'Intel(r) WiFi Link', + MACAddress: '00:11:22:33:44:55' + } + } as any) + }) + + it('should set link preference to ME (1) with timeout', async () => { + req.body.linkPreference = 1 + req.body.timeout = 300 + + await setLinkPreference(req as any, resSpy) + + expect(setEthernetLinkPreferenceSpy).toHaveBeenCalledWith(1, 300) + expect(mqttSpy).toHaveBeenCalledWith('success', ['AMT_LinkPreference'], 'Link Preference set to ME') + expect(resSpy.status).toHaveBeenCalledWith(200) + expect(resSpy.json).toHaveBeenCalledWith({ + status: 'Link Preference set to ME', + linkPreference: 1, + timeout: 300, + instanceID: 'Intel(r) AMT Ethernet Port Settings 0', + wifiPort: { + InstanceID: 'Intel(r) AMT Ethernet Port Settings 0', + PhysicalConnectionType: '3', + ElementName: 'Intel(r) WiFi Link', + MACAddress: '00:11:22:33:44:55' + } + }) + }) + + it('should set link preference to HOST (2) with timeout', async () => { + req.body.linkPreference = 2 + req.body.timeout = 600 + + await setLinkPreference(req as any, resSpy) + + expect(setEthernetLinkPreferenceSpy).toHaveBeenCalledWith(2, 600) + expect(mqttSpy).toHaveBeenCalledWith('success', ['AMT_LinkPreference'], 'Link Preference set to HOST') + expect(resSpy.status).toHaveBeenCalledWith(200) + expect(resSpy.json).toHaveBeenCalledWith({ + status: 'Link Preference set to HOST', + linkPreference: 2, + timeout: 600, + instanceID: 'Intel(r) AMT Ethernet Port Settings 0', + wifiPort: { + InstanceID: 'Intel(r) AMT Ethernet Port Settings 0', + PhysicalConnectionType: '3', + ElementName: 'Intel(r) WiFi Link', + MACAddress: '00:11:22:33:44:55' + } + }) + }) + + it('should handle errors gracefully', async () => { + const errorMessage = 'Failed to set link preference' + setEthernetLinkPreferenceSpy.mockRejectedValue(new Error(errorMessage)) + + await setLinkPreference(req as any, resSpy) + + expect(mqttSpy).toHaveBeenCalledWith('fail', ['AMT_LinkPreference'], messages.INTERNAL_SERVICE_ERROR) + expect(resSpy.status).toHaveBeenCalledWith(500) + expect(resSpy.json).toHaveBeenCalledWith(ErrorResponse(500, 'Exception during Set Link Preference')) + }) + + it('should return 400 when attempting to set link preference on non-WiFi port', async () => { + // Mock a validation failure response - the exact message depends on actual port type + const mockFaultResponse = { + Header: {}, + Body: { + Fault: { + Code: { Value: 'ValidationError' }, + Reason: { Text: 'SetLinkPreference is only applicable for WiFi ports' } + } + } + } + setEthernetLinkPreferenceSpy.mockResolvedValue(mockFaultResponse) + + await setLinkPreference(req as any, resSpy) + + // Verify 400 response with the fault message + expect(resSpy.status).toHaveBeenCalledWith(400) + expect(resSpy.json).toHaveBeenCalledWith({ error: mockFaultResponse.Body.Fault.Reason.Text }) + expect(mqttSpy).toHaveBeenCalledWith('fail', ['AMT_LinkPreference'], mockFaultResponse.Body.Fault.Reason.Text) + }) + + it('should return 500 when port validation returns null', async () => { + setEthernetLinkPreferenceSpy.mockResolvedValue(null) + + await setLinkPreference(req as any, resSpy) + + expect(mqttSpy).toHaveBeenCalledWith('fail', ['AMT_LinkPreference'], 'Unexpected error') + expect(resSpy.status).toHaveBeenCalledWith(500) + expect(resSpy.json).toHaveBeenCalledWith(ErrorResponse(500, 'Failed to set link preference')) + }) + + it('should auto-detect WiFi port when instanceID not provided', async () => { + req.query.instanceID = undefined + const mockResponse = { + Body: { SetLinkPreference_OUTPUT: { ReturnValue: '0' } }, + _detectedInstanceID: 'Intel(r) AMT Ethernet Port Settings 1', + _wifiPortSettings: { + InstanceID: 'Intel(r) AMT Ethernet Port Settings 1', + PhysicalConnectionType: '3', + ElementName: 'WiFi Port', + MACAddress: 'AA:BB:CC:DD:EE:FF' + } + } + setEthernetLinkPreferenceSpy.mockResolvedValue(mockResponse) + + await setLinkPreference(req as any, resSpy) + + expect(setEthernetLinkPreferenceSpy).toHaveBeenCalledWith(1, 300) + expect(resSpy.status).toHaveBeenCalledWith(200) + expect(resSpy.json).toHaveBeenCalledWith({ + status: 'Link Preference set to ME', + linkPreference: 1, + timeout: 300, + instanceID: 'Intel(r) AMT Ethernet Port Settings 1', + wifiPort: { + InstanceID: 'Intel(r) AMT Ethernet Port Settings 1', + PhysicalConnectionType: '3', + ElementName: 'WiFi Port', + MACAddress: 'AA:BB:CC:DD:EE:FF' + } + }) + }) + + it('should return 400 when no WiFi port found during auto-detection', async () => { + req.query.instanceID = undefined + const mockFaultResponse = { + Body: { + Fault: { + Code: { Value: 'NoWiFiPort' }, + Reason: { Text: 'No WiFi port found on this device. SetLinkPreference requires a WiFi interface (PhysicalConnectionType=3).' } + } + } + } + setEthernetLinkPreferenceSpy.mockResolvedValue(mockFaultResponse) + + await setLinkPreference(req as any, resSpy) + + expect(resSpy.status).toHaveBeenCalledWith(400) + expect(resSpy.json).toHaveBeenCalledWith({ + error: 'No WiFi port found on this device. SetLinkPreference requires a WiFi interface (PhysicalConnectionType=3).' + }) + }) +}) diff --git a/src/routes/amt/linkPreference.ts b/src/routes/amt/linkPreference.ts new file mode 100644 index 000000000..5b5ea3b65 --- /dev/null +++ b/src/routes/amt/linkPreference.ts @@ -0,0 +1,58 @@ +/********************************************************************* + * Copyright (c) Intel Corporation 2025 + * SPDX-License-Identifier: Apache-2.0 + **********************************************************************/ + +import { type Response, type Request } from 'express' +import { logger, messages } from '../../logging/index.js' +import { ErrorResponse } from '../../utils/amtHelper.js' +import { MqttProvider } from '../../utils/MqttProvider.js' +import { type DeviceAction } from '../../amt/DeviceAction.js' + +export async function setLinkPreference(req: Request, res: Response): Promise { + try { + const guid: string = req.params.guid + const linkPreference: number = Number(req.body.linkPreference) + const timeout: number = Number(req.body.timeout) + const deviceAction: DeviceAction = req.deviceAction as DeviceAction + + const linkPrefName = linkPreference === 1 ? 'ME' : 'HOST' + logger.debug(`Set Link Preference to ${linkPrefName} for ${guid} with timeout ${timeout}s (WiFi port auto-detected)`) + + const result = await deviceAction.setEthernetLinkPreference(linkPreference as 1 | 2, timeout) + + // Check if no WiFi port found + if (result?.Body?.Fault != null) { + const errorMsg = result.Body.Fault.Reason?.Text ?? 'WiFi port not found' + logger.error(`Set Link Preference failed: ${errorMsg}`) + MqttProvider.publishEvent('fail', ['AMT_LinkPreference'], errorMsg) + res.status(400).json({ error: errorMsg }).end() + return + } + + // Check if result is null (other error) + if (result == null) { + logger.error('Set Link Preference failed: unexpected error') + MqttProvider.publishEvent('fail', ['AMT_LinkPreference'], 'Unexpected error') + res.status(500).json(ErrorResponse(500, 'Failed to set link preference')).end() + return + } + + // Extract the detected instanceID and WiFi port settings + const detectedInstanceID = (result as any)._detectedInstanceID ?? 'Unknown' + const wifiPortSettings = (result as any)._wifiPortSettings + + MqttProvider.publishEvent('success', ['AMT_LinkPreference'], `Link Preference set to ${linkPrefName}`) + res.status(200).json({ + status: `Link Preference set to ${linkPrefName}`, + linkPreference, + timeout, + instanceID: detectedInstanceID, + wifiPort: wifiPortSettings + }).end() + } catch (error) { + logger.error(`Exception during Set Link Preference: ${error}`) + MqttProvider.publishEvent('fail', ['AMT_LinkPreference'], messages.INTERNAL_SERVICE_ERROR) + res.status(500).json(ErrorResponse(500, 'Exception during Set Link Preference')).end() + } +} diff --git a/src/routes/amt/linkPreferenceValidator.ts b/src/routes/amt/linkPreferenceValidator.ts new file mode 100644 index 000000000..4fcf872e0 --- /dev/null +++ b/src/routes/amt/linkPreferenceValidator.ts @@ -0,0 +1,13 @@ +/********************************************************************* + * Copyright (c) Intel Corporation 2025 + * SPDX-License-Identifier: Apache-2.0 + **********************************************************************/ + +import { check, query } from 'express-validator' + +export const linkPreferenceValidator = (): any => [ + check('linkPreference').isInt({ min: 1, max: 2 }).withMessage('linkPreference must be 1 (ME) or 2 (HOST)'), + check('timeout').isInt({ min: 0 }).withMessage('timeout must be a non-negative integer'), + query('instanceID').optional().isString() +] + diff --git a/src/test/collections/MPS.postman_collection.json b/src/test/collections/MPS.postman_collection.json index 97234435d..7832cb0db 100644 --- a/src/test/collections/MPS.postman_collection.json +++ b/src/test/collections/MPS.postman_collection.json @@ -2682,6 +2682,7 @@ "pm.test(\"Body should have JWT Token\", function () {\r", " var jsonData = pm.response.json();\r", " pm.expect(jsonData.token).to.be.not.null;\r", + " pm.environment.set(\"token\", jsonData.token);\r", "});\r", "" ], @@ -2694,7 +2695,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\r\n \"username\":\"standalone\",\r\n \"password\":\"G@ppm0ym\"\r\n}", + "raw": "{\r\n \"username\":\"admin\",\r\n \"password\":\"P@ssw0rd\"\r\n}", "options": { "raw": { "language": "json" @@ -2702,12 +2703,14 @@ } }, "url": { - "raw": "{{protocol}}://{{host}}/api/v1/authorize", + "raw": "{{protocol}}://{{host}}/mps/login/api/v1/authorize", "protocol": "{{protocol}}", "host": [ "{{host}}" ], "path": [ + "mps", + "login", "api", "v1", "authorize" @@ -2958,6 +2961,68 @@ } }, "response": [] + }, + { + "name": "Set Link Preference (Auto-detect WiFi)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 404\", function () {", + " pm.response.to.have.status(404);", + "});", + "", + "pm.test(\"Device should not be found\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.error).to.eq(\"Device not found/connected. Please connect again using CIRA.\")", + " pm.expect(jsonData.errorDescription).to.eq(\"guid : test-guid-123\")", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{token}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"linkPreference\": 1,\n \"timeout\": 300\n}" + }, + "url": { + "raw": "{{protocol}}://{{host}}/mps/api/v1/amt/network/linkPreference/test-guid-123", + "protocol": "{{protocol}}", + "host": [ + "{{host}}" + ], + "path": [ + "mps", + "api", + "v1", + "amt", + "network", + "linkPreference", + "test-guid-123" + ] + } + }, + "response": [] } ], "auth": { diff --git a/src/test/collections/MPS.postman_environment.json b/src/test/collections/MPS.postman_environment.json index 3d45fc261..77e567cc3 100644 --- a/src/test/collections/MPS.postman_environment.json +++ b/src/test/collections/MPS.postman_environment.json @@ -4,12 +4,12 @@ "values": [ { "key": "host", - "value": "localhost:3000", + "value": "localhost", "enabled": true }, { "key": "protocol", - "value": "http", + "value": "https", "enabled": true }, { diff --git a/src/utils/tlsConfiguration.test.ts b/src/utils/tlsConfiguration.test.ts index f7e4f5e2c..62bec9d46 100644 --- a/src/utils/tlsConfiguration.test.ts +++ b/src/utils/tlsConfiguration.test.ts @@ -5,6 +5,7 @@ import { web, mps } from './tlsConfiguration.js' import path from 'node:path' +import { fileURLToPath } from 'node:url' import fs from 'node:fs' import { logger } from '../logging/index.js' import { type mpsConfigType, type webConfigType } from '../models/Config.js' @@ -12,6 +13,9 @@ import { constants } from 'node:crypto' import { jest } from '@jest/globals' import { type SpyInstance, spyOn } from 'jest-mock' +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) + let existsSyncSpy: SpyInstance let readFileSyncSpy: SpyInstance let jsonParseSpy: SpyInstance diff --git a/swagger.yaml b/swagger.yaml index 788de350b..9e5aed040 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -739,6 +739,118 @@ paths: $ref: '#/components/schemas/DisconnectErrorResponse' 500: description: 'Internal server error' + /api/v1/amt/network/linkPreference/{guid}: + post: + summary: Set WiFi Link Preference + description: | + Set Ethernet Link Preference for WiFi ports only. + - **LinkPreference 1 (ME)**: Routes traffic through Management Engine for the specified timeout duration + - **LinkPreference 2 (HOST)**: Routes traffic through host OS (timeout should be 0) + + **WiFi Port Validation**: This API only works on WiFi ports (PhysicalConnectionType = 3). + It will return a 400 error if attempting to set link preference on wired LAN ports (types 0, 1, or 2). + + **Auto-Detection**: If `instanceID` is not provided, the API will automatically detect and use the first available WiFi port. + tags: + - AMT + parameters: + - name: guid + in: path + description: GUID of device + example: 123e4567-e89b-12d3-a456-426614174000 + required: true + schema: + type: string + - name: instanceID + in: query + description: | + InstanceID of the WiFi Ethernet port (e.g., "Intel(r) AMT Ethernet Port Settings 1"). + If not provided, the API will automatically detect the WiFi port. + Must be a WiFi port (PhysicalConnectionType = 3). + required: false + schema: + type: string + example: Intel(r) AMT Ethernet Port Settings 1 + requestBody: + description: Link preference and timeout settings + required: true + content: + application/json: + schema: + type: object + properties: + linkPreference: + type: integer + description: Link preference - 1 = ME, 2 = HOST + enum: [1, 2] + example: 1 + timeout: + type: integer + description: Timeout in seconds + example: 300 + required: + - linkPreference + - timeout + responses: + 200: + description: 'Link preference set successfully' + content: + application/json: + schema: + type: object + properties: + status: + type: string + example: Link Preference set to ME + linkPreference: + type: integer + example: 1 + timeout: + type: integer + example: 300 + instanceID: + type: string + description: The WiFi port InstanceID that was auto-detected + example: Intel(r) AMT Ethernet Port Settings 1 + wifiPort: + type: object + description: Full AMT_EthernetPortSettings information for the WiFi port + properties: + InstanceID: + type: string + example: Intel(r) AMT Ethernet Port Settings 1 + ElementName: + type: string + example: Intel(r) WiFi Link + PhysicalConnectionType: + type: string + description: 'Connection type: 0=Integrated LAN, 1=Discrete LAN, 2=Thunderbolt, 3=Wireless LAN (WiFi)' + example: "3" + MACAddress: + type: string + example: "AA:BB:CC:DD:EE:FF" + LinkIsUp: + type: boolean + example: true + 400: + description: 'Validation error - port is not a WiFi port or no WiFi port found' + content: + application/json: + schema: + type: object + properties: + error: + type: string + example: "SetLinkPreference is only applicable for WiFi ports. InstanceID \"Intel(r) AMT Ethernet Port Settings 0\" has PhysicalConnectionType=0 (0=Integrated LAN, 1=Discrete LAN, 2=Thunderbolt). WiFi ports have type 3 (Wireless LAN)." + 404: + description: 'Device not found' + content: + application/json: + schema: + $ref: '#/components/schemas/DisconnectErrorResponse' + 500: + description: 'Internal server error' + /api/v1/amt/screen/{guid}: put: summary: Put the changed settings for KVM in AMT description: Modify screen settings for KVM in AMT device