From 4cc8ba3ddddd587bd5417d33b3aefebb4c24b5ea Mon Sep 17 00:00:00 2001 From: kithwa Date: Fri, 31 Oct 2025 17:07:24 +0800 Subject: [PATCH 01/23] feat: mps API for AMT linkPreference --- src/amt/DeviceAction.ts | 20 ++++++++ src/routes/amt/index.ts | 6 +++ src/routes/amt/linkPreference.ts | 44 ++++++++++++++++ src/routes/amt/linkPreferenceValidator.ts | 11 ++++ swagger.yaml | 62 +++++++++++++++++++++++ 5 files changed, 143 insertions(+) create mode 100644 src/routes/amt/linkPreference.ts create mode 100644 src/routes/amt/linkPreferenceValidator.ts diff --git a/src/amt/DeviceAction.ts b/src/amt/DeviceAction.ts index bbfcff5ae..61c33a104 100644 --- a/src/amt/DeviceAction.ts +++ b/src/amt/DeviceAction.ts @@ -660,4 +660,24 @@ export class DeviceAction { logger.silly(`putKVMRedirectionSettingData ${messages.COMPLETE}`) return result.Envelope.Body } + + async setEthernetLinkPreference( + linkPreference: AMT.Types.EthernetPortSettings.LinkPreference, + timeoutSeconds: number + ): Promise> { + logger.silly(`setEthernetLinkPreference ${messages.REQUEST}`) + const xmlRequestBody = this.amt.EthernetPortSettings.SetLinkPreference(linkPreference, timeoutSeconds) + const result = await this.ciraHandler.Get(this.ciraSocket, xmlRequestBody) + logger.silly(`setEthernetLinkPreference ${messages.COMPLETE}`) + return result.Envelope + } + + async setLinkPreferenceME(timeoutSeconds: number): Promise> { + return await this.setEthernetLinkPreference(1, timeoutSeconds) + } + + async cancelLinkPreference(): Promise> { + // Set preference back to HOST; timeout 0 implies immediate reversion semantics + return await this.setEthernetLinkPreference(2, 0) + } } diff --git a/src/routes/amt/index.ts b/src/routes/amt/index.ts index 3fb6e78bf..99eff0a1b 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, cancelLinkPreference } from './linkPreference.js' +import { linkPreferenceValidator } from './linkPreferenceValidator.js' const amtRouter: Router = Router() @@ -68,4 +70,8 @@ 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) +amtRouter.get('/network/linkPreference/cancel/:guid', ciraMiddleware, cancelLinkPreference) + export default amtRouter diff --git a/src/routes/amt/linkPreference.ts b/src/routes/amt/linkPreference.ts new file mode 100644 index 000000000..052fa038d --- /dev/null +++ b/src/routes/amt/linkPreference.ts @@ -0,0 +1,44 @@ +/********************************************************************* + * 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 timeout: number = Number(req.body.timeout) + const deviceAction: DeviceAction = req.deviceAction as DeviceAction + + logger.debug(`Set Link Preference to ME for ${guid} with timeout ${timeout}s`) + await deviceAction.setLinkPreferenceME(timeout) + MqttProvider.publishEvent('success', ['AMT_LinkPreference'], 'Link Preference set to ME') + res.status(200).json({ status: 'Link Preference set to ME', timeout }).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() + } +} + +export async function cancelLinkPreference(req: Request, res: Response): Promise { + try { + const guid: string = req.params.guid + const deviceAction: DeviceAction = req.deviceAction as DeviceAction + + logger.debug(`Cancel Link Preference; revert to HOST for ${guid}`) + await deviceAction.cancelLinkPreference() + MqttProvider.publishEvent('success', ['AMT_LinkPreference'], 'Link Preference reverted to HOST') + res.status(200).json({ status: 'Link Preference reverted to HOST' }).end() + } catch (error) { + logger.error(`Exception during Cancel Link Preference: ${error}`) + MqttProvider.publishEvent('fail', ['AMT_LinkPreference'], messages.INTERNAL_SERVICE_ERROR) + res.status(500).json(ErrorResponse(500, 'Exception during Cancel Link Preference')).end() + } +} + diff --git a/src/routes/amt/linkPreferenceValidator.ts b/src/routes/amt/linkPreferenceValidator.ts new file mode 100644 index 000000000..9cf4ab635 --- /dev/null +++ b/src/routes/amt/linkPreferenceValidator.ts @@ -0,0 +1,11 @@ +/********************************************************************* + * Copyright (c) Intel Corporation 2025 + * SPDX-License-Identifier: Apache-2.0 + **********************************************************************/ + +import { check } from 'express-validator' + +export const linkPreferenceValidator = (): any => [ + check('timeout').isInt({ min: 0 }) +] + diff --git a/swagger.yaml b/swagger.yaml index 788de350b..95a829b0a 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -739,6 +739,68 @@ paths: $ref: '#/components/schemas/DisconnectErrorResponse' 500: description: 'Internal server error' + /api/v1/amt/network/linkPreference/{guid}: + post: + summary: Set link preference to ME with timeout + description: Set Ethernet Link Preference to ME for a duration. + tags: + - AMT + parameters: + - name: guid + in: path + description: GUID of device + example: 123e4567-e89b-12d3-a456-426614174000 + required: true + schema: + type: string + requestBody: + description: Timeout setting in seconds + required: true + content: + application/json: + schema: + type: object + properties: + timeout: + type: integer + description: Timeout in seconds + example: 300 + responses: + 200: + description: 'Link preference set' + 404: + description: 'Device not found' + content: + application/json: + schema: + $ref: '#/components/schemas/DisconnectErrorResponse' + 500: + description: 'Internal server error' + /api/v1/amt/network/linkPreference/cancel/{guid}: + get: + summary: Cancel link preference and revert to HOST + description: Cancels temporary link preference and sets preference back to HOST. + tags: + - AMT + parameters: + - name: guid + in: path + description: GUID of device + example: 123e4567-e89b-12d3-a456-426614174000 + required: true + schema: + type: string + responses: + 200: + description: 'Link preference reverted' + 404: + description: 'Device not found' + content: + application/json: + schema: + $ref: '#/components/schemas/DisconnectErrorResponse' + 500: + description: 'Internal server error' put: summary: Put the changed settings for KVM in AMT description: Modify screen settings for KVM in AMT device From d11b7eec51b2881f93ae044e53dc54c7ace6ae2d Mon Sep 17 00:00:00 2001 From: "Cheah, Kit Hwa" Date: Thu, 13 Nov 2025 21:52:22 +0800 Subject: [PATCH 02/23] fix: add instanceID as API input param --- src/amt/DeviceAction.ts | 13 +++++++------ src/routes/amt/linkPreference.ts | 14 ++++++++------ src/routes/amt/linkPreferenceValidator.ts | 3 ++- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/amt/DeviceAction.ts b/src/amt/DeviceAction.ts index 61c33a104..b89b30b80 100644 --- a/src/amt/DeviceAction.ts +++ b/src/amt/DeviceAction.ts @@ -663,21 +663,22 @@ export class DeviceAction { async setEthernetLinkPreference( linkPreference: AMT.Types.EthernetPortSettings.LinkPreference, - timeoutSeconds: number + timeoutSeconds: number, + instanceID: string = 'Intel(r) AMT Ethernet Port Settings 0' ): Promise> { logger.silly(`setEthernetLinkPreference ${messages.REQUEST}`) - const xmlRequestBody = this.amt.EthernetPortSettings.SetLinkPreference(linkPreference, timeoutSeconds) + const xmlRequestBody = this.amt.EthernetPortSettings.SetLinkPreference(linkPreference, timeoutSeconds, instanceID) const result = await this.ciraHandler.Get(this.ciraSocket, xmlRequestBody) logger.silly(`setEthernetLinkPreference ${messages.COMPLETE}`) return result.Envelope } - async setLinkPreferenceME(timeoutSeconds: number): Promise> { - return await this.setEthernetLinkPreference(1, timeoutSeconds) + async setLinkPreferenceME(timeoutSeconds: number, instanceID?: string): Promise> { + return await this.setEthernetLinkPreference(1, timeoutSeconds, instanceID) } - async cancelLinkPreference(): Promise> { + async cancelLinkPreference(instanceID?: string): Promise> { // Set preference back to HOST; timeout 0 implies immediate reversion semantics - return await this.setEthernetLinkPreference(2, 0) + return await this.setEthernetLinkPreference(2, 0, instanceID) } } diff --git a/src/routes/amt/linkPreference.ts b/src/routes/amt/linkPreference.ts index 052fa038d..f5fb55970 100644 --- a/src/routes/amt/linkPreference.ts +++ b/src/routes/amt/linkPreference.ts @@ -13,12 +13,13 @@ export async function setLinkPreference(req: Request, res: Response): Promise { try { const guid: string = req.params.guid + const instanceID: string | undefined = req.query.instanceID as string const deviceAction: DeviceAction = req.deviceAction as DeviceAction - logger.debug(`Cancel Link Preference; revert to HOST for ${guid}`) - await deviceAction.cancelLinkPreference() + logger.debug(`Cancel Link Preference; revert to HOST for ${guid}, instanceID: ${instanceID ?? 'default'}`) + await deviceAction.cancelLinkPreference(instanceID) MqttProvider.publishEvent('success', ['AMT_LinkPreference'], 'Link Preference reverted to HOST') - res.status(200).json({ status: 'Link Preference reverted to HOST' }).end() + res.status(200).json({ status: 'Link Preference reverted to HOST', instanceID: instanceID ?? 'Intel(r) AMT Ethernet Port Settings 0' }).end() } catch (error) { logger.error(`Exception during Cancel Link Preference: ${error}`) MqttProvider.publishEvent('fail', ['AMT_LinkPreference'], messages.INTERNAL_SERVICE_ERROR) diff --git a/src/routes/amt/linkPreferenceValidator.ts b/src/routes/amt/linkPreferenceValidator.ts index 9cf4ab635..2c81b7d3b 100644 --- a/src/routes/amt/linkPreferenceValidator.ts +++ b/src/routes/amt/linkPreferenceValidator.ts @@ -6,6 +6,7 @@ import { check } from 'express-validator' export const linkPreferenceValidator = (): any => [ - check('timeout').isInt({ min: 0 }) + check('timeout').isInt({ min: 0 }), + check('instanceID').optional().isString() ] From 14dfe5af8c8229c6cd2187ca4d159054b9d3be9e Mon Sep 17 00:00:00 2001 From: "Cheah, Kit Hwa" Date: Fri, 14 Nov 2025 08:59:17 +0800 Subject: [PATCH 03/23] feat: add Get() MPS API for EthernetPortSettings --- src/amt/DeviceAction.ts | 11 ++++ src/routes/amt/getEthernetPortSettings.ts | 27 ++++++++++ src/routes/amt/index.ts | 4 ++ swagger.yaml | 66 +++++++++++++++++++++++ 4 files changed, 108 insertions(+) create mode 100644 src/routes/amt/getEthernetPortSettings.ts diff --git a/src/amt/DeviceAction.ts b/src/amt/DeviceAction.ts index b89b30b80..b888d15fb 100644 --- a/src/amt/DeviceAction.ts +++ b/src/amt/DeviceAction.ts @@ -661,6 +661,17 @@ export class DeviceAction { return result.Envelope.Body } + async getEthernetPortSettings( + instanceID: string = 'Intel(r) AMT Ethernet Port Settings 0' + ): Promise { + logger.silly(`getEthernetPortSettings ${messages.REQUEST}`) + const selector = { name: 'InstanceID', value: instanceID } + const xmlRequestBody = this.amt.EthernetPortSettings.Get(selector) + const result = await this.ciraHandler.Get(this.ciraSocket, xmlRequestBody) + logger.silly(`getEthernetPortSettings ${messages.COMPLETE}`) + return result.Envelope.Body + } + async setEthernetLinkPreference( linkPreference: AMT.Types.EthernetPortSettings.LinkPreference, timeoutSeconds: number, diff --git a/src/routes/amt/getEthernetPortSettings.ts b/src/routes/amt/getEthernetPortSettings.ts new file mode 100644 index 000000000..25c2fa8ac --- /dev/null +++ b/src/routes/amt/getEthernetPortSettings.ts @@ -0,0 +1,27 @@ +/********************************************************************* + * 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 getEthernetPortSettings(req: Request, res: Response): Promise { + try { + const guid: string = req.params.guid + const instanceID: string | undefined = req.query.instanceID as string + const deviceAction: DeviceAction = req.deviceAction as DeviceAction + + logger.debug(`Get Ethernet Port Settings for ${guid}, instanceID: ${instanceID ?? 'default'}`) + const result = await deviceAction.getEthernetPortSettings(instanceID) + MqttProvider.publishEvent('success', ['AMT_EthernetPortSettings'], 'Ethernet Port Settings retrieved') + res.status(200).json(result).end() + } catch (error) { + logger.error(`Exception during Get Ethernet Port Settings: ${error}`) + MqttProvider.publishEvent('fail', ['AMT_EthernetPortSettings'], messages.INTERNAL_SERVICE_ERROR) + res.status(500).json(ErrorResponse(500, 'Exception during Get Ethernet Port Settings')).end() + } +} diff --git a/src/routes/amt/index.ts b/src/routes/amt/index.ts index 99eff0a1b..d76988142 100644 --- a/src/routes/amt/index.ts +++ b/src/routes/amt/index.ts @@ -40,6 +40,7 @@ import { getScreenSettingData } from './kvm/get.js' import { setKVMRedirectionSettingData } from './kvm/set.js' import { setLinkPreference, cancelLinkPreference } from './linkPreference.js' import { linkPreferenceValidator } from './linkPreferenceValidator.js' +import { getEthernetPortSettings } from './getEthernetPortSettings.js' const amtRouter: Router = Router() @@ -70,6 +71,9 @@ amtRouter.post('/certificates/:guid', certValidator(), validateMiddleware, ciraM amtRouter.get('/kvm/displays/:guid', ciraMiddleware, getScreenSettingData) amtRouter.put('/kvm/displays/:guid', validator(), ciraMiddleware, setKVMRedirectionSettingData) +// Ethernet Port Settings +amtRouter.get('/network/ethernetPortSettings/:guid', ciraMiddleware, getEthernetPortSettings) + // Link Preference (ME/HOST) amtRouter.post('/network/linkPreference/:guid', linkPreferenceValidator(), validateMiddleware, ciraMiddleware, setLinkPreference) amtRouter.get('/network/linkPreference/cancel/:guid', ciraMiddleware, cancelLinkPreference) diff --git a/swagger.yaml b/swagger.yaml index 95a829b0a..657741d5a 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -739,6 +739,72 @@ paths: $ref: '#/components/schemas/DisconnectErrorResponse' 500: description: 'Internal server error' + /api/v1/amt/network/ethernetPortSettings/{guid}: + get: + summary: Get Ethernet Port Settings + description: Retrieve Ethernet Port Settings including link preference, link policy, and other network configurations. + 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 Ethernet port (e.g., "Intel(r) AMT Ethernet Port Settings 0") + required: false + schema: + type: string + default: Intel(r) AMT Ethernet Port Settings 0 + responses: + 200: + description: 'Ethernet Port Settings retrieved successfully' + content: + application/json: + schema: + type: object + properties: + InstanceID: + type: string + example: Intel(r) AMT Ethernet Port Settings 0 + ElementName: + type: string + example: Intel(r) AMT Ethernet Port Settings + LinkPreference: + type: integer + description: 1 = ME, 2 = HOST + example: 2 + LinkControl: + type: integer + description: 1 = ME, 2 = HOST + example: 2 + LinkPolicy: + type: array + items: + type: integer + description: Array of link policy values + example: [1, 14, 16] + LinkProtection: + type: integer + example: 1 + SharedMAC: + type: boolean + example: true + MACAddress: + type: string + example: "00:11:22:33:44:55" + 404: + description: 'Device not found' + content: + application/json: + schema: + $ref: '#/components/schemas/DisconnectErrorResponse' + 500: + description: 'Internal server error' /api/v1/amt/network/linkPreference/{guid}: post: summary: Set link preference to ME with timeout From 6b2fb1d1f2a1f3336f7e2633c73ab756c79f22d9 Mon Sep 17 00:00:00 2001 From: "Cheah, Kit Hwa" Date: Fri, 14 Nov 2025 09:12:04 +0800 Subject: [PATCH 04/23] fix: set instanceID as REST Query param instead of in the body --- src/routes/amt/linkPreference.ts | 2 +- src/routes/amt/linkPreferenceValidator.ts | 4 ++-- swagger.yaml | 16 ++++++++++++++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/routes/amt/linkPreference.ts b/src/routes/amt/linkPreference.ts index f5fb55970..2c391b986 100644 --- a/src/routes/amt/linkPreference.ts +++ b/src/routes/amt/linkPreference.ts @@ -13,7 +13,7 @@ export async function setLinkPreference(req: Request, res: Response): Promise [ check('timeout').isInt({ min: 0 }), - check('instanceID').optional().isString() + query('instanceID').optional().isString() ] diff --git a/swagger.yaml b/swagger.yaml index 657741d5a..67ec2069b 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -819,6 +819,13 @@ paths: required: true schema: type: string + - name: instanceID + in: query + description: InstanceID of the Ethernet port (e.g., "Intel(r) AMT Ethernet Port Settings 0") + required: false + schema: + type: string + default: Intel(r) AMT Ethernet Port Settings 0 requestBody: description: Timeout setting in seconds required: true @@ -831,6 +838,8 @@ paths: type: integer description: Timeout in seconds example: 300 + required: + - timeout responses: 200: description: 'Link preference set' @@ -856,6 +865,13 @@ paths: required: true schema: type: string + - name: instanceID + in: query + description: InstanceID of the Ethernet port (e.g., "Intel(r) AMT Ethernet Port Settings 0") + required: false + schema: + type: string + default: Intel(r) AMT Ethernet Port Settings 0 responses: 200: description: 'Link preference reverted' From ea951df22cd5a7504d75e4384d8a573fee53e99d Mon Sep 17 00:00:00 2001 From: "Cheah, Kit Hwa" Date: Fri, 14 Nov 2025 10:06:45 +0800 Subject: [PATCH 05/23] feat: add Enumerate API for EthernetPortSettings --- src/amt/DeviceAction.ts | 16 +++++ .../amt/enumerateEthernetPortSettings.ts | 26 +++++++++ src/routes/amt/index.ts | 2 + swagger.yaml | 58 +++++++++++++++++++ 4 files changed, 102 insertions(+) create mode 100644 src/routes/amt/enumerateEthernetPortSettings.ts diff --git a/src/amt/DeviceAction.ts b/src/amt/DeviceAction.ts index b888d15fb..b90867625 100644 --- a/src/amt/DeviceAction.ts +++ b/src/amt/DeviceAction.ts @@ -672,6 +672,22 @@ export class DeviceAction { return result.Envelope.Body } + async enumerateEthernetPortSettings(): Promise< + Common.Models.Envelope> + > { + logger.silly(`enumerateEthernetPortSettings ${messages.REQUEST}`) + let xmlRequestBody = this.amt.EthernetPortSettings.Enumerate() + const enumResponse = await this.ciraHandler.Enumerate(this.ciraSocket, xmlRequestBody) + if (enumResponse == null) { + logger.error(`enumerateEthernetPortSettings 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(`enumerateEthernetPortSettings ${messages.COMPLETE}`) + return pullResponse.Envelope + } + async setEthernetLinkPreference( linkPreference: AMT.Types.EthernetPortSettings.LinkPreference, timeoutSeconds: number, diff --git a/src/routes/amt/enumerateEthernetPortSettings.ts b/src/routes/amt/enumerateEthernetPortSettings.ts new file mode 100644 index 000000000..bf957df24 --- /dev/null +++ b/src/routes/amt/enumerateEthernetPortSettings.ts @@ -0,0 +1,26 @@ +/********************************************************************* + * 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 enumerateEthernetPortSettings(req: Request, res: Response): Promise { + try { + const guid: string = req.params.guid + const deviceAction: DeviceAction = req.deviceAction as DeviceAction + + logger.debug(`Enumerate Ethernet Port Settings for ${guid}`) + const result = await deviceAction.enumerateEthernetPortSettings() + MqttProvider.publishEvent('success', ['AMT_EthernetPortSettings'], 'Ethernet Port Settings enumerated') + res.status(200).json(result).end() + } catch (error) { + logger.error(`Exception during Enumerate Ethernet Port Settings: ${error}`) + MqttProvider.publishEvent('fail', ['AMT_EthernetPortSettings'], messages.INTERNAL_SERVICE_ERROR) + res.status(500).json(ErrorResponse(500, 'Exception during Enumerate Ethernet Port Settings')).end() + } +} diff --git a/src/routes/amt/index.ts b/src/routes/amt/index.ts index d76988142..dc446596c 100644 --- a/src/routes/amt/index.ts +++ b/src/routes/amt/index.ts @@ -41,6 +41,7 @@ import { setKVMRedirectionSettingData } from './kvm/set.js' import { setLinkPreference, cancelLinkPreference } from './linkPreference.js' import { linkPreferenceValidator } from './linkPreferenceValidator.js' import { getEthernetPortSettings } from './getEthernetPortSettings.js' +import { enumerateEthernetPortSettings } from './enumerateEthernetPortSettings.js' const amtRouter: Router = Router() @@ -73,6 +74,7 @@ amtRouter.put('/kvm/displays/:guid', validator(), ciraMiddleware, setKVMRedirect // Ethernet Port Settings amtRouter.get('/network/ethernetPortSettings/:guid', ciraMiddleware, getEthernetPortSettings) +amtRouter.get('/network/ethernetPortSettings/enumerate/:guid', ciraMiddleware, enumerateEthernetPortSettings) // Link Preference (ME/HOST) amtRouter.post('/network/linkPreference/:guid', linkPreferenceValidator(), validateMiddleware, ciraMiddleware, setLinkPreference) diff --git a/swagger.yaml b/swagger.yaml index 67ec2069b..da58b0d63 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -805,6 +805,64 @@ paths: $ref: '#/components/schemas/DisconnectErrorResponse' 500: description: 'Internal server error' + /api/v1/amt/network/ethernetPortSettings/enumerate/{guid}: + get: + summary: Enumerate all Ethernet Port Settings + description: Retrieve all Ethernet Port Settings instances (all network ports). + tags: + - AMT + parameters: + - name: guid + in: path + description: GUID of device + example: 123e4567-e89b-12d3-a456-426614174000 + required: true + schema: + type: string + responses: + 200: + description: 'All Ethernet Port Settings retrieved successfully' + content: + application/json: + schema: + type: object + properties: + Body: + type: object + properties: + PullResponse: + type: object + properties: + Items: + type: array + items: + type: object + properties: + InstanceID: + type: string + example: Intel(r) AMT Ethernet Port Settings 0 + ElementName: + type: string + example: Intel(r) AMT Ethernet Port Settings + LinkPreference: + type: integer + description: 1 = ME, 2 = HOST + example: 2 + LinkControl: + type: integer + description: 1 = ME, 2 = HOST + example: 2 + MACAddress: + type: string + example: "00:11:22:33:44:55" + 404: + description: 'Device not found' + content: + application/json: + schema: + $ref: '#/components/schemas/DisconnectErrorResponse' + 500: + description: 'Internal server error' /api/v1/amt/network/linkPreference/{guid}: post: summary: Set link preference to ME with timeout From 3ee78fd1da1d5cb969d1fc865a0ee0c4e6835e8a Mon Sep 17 00:00:00 2001 From: "Cheah, Kit Hwa" Date: Fri, 14 Nov 2025 10:38:33 +0800 Subject: [PATCH 06/23] fix: remove cancelLinkPreferenc API in MPS --- src/routes/amt/index.ts | 3 +- src/routes/amt/linkPreference.ts | 33 ++++-------- src/routes/amt/linkPreferenceValidator.ts | 1 + swagger.yaml | 62 ++++++++++------------- 4 files changed, 41 insertions(+), 58 deletions(-) diff --git a/src/routes/amt/index.ts b/src/routes/amt/index.ts index dc446596c..f80cc46c7 100644 --- a/src/routes/amt/index.ts +++ b/src/routes/amt/index.ts @@ -38,7 +38,7 @@ import { validator } from './kvm/validator.js' import { get } from 'http' import { getScreenSettingData } from './kvm/get.js' import { setKVMRedirectionSettingData } from './kvm/set.js' -import { setLinkPreference, cancelLinkPreference } from './linkPreference.js' +import { setLinkPreference } from './linkPreference.js' import { linkPreferenceValidator } from './linkPreferenceValidator.js' import { getEthernetPortSettings } from './getEthernetPortSettings.js' import { enumerateEthernetPortSettings } from './enumerateEthernetPortSettings.js' @@ -78,6 +78,5 @@ amtRouter.get('/network/ethernetPortSettings/enumerate/:guid', ciraMiddleware, e // Link Preference (ME/HOST) amtRouter.post('/network/linkPreference/:guid', linkPreferenceValidator(), validateMiddleware, ciraMiddleware, setLinkPreference) -amtRouter.get('/network/linkPreference/cancel/:guid', ciraMiddleware, cancelLinkPreference) export default amtRouter diff --git a/src/routes/amt/linkPreference.ts b/src/routes/amt/linkPreference.ts index 2c391b986..06bf5df6d 100644 --- a/src/routes/amt/linkPreference.ts +++ b/src/routes/amt/linkPreference.ts @@ -12,35 +12,24 @@ 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 instanceID: string | undefined = req.query.instanceID as string const deviceAction: DeviceAction = req.deviceAction as DeviceAction - logger.debug(`Set Link Preference to ME for ${guid} with timeout ${timeout}s, instanceID: ${instanceID ?? 'default'}`) - await deviceAction.setLinkPreferenceME(timeout, instanceID) - MqttProvider.publishEvent('success', ['AMT_LinkPreference'], 'Link Preference set to ME') - res.status(200).json({ status: 'Link Preference set to ME', timeout, instanceID: instanceID ?? 'Intel(r) AMT Ethernet Port Settings 0' }).end() + const linkPrefName = linkPreference === 1 ? 'ME' : 'HOST' + logger.debug(`Set Link Preference to ${linkPrefName} for ${guid} with timeout ${timeout}s, instanceID: ${instanceID ?? 'default'}`) + await deviceAction.setEthernetLinkPreference(linkPreference as 1 | 2, timeout, instanceID) + MqttProvider.publishEvent('success', ['AMT_LinkPreference'], `Link Preference set to ${linkPrefName}`) + res.status(200).json({ + status: `Link Preference set to ${linkPrefName}`, + linkPreference, + timeout, + instanceID: instanceID ?? 'Intel(r) AMT Ethernet Port Settings 0' + }).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() } } - -export async function cancelLinkPreference(req: Request, res: Response): Promise { - try { - const guid: string = req.params.guid - const instanceID: string | undefined = req.query.instanceID as string - const deviceAction: DeviceAction = req.deviceAction as DeviceAction - - logger.debug(`Cancel Link Preference; revert to HOST for ${guid}, instanceID: ${instanceID ?? 'default'}`) - await deviceAction.cancelLinkPreference(instanceID) - MqttProvider.publishEvent('success', ['AMT_LinkPreference'], 'Link Preference reverted to HOST') - res.status(200).json({ status: 'Link Preference reverted to HOST', instanceID: instanceID ?? 'Intel(r) AMT Ethernet Port Settings 0' }).end() - } catch (error) { - logger.error(`Exception during Cancel Link Preference: ${error}`) - MqttProvider.publishEvent('fail', ['AMT_LinkPreference'], messages.INTERNAL_SERVICE_ERROR) - res.status(500).json(ErrorResponse(500, 'Exception during Cancel Link Preference')).end() - } -} - diff --git a/src/routes/amt/linkPreferenceValidator.ts b/src/routes/amt/linkPreferenceValidator.ts index 0390a84d5..83361a41c 100644 --- a/src/routes/amt/linkPreferenceValidator.ts +++ b/src/routes/amt/linkPreferenceValidator.ts @@ -6,6 +6,7 @@ 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 }), query('instanceID').optional().isString() ] diff --git a/swagger.yaml b/swagger.yaml index da58b0d63..38db809e3 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -865,8 +865,8 @@ paths: description: 'Internal server error' /api/v1/amt/network/linkPreference/{guid}: post: - summary: Set link preference to ME with timeout - description: Set Ethernet Link Preference to ME for a duration. + summary: Set link preference with timeout + description: Set Ethernet Link Preference to ME (1) or HOST (2) for a specified duration. tags: - AMT parameters: @@ -885,54 +885,47 @@ paths: type: string default: Intel(r) AMT Ethernet Port Settings 0 requestBody: - description: Timeout setting in seconds + 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 + description: Timeout in seconds (0 for immediate change without timeout) example: 300 required: + - linkPreference - timeout responses: 200: - description: 'Link preference set' - 404: - description: 'Device not found' + description: 'Link preference set successfully' content: application/json: schema: - $ref: '#/components/schemas/DisconnectErrorResponse' - 500: - description: 'Internal server error' - /api/v1/amt/network/linkPreference/cancel/{guid}: - get: - summary: Cancel link preference and revert to HOST - description: Cancels temporary link preference and sets preference back to HOST. - 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 Ethernet port (e.g., "Intel(r) AMT Ethernet Port Settings 0") - required: false - schema: - type: string - default: Intel(r) AMT Ethernet Port Settings 0 - responses: - 200: - description: 'Link preference reverted' + 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 + example: Intel(r) AMT Ethernet Port Settings 0 + 400: + description: 'Invalid parameters' 404: description: 'Device not found' content: @@ -941,6 +934,7 @@ paths: $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 From ad292fbe717dd74c1656f70b0adc2bf669bbb61b Mon Sep 17 00:00:00 2001 From: "Cheah, Kit Hwa" Date: Fri, 14 Nov 2025 11:10:35 +0800 Subject: [PATCH 07/23] fix: timeout by default 0 if LinkPreference Host --- src/amt/DeviceAction.ts | 4 +++- src/routes/amt/linkPreference.ts | 2 ++ src/routes/amt/linkPreferenceValidator.ts | 4 +++- swagger.yaml | 8 ++++++-- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/amt/DeviceAction.ts b/src/amt/DeviceAction.ts index b90867625..f62c4f6f8 100644 --- a/src/amt/DeviceAction.ts +++ b/src/amt/DeviceAction.ts @@ -694,6 +694,8 @@ export class DeviceAction { instanceID: string = 'Intel(r) AMT Ethernet Port Settings 0' ): Promise> { logger.silly(`setEthernetLinkPreference ${messages.REQUEST}`) + // Note: timeout is only applicable when linkPreference is ME (1) + // When linkPreference is HOST (2), timeout is automatically set to 0 in wsman-messages const xmlRequestBody = this.amt.EthernetPortSettings.SetLinkPreference(linkPreference, timeoutSeconds, instanceID) const result = await this.ciraHandler.Get(this.ciraSocket, xmlRequestBody) logger.silly(`setEthernetLinkPreference ${messages.COMPLETE}`) @@ -705,7 +707,7 @@ export class DeviceAction { } async cancelLinkPreference(instanceID?: string): Promise> { - // Set preference back to HOST; timeout 0 implies immediate reversion semantics + // Set preference back to HOST (timeout is ignored for HOST preference) return await this.setEthernetLinkPreference(2, 0, instanceID) } } diff --git a/src/routes/amt/linkPreference.ts b/src/routes/amt/linkPreference.ts index 06bf5df6d..a7459afda 100644 --- a/src/routes/amt/linkPreference.ts +++ b/src/routes/amt/linkPreference.ts @@ -18,6 +18,8 @@ export async function setLinkPreference(req: Request, res: Response): Promise [ check('linkPreference').isInt({ min: 1, max: 2 }).withMessage('linkPreference must be 1 (ME) or 2 (HOST)'), - check('timeout').isInt({ min: 0 }), + check('timeout').isInt({ min: 0 }).withMessage('timeout must be a non-negative integer'), query('instanceID').optional().isString() ] diff --git a/swagger.yaml b/swagger.yaml index 38db809e3..b781180d4 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -866,7 +866,11 @@ paths: /api/v1/amt/network/linkPreference/{guid}: post: summary: Set link preference with timeout - description: Set Ethernet Link Preference to ME (1) or HOST (2) for a specified duration. + description: | + Set Ethernet Link Preference to ME (1) or HOST (2) for a specified duration. + + **Important**: The timeout parameter is only applicable when linkPreference is set to ME (1). + When linkPreference is set to HOST (2), the timeout value is automatically ignored and set to 0 internally. tags: - AMT parameters: @@ -899,7 +903,7 @@ paths: example: 1 timeout: type: integer - description: Timeout in seconds (0 for immediate change without timeout) + description: Timeout in seconds. Only applicable when linkPreference=1 (ME). Automatically ignored when linkPreference=2 (HOST). example: 300 required: - linkPreference From a88c2051d8c521ae2afb9f0aa69d2062d8b9be6a Mon Sep 17 00:00:00 2001 From: "Cheah, Kit Hwa" Date: Fri, 14 Nov 2025 12:49:06 +0800 Subject: [PATCH 08/23] fix: revert auto-configure timeout to 0 --- src/amt/DeviceAction.ts | 4 +--- src/routes/amt/linkPreference.ts | 2 -- src/routes/amt/linkPreferenceValidator.ts | 2 -- swagger.yaml | 8 ++------ 4 files changed, 3 insertions(+), 13 deletions(-) diff --git a/src/amt/DeviceAction.ts b/src/amt/DeviceAction.ts index f62c4f6f8..5d7237d9d 100644 --- a/src/amt/DeviceAction.ts +++ b/src/amt/DeviceAction.ts @@ -694,8 +694,6 @@ export class DeviceAction { instanceID: string = 'Intel(r) AMT Ethernet Port Settings 0' ): Promise> { logger.silly(`setEthernetLinkPreference ${messages.REQUEST}`) - // Note: timeout is only applicable when linkPreference is ME (1) - // When linkPreference is HOST (2), timeout is automatically set to 0 in wsman-messages const xmlRequestBody = this.amt.EthernetPortSettings.SetLinkPreference(linkPreference, timeoutSeconds, instanceID) const result = await this.ciraHandler.Get(this.ciraSocket, xmlRequestBody) logger.silly(`setEthernetLinkPreference ${messages.COMPLETE}`) @@ -707,7 +705,7 @@ export class DeviceAction { } async cancelLinkPreference(instanceID?: string): Promise> { - // Set preference back to HOST (timeout is ignored for HOST preference) + // Set preference back to HOST with timeout 0 return await this.setEthernetLinkPreference(2, 0, instanceID) } } diff --git a/src/routes/amt/linkPreference.ts b/src/routes/amt/linkPreference.ts index a7459afda..06bf5df6d 100644 --- a/src/routes/amt/linkPreference.ts +++ b/src/routes/amt/linkPreference.ts @@ -18,8 +18,6 @@ export async function setLinkPreference(req: Request, res: Response): Promise [ 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'), diff --git a/swagger.yaml b/swagger.yaml index b781180d4..25c65326a 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -866,11 +866,7 @@ paths: /api/v1/amt/network/linkPreference/{guid}: post: summary: Set link preference with timeout - description: | - Set Ethernet Link Preference to ME (1) or HOST (2) for a specified duration. - - **Important**: The timeout parameter is only applicable when linkPreference is set to ME (1). - When linkPreference is set to HOST (2), the timeout value is automatically ignored and set to 0 internally. + description: Set Ethernet Link Preference to ME (1) for a specified duration, or to HOST (2). tags: - AMT parameters: @@ -903,7 +899,7 @@ paths: example: 1 timeout: type: integer - description: Timeout in seconds. Only applicable when linkPreference=1 (ME). Automatically ignored when linkPreference=2 (HOST). + description: Timeout in seconds example: 300 required: - linkPreference From d55d3e826a75765f994a056019a2409edb1cc54b Mon Sep 17 00:00:00 2001 From: "Cheah, Kit Hwa" Date: Fri, 14 Nov 2025 13:15:41 +0800 Subject: [PATCH 09/23] test: add Unit Tests for new network APIs --- .../amt/enumerateEthernetPortSettings.test.ts | 118 +++++++++++++++ .../amt/getEthernetPortSettings.test.ts | 96 ++++++++++++ src/routes/amt/linkPreference.test.ts | 140 ++++++++++++++++++ 3 files changed, 354 insertions(+) create mode 100644 src/routes/amt/enumerateEthernetPortSettings.test.ts create mode 100644 src/routes/amt/getEthernetPortSettings.test.ts create mode 100644 src/routes/amt/linkPreference.test.ts diff --git a/src/routes/amt/enumerateEthernetPortSettings.test.ts b/src/routes/amt/enumerateEthernetPortSettings.test.ts new file mode 100644 index 000000000..fc2d909f6 --- /dev/null +++ b/src/routes/amt/enumerateEthernetPortSettings.test.ts @@ -0,0 +1,118 @@ +/********************************************************************* + * 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 { enumerateEthernetPortSettings } from './enumerateEthernetPortSettings.js' + +describe('Enumerate Ethernet Port Settings', () => { + let req: Express.Request + let resSpy + let mqttSpy: SpyInstance + let enumerateEthernetPortSettingsSpy: SpyInstance + let device: DeviceAction + let mockEnumerateResponse + + beforeEach(() => { + const handler = new CIRAHandler(new HttpHandler(), 'admin', 'P@ssw0rd') + device = new DeviceAction(handler, null) + + mockEnumerateResponse = { + Body: { + PullResponse: { + Items: { + AMT_EthernetPortSettings: [ + { + InstanceID: 'Intel(r) AMT Ethernet Port Settings 0', + ElementName: 'Intel(r) AMT Ethernet Port Settings', + MACAddress: 'a4-ae-11-1c-02-4d', + LinkIsUp: true, + LinkPolicy: [1, 14, 16], + LinkPreference: 2, + LinkControl: 2 + }, + { + InstanceID: 'Intel(r) AMT Ethernet Port Settings 1', + ElementName: 'Intel(r) AMT Ethernet Port Settings', + MACAddress: 'a4-ae-11-1c-02-4e', + LinkIsUp: false, + LinkPolicy: [1, 14], + LinkPreference: 1, + LinkControl: 1 + } + ] + } + } + } + } + + req = { + params: { + guid: '123456' + }, + deviceAction: device + } + resSpy = createSpyObj('Response', [ + 'status', + 'json', + 'end', + 'send' + ]) + resSpy.status.mockReturnThis() + resSpy.json.mockReturnThis() + resSpy.send.mockReturnThis() + mqttSpy = spyOn(MqttProvider, 'publishEvent') + enumerateEthernetPortSettingsSpy = spyOn(device, 'enumerateEthernetPortSettings').mockResolvedValue(mockEnumerateResponse as any) + }) + + it('should enumerate all ethernet port settings', async () => { + await enumerateEthernetPortSettings(req as any, resSpy) + + expect(enumerateEthernetPortSettingsSpy).toHaveBeenCalled() + expect(mqttSpy).toHaveBeenCalledWith('success', ['AMT_EthernetPortSettings'], 'Ethernet Port Settings enumerated') + expect(resSpy.status).toHaveBeenCalledWith(200) + expect(resSpy.json).toHaveBeenCalledWith(mockEnumerateResponse.Body.PullResponse.Items.AMT_EthernetPortSettings) + }) + + it('should handle single port in enumerate response', async () => { + const singlePortResponse = { + Body: { + PullResponse: { + Items: { + AMT_EthernetPortSettings: { + InstanceID: 'Intel(r) AMT Ethernet Port Settings 0', + ElementName: 'Intel(r) AMT Ethernet Port Settings', + MACAddress: 'a4-ae-11-1c-02-4d' + } + } + } + } + } + enumerateEthernetPortSettingsSpy.mockResolvedValue(singlePortResponse as any) + + await enumerateEthernetPortSettings(req as any, resSpy) + + expect(resSpy.status).toHaveBeenCalledWith(200) + // Should wrap single object in array + expect(resSpy.json).toHaveBeenCalled() + }) + + it('should handle errors gracefully', async () => { + const errorMessage = 'Failed to enumerate ethernet port settings' + enumerateEthernetPortSettingsSpy.mockRejectedValue(new Error(errorMessage)) + + await enumerateEthernetPortSettings(req as any, resSpy) + + expect(mqttSpy).toHaveBeenCalledWith('fail', ['AMT_EthernetPortSettings'], messages.INTERNAL_SERVICE_ERROR) + expect(resSpy.status).toHaveBeenCalledWith(500) + expect(resSpy.json).toHaveBeenCalledWith(ErrorResponse(500, 'Exception during Enumerate Ethernet Port Settings')) + }) +}) diff --git a/src/routes/amt/getEthernetPortSettings.test.ts b/src/routes/amt/getEthernetPortSettings.test.ts new file mode 100644 index 000000000..dc3f8f436 --- /dev/null +++ b/src/routes/amt/getEthernetPortSettings.test.ts @@ -0,0 +1,96 @@ +/********************************************************************* + * 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 { getEthernetPortSettings } from './getEthernetPortSettings.js' + +describe('Get Ethernet Port Settings', () => { + let req: Express.Request + let resSpy + let mqttSpy: SpyInstance + let getEthernetPortSettingsSpy: SpyInstance + let device: DeviceAction + let mockEthernetPortSettings + + beforeEach(() => { + const handler = new CIRAHandler(new HttpHandler(), 'admin', 'P@ssw0rd') + device = new DeviceAction(handler, null) + + mockEthernetPortSettings = { + Body: { + AMT_EthernetPortSettings: { + InstanceID: 'Intel(r) AMT Ethernet Port Settings 0', + ElementName: 'Intel(r) AMT Ethernet Port Settings', + MACAddress: 'a4-ae-11-1c-02-4d', + LinkIsUp: true, + LinkPolicy: [1, 14, 16], + LinkPreference: 2, + LinkControl: 2, + SharedMAC: true, + SharedStaticIp: false, + SharedDynamicIP: true, + IpSyncEnabled: true, + DHCPEnabled: true, + PhysicalConnectionType: 0 + } + } + } + + req = { + params: { + guid: '123456' + }, + query: {}, + deviceAction: device + } + resSpy = createSpyObj('Response', [ + 'status', + 'json', + 'end', + 'send' + ]) + resSpy.status.mockReturnThis() + resSpy.json.mockReturnThis() + resSpy.send.mockReturnThis() + mqttSpy = spyOn(MqttProvider, 'publishEvent') + getEthernetPortSettingsSpy = spyOn(device, 'getEthernetPortSettings').mockResolvedValue(mockEthernetPortSettings as any) + }) + + it('should get ethernet port settings with default instanceID', async () => { + await getEthernetPortSettings(req as any, resSpy) + + expect(getEthernetPortSettingsSpy).toHaveBeenCalledWith(undefined) + expect(mqttSpy).toHaveBeenCalledWith('success', ['AMT_EthernetPortSettings'], 'Ethernet Port Settings retrieved') + expect(resSpy.status).toHaveBeenCalledWith(200) + expect(resSpy.json).toHaveBeenCalledWith(mockEthernetPortSettings.Body.AMT_EthernetPortSettings) + }) + + it('should get ethernet port settings with custom instanceID', async () => { + req.query.instanceID = 'Intel(r) AMT Ethernet Port Settings 1' + + await getEthernetPortSettings(req as any, resSpy) + + expect(getEthernetPortSettingsSpy).toHaveBeenCalledWith('Intel(r) AMT Ethernet Port Settings 1') + expect(resSpy.status).toHaveBeenCalledWith(200) + }) + + it('should handle errors gracefully', async () => { + const errorMessage = 'Failed to get ethernet port settings' + getEthernetPortSettingsSpy.mockRejectedValue(new Error(errorMessage)) + + await getEthernetPortSettings(req as any, resSpy) + + expect(mqttSpy).toHaveBeenCalledWith('fail', ['AMT_EthernetPortSettings'], messages.INTERNAL_SERVICE_ERROR) + expect(resSpy.status).toHaveBeenCalledWith(500) + expect(resSpy.json).toHaveBeenCalledWith(ErrorResponse(500, 'Exception during Get Ethernet Port Settings')) + }) +}) diff --git a/src/routes/amt/linkPreference.test.ts b/src/routes/amt/linkPreference.test.ts new file mode 100644 index 000000000..289a69c3f --- /dev/null +++ b/src/routes/amt/linkPreference.test.ts @@ -0,0 +1,140 @@ +/********************************************************************* + * 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: Express.Request + 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') + setEthernetLinkPreferenceSpy = spyOn(device, 'setEthernetLinkPreference').mockResolvedValue({} 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, undefined) + 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' + }) + }) + + 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, undefined) + 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' + }) + }) + + it('should set link preference with custom instanceID', async () => { + req.body.linkPreference = 1 + req.body.timeout = 120 + req.query.instanceID = 'Intel(r) AMT Ethernet Port Settings 1' + + await setLinkPreference(req as any, resSpy) + + expect(setEthernetLinkPreferenceSpy).toHaveBeenCalledWith(1, 120, 'Intel(r) AMT Ethernet Port Settings 1') + expect(resSpy.json).toHaveBeenCalledWith({ + status: 'Link Preference set to ME', + linkPreference: 1, + timeout: 120, + instanceID: 'Intel(r) AMT Ethernet Port Settings 1' + }) + }) + + it('should set link preference to ME with timeout 0', async () => { + req.body.linkPreference = 1 + req.body.timeout = 0 + + await setLinkPreference(req as any, resSpy) + + expect(setEthernetLinkPreferenceSpy).toHaveBeenCalledWith(1, 0, undefined) + expect(resSpy.json).toHaveBeenCalledWith({ + status: 'Link Preference set to ME', + linkPreference: 1, + timeout: 0, + instanceID: 'Intel(r) AMT Ethernet Port Settings 0' + }) + }) + + it('should set link preference to HOST with timeout 0', async () => { + req.body.linkPreference = 2 + req.body.timeout = 0 + + await setLinkPreference(req as any, resSpy) + + expect(setEthernetLinkPreferenceSpy).toHaveBeenCalledWith(2, 0, undefined) + expect(resSpy.json).toHaveBeenCalledWith({ + status: 'Link Preference set to HOST', + linkPreference: 2, + timeout: 0, + instanceID: 'Intel(r) AMT Ethernet Port Settings 0' + }) + }) + + 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')) + }) +}) From bf23279fb2cbb3b2ff36a42447b00bd56982ff30 Mon Sep 17 00:00:00 2001 From: "Cheah, Kit Hwa" Date: Fri, 14 Nov 2025 14:32:24 +0800 Subject: [PATCH 10/23] fix: fix unit test issues --- .../amt/enumerateEthernetPortSettings.ts | 8 ++++- .../amt/getEthernetPortSettings.test.ts | 34 +++++++++---------- src/routes/amt/linkPreference.test.ts | 2 +- src/utils/tlsConfiguration.test.ts | 4 +++ 4 files changed, 28 insertions(+), 20 deletions(-) diff --git a/src/routes/amt/enumerateEthernetPortSettings.ts b/src/routes/amt/enumerateEthernetPortSettings.ts index bf957df24..06a00f4b2 100644 --- a/src/routes/amt/enumerateEthernetPortSettings.ts +++ b/src/routes/amt/enumerateEthernetPortSettings.ts @@ -16,8 +16,14 @@ export async function enumerateEthernetPortSettings(req: Request, res: Response) logger.debug(`Enumerate Ethernet Port Settings for ${guid}`) const result = await deviceAction.enumerateEthernetPortSettings() + + // Extract the AMT_EthernetPortSettings from the response + const settings = (result?.Body?.PullResponse?.Items as any)?.AMT_EthernetPortSettings + // Ensure settings is always an array + const settingsArray = Array.isArray(settings) ? settings : (settings ? [settings] : []) + MqttProvider.publishEvent('success', ['AMT_EthernetPortSettings'], 'Ethernet Port Settings enumerated') - res.status(200).json(result).end() + res.status(200).json(settingsArray).end() } catch (error) { logger.error(`Exception during Enumerate Ethernet Port Settings: ${error}`) MqttProvider.publishEvent('fail', ['AMT_EthernetPortSettings'], messages.INTERNAL_SERVICE_ERROR) diff --git a/src/routes/amt/getEthernetPortSettings.test.ts b/src/routes/amt/getEthernetPortSettings.test.ts index dc3f8f436..5742a1a4b 100644 --- a/src/routes/amt/getEthernetPortSettings.test.ts +++ b/src/routes/amt/getEthernetPortSettings.test.ts @@ -14,7 +14,7 @@ import { MqttProvider } from '../../utils/MqttProvider.js' import { getEthernetPortSettings } from './getEthernetPortSettings.js' describe('Get Ethernet Port Settings', () => { - let req: Express.Request + let req let resSpy let mqttSpy: SpyInstance let getEthernetPortSettingsSpy: SpyInstance @@ -26,22 +26,20 @@ describe('Get Ethernet Port Settings', () => { device = new DeviceAction(handler, null) mockEthernetPortSettings = { - Body: { - AMT_EthernetPortSettings: { - InstanceID: 'Intel(r) AMT Ethernet Port Settings 0', - ElementName: 'Intel(r) AMT Ethernet Port Settings', - MACAddress: 'a4-ae-11-1c-02-4d', - LinkIsUp: true, - LinkPolicy: [1, 14, 16], - LinkPreference: 2, - LinkControl: 2, - SharedMAC: true, - SharedStaticIp: false, - SharedDynamicIP: true, - IpSyncEnabled: true, - DHCPEnabled: true, - PhysicalConnectionType: 0 - } + AMT_EthernetPortSettings: { + InstanceID: 'Intel(r) AMT Ethernet Port Settings 0', + ElementName: 'Intel(r) AMT Ethernet Port Settings', + MACAddress: 'a4-ae-11-1c-02-4d', + LinkIsUp: true, + LinkPolicy: [1, 14, 16], + LinkPreference: 2, + LinkControl: 2, + SharedMAC: true, + SharedStaticIp: false, + SharedDynamicIP: true, + IpSyncEnabled: true, + DHCPEnabled: true, + PhysicalConnectionType: 0 } } @@ -71,7 +69,7 @@ describe('Get Ethernet Port Settings', () => { expect(getEthernetPortSettingsSpy).toHaveBeenCalledWith(undefined) expect(mqttSpy).toHaveBeenCalledWith('success', ['AMT_EthernetPortSettings'], 'Ethernet Port Settings retrieved') expect(resSpy.status).toHaveBeenCalledWith(200) - expect(resSpy.json).toHaveBeenCalledWith(mockEthernetPortSettings.Body.AMT_EthernetPortSettings) + expect(resSpy.json).toHaveBeenCalledWith(mockEthernetPortSettings) }) it('should get ethernet port settings with custom instanceID', async () => { diff --git a/src/routes/amt/linkPreference.test.ts b/src/routes/amt/linkPreference.test.ts index 289a69c3f..1901fc0ab 100644 --- a/src/routes/amt/linkPreference.test.ts +++ b/src/routes/amt/linkPreference.test.ts @@ -14,7 +14,7 @@ import { MqttProvider } from '../../utils/MqttProvider.js' import { setLinkPreference } from './linkPreference.js' describe('Link Preference', () => { - let req: Express.Request + let req let resSpy let mqttSpy: SpyInstance let setEthernetLinkPreferenceSpy: SpyInstance 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 From 55b30d9fce0198ae6e96b01e76916ff583067c40 Mon Sep 17 00:00:00 2001 From: "Cheah, Kit Hwa" Date: Tue, 18 Nov 2025 17:23:35 +0800 Subject: [PATCH 11/23] fix: validate instanceID as WiFi port before proceeding Signed-off-by: Cheah, Kit Hwa --- src/amt/DeviceAction.ts | 65 +++++++++++ src/amt/deviceAction.test.ts | 149 ++++++++++++++++++++++++++ src/routes/amt/linkPreference.test.ts | 31 ++++++ src/routes/amt/linkPreference.ts | 21 +++- 4 files changed, 265 insertions(+), 1 deletion(-) diff --git a/src/amt/DeviceAction.ts b/src/amt/DeviceAction.ts index 5d7237d9d..a8f134752 100644 --- a/src/amt/DeviceAction.ts +++ b/src/amt/DeviceAction.ts @@ -688,12 +688,77 @@ export class DeviceAction { return pullResponse.Envelope } + /** + * Validates if a given instance is a WiFi port by checking PhysicalConnectionType + * @returns Object with isWiFi boolean and connectionType number, or null if not found + */ + async validateWiFiPort(instanceID: string): Promise<{ isWiFi: boolean; connectionType: number; instanceID: string } | null> { + logger.silly(`validateWiFiPort for ${instanceID}`) + try { + // Enumerate all ethernet port settings + const enumResult = await this.enumerateEthernetPortSettings() + if (enumResult?.Body?.PullResponse?.Items == null) { + logger.error('validateWiFiPort: No ethernet port settings found') + return null + } + + // Extract AMT_EthernetPortSettings from Items + const settings: any = (enumResult.Body.PullResponse.Items as any).AMT_EthernetPortSettings + if (settings == null) { + logger.error('validateWiFiPort: AMT_EthernetPortSettings not found in response') + return null + } + + // Ensure ports is always an array + const ports = Array.isArray(settings) ? settings : [settings] + + // Find the port matching the instanceID + const targetPort = ports.find((port: any) => port.InstanceID === instanceID) + if (targetPort == null) { + logger.error(`validateWiFiPort: InstanceID ${instanceID} not found`) + return null + } + + const connectionType = parseInt(targetPort.PhysicalConnectionType, 10) + // PhysicalConnectionType: 0=Integrated LAN, 1=Discrete LAN, 2=Thunderbolt, 3=Wireless LAN + const isWiFi = connectionType === 2 || connectionType === 3 + logger.silly(`validateWiFiPort: ${instanceID} connectionType=${connectionType} isWiFi=${isWiFi}`) + return { isWiFi, connectionType, instanceID } + } catch (err) { + logger.error(`validateWiFiPort error: ${(err as Error).message}`) + return null + } + } + async setEthernetLinkPreference( linkPreference: AMT.Types.EthernetPortSettings.LinkPreference, timeoutSeconds: number, instanceID: string = 'Intel(r) AMT Ethernet Port Settings 0' ): Promise> { logger.silly(`setEthernetLinkPreference ${messages.REQUEST}`) + + // Validate that the target instance is a WiFi port + const validation = await this.validateWiFiPort(instanceID) + if (validation == null) { + logger.error('setEthernetLinkPreference: Failed to validate port') + return null + } + + if (!validation.isWiFi) { + const errorMsg = `SetLinkPreference is only applicable for WiFi ports. InstanceID "${instanceID}" has PhysicalConnectionType=${validation.connectionType} (0=Integrated LAN, 1=Discrete LAN). WiFi ports have type 2 (Thunderbolt) or 3 (Wireless LAN).` + logger.error(`setEthernetLinkPreference: ${errorMsg}`) + // Return an error envelope structure that handlers can recognize + return { + Header: {}, + Body: { + Fault: { + Code: { Value: 'ValidationError' }, + Reason: { Text: errorMsg } + } + } + } as any + } + const xmlRequestBody = this.amt.EthernetPortSettings.SetLinkPreference(linkPreference, timeoutSeconds, instanceID) const result = await this.ciraHandler.Get(this.ciraSocket, xmlRequestBody) logger.silly(`setEthernetLinkPreference ${messages.COMPLETE}`) diff --git a/src/amt/deviceAction.test.ts b/src/amt/deviceAction.test.ts index 78ae1b5be..4210bc0c1 100644 --- a/src/amt/deviceAction.test.ts +++ b/src/amt/deviceAction.test.ts @@ -627,4 +627,153 @@ describe('Device Action Tests', () => { expect(result).toEqual(putKVMRedirectionSettingDataResponse.Envelope.Body) }) }) + + describe('WiFi port validation and link preference', () => { + let enumerateEthernetPortSettingsSpy: SpyInstance + + beforeEach(() => { + enumerateEthernetPortSettingsSpy = spyOn(device, 'enumerateEthernetPortSettings') + }) + + it('should validate WiFi port (PhysicalConnectionType=3)', 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' + } + ] + } + } + } + } + enumerateEthernetPortSettingsSpy.mockResolvedValue(mockEnumResponse as any) + + const result = await device.validateWiFiPort('Intel(r) AMT Ethernet Port Settings 1') + + expect(result).not.toBeNull() + expect(result?.isWiFi).toBe(true) + expect(result?.connectionType).toBe(3) + expect(result?.instanceID).toBe('Intel(r) AMT Ethernet Port Settings 1') + }) + + it('should identify non-WiFi port (PhysicalConnectionType=0)', async () => { + const mockEnumResponse = { + Body: { + PullResponse: { + Items: { + AMT_EthernetPortSettings: { + InstanceID: 'Intel(r) AMT Ethernet Port Settings 0', + PhysicalConnectionType: '0', // Integrated LAN + ElementName: 'LAN Port' + } + } + } + } + } + enumerateEthernetPortSettingsSpy.mockResolvedValue(mockEnumResponse as any) + + const result = await device.validateWiFiPort('Intel(r) AMT Ethernet Port Settings 0') + + expect(result).not.toBeNull() + expect(result?.isWiFi).toBe(false) + expect(result?.connectionType).toBe(0) + }) + + it('should identify Thunderbolt as WiFi (PhysicalConnectionType=2)', async () => { + const mockEnumResponse = { + Body: { + PullResponse: { + Items: { + AMT_EthernetPortSettings: { + InstanceID: 'Intel(r) AMT Ethernet Port Settings 2', + PhysicalConnectionType: '2', // Thunderbolt + ElementName: 'Thunderbolt' + } + } + } + } + } + enumerateEthernetPortSettingsSpy.mockResolvedValue(mockEnumResponse as any) + + const result = await device.validateWiFiPort('Intel(r) AMT Ethernet Port Settings 2') + + expect(result?.isWiFi).toBe(true) + expect(result?.connectionType).toBe(2) + }) + + it('should return null when instanceID not found', async () => { + const mockEnumResponse = { + Body: { + PullResponse: { + Items: { + AMT_EthernetPortSettings: { + InstanceID: 'Intel(r) AMT Ethernet Port Settings 0', + PhysicalConnectionType: '0' + } + } + } + } + } + enumerateEthernetPortSettingsSpy.mockResolvedValue(mockEnumResponse as any) + + const result = await device.validateWiFiPort('NonExistent Instance') + + expect(result).toBeNull() + }) + + it('should reject setLinkPreference on non-WiFi port', async () => { + const mockEnumResponse = { + Body: { + PullResponse: { + Items: { + AMT_EthernetPortSettings: { + InstanceID: 'Intel(r) AMT Ethernet Port Settings 0', + PhysicalConnectionType: '0' // Integrated LAN + } + } + } + } + } + enumerateEthernetPortSettingsSpy.mockResolvedValue(mockEnumResponse as any) + + const result = await device.setEthernetLinkPreference(1, 300, 'Intel(r) AMT Ethernet Port Settings 0') + + expect(result?.Body?.Fault).toBeDefined() + expect(result?.Body?.Fault?.Code?.Value).toBe('ValidationError') + expect(result?.Body?.Fault?.Reason?.Text).toContain('only applicable for WiFi ports') + expect(result?.Body?.Fault?.Reason?.Text).toContain('PhysicalConnectionType=0') + }) + + it('should allow setLinkPreference on WiFi port', async () => { + const mockEnumResponse = { + Body: { + PullResponse: { + Items: { + AMT_EthernetPortSettings: { + InstanceID: 'Intel(r) AMT Ethernet Port Settings 1', + PhysicalConnectionType: '3' // Wireless LAN + } + } + } + } + } + enumerateEthernetPortSettingsSpy.mockResolvedValue(mockEnumResponse as any) + getSpy.mockResolvedValue({ Envelope: { Body: { SetLinkPreference_OUTPUT: { ReturnValue: '0' } } } }) + + const result = await device.setEthernetLinkPreference(1, 300, 'Intel(r) AMT Ethernet Port Settings 1') + + expect(result?.Body?.Fault).toBeUndefined() + expect(getSpy).toHaveBeenCalled() + }) + }) }) diff --git a/src/routes/amt/linkPreference.test.ts b/src/routes/amt/linkPreference.test.ts index 1901fc0ab..328c5d97a 100644 --- a/src/routes/amt/linkPreference.test.ts +++ b/src/routes/amt/linkPreference.test.ts @@ -137,4 +137,35 @@ describe('Link Preference', () => { 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'], 'Port validation failed') + expect(resSpy.status).toHaveBeenCalledWith(500) + expect(resSpy.json).toHaveBeenCalledWith(ErrorResponse(500, 'Failed to validate ethernet port')) + }) }) diff --git a/src/routes/amt/linkPreference.ts b/src/routes/amt/linkPreference.ts index 06bf5df6d..f4398480d 100644 --- a/src/routes/amt/linkPreference.ts +++ b/src/routes/amt/linkPreference.ts @@ -19,7 +19,26 @@ export async function setLinkPreference(req: Request, res: Response): Promise Date: Tue, 18 Nov 2025 17:38:37 +0800 Subject: [PATCH 12/23] fix: Wireless LAN is PhysicalConnectionType 3 Signed-off-by: Cheah, Kit Hwa --- src/amt/DeviceAction.ts | 5 +++-- src/amt/deviceAction.test.ts | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/amt/DeviceAction.ts b/src/amt/DeviceAction.ts index a8f134752..4d24d12cc 100644 --- a/src/amt/DeviceAction.ts +++ b/src/amt/DeviceAction.ts @@ -721,7 +721,8 @@ export class DeviceAction { const connectionType = parseInt(targetPort.PhysicalConnectionType, 10) // PhysicalConnectionType: 0=Integrated LAN, 1=Discrete LAN, 2=Thunderbolt, 3=Wireless LAN - const isWiFi = connectionType === 2 || connectionType === 3 + // PhysicalConnectionType 3 (Wireless LAN) is WiFi + const isWiFi = connectionType === 3 logger.silly(`validateWiFiPort: ${instanceID} connectionType=${connectionType} isWiFi=${isWiFi}`) return { isWiFi, connectionType, instanceID } } catch (err) { @@ -745,7 +746,7 @@ export class DeviceAction { } if (!validation.isWiFi) { - const errorMsg = `SetLinkPreference is only applicable for WiFi ports. InstanceID "${instanceID}" has PhysicalConnectionType=${validation.connectionType} (0=Integrated LAN, 1=Discrete LAN). WiFi ports have type 2 (Thunderbolt) or 3 (Wireless LAN).` + const errorMsg = `SetLinkPreference is only applicable for WiFi ports. InstanceID "${instanceID}" has PhysicalConnectionType=${validation.connectionType} (0=Integrated LAN, 1=Discrete LAN, 2=Thunderbolt). WiFi ports have type 3 (Wireless LAN).` logger.error(`setEthernetLinkPreference: ${errorMsg}`) // Return an error envelope structure that handlers can recognize return { diff --git a/src/amt/deviceAction.test.ts b/src/amt/deviceAction.test.ts index 4210bc0c1..c2ac89098 100644 --- a/src/amt/deviceAction.test.ts +++ b/src/amt/deviceAction.test.ts @@ -689,7 +689,7 @@ describe('Device Action Tests', () => { expect(result?.connectionType).toBe(0) }) - it('should identify Thunderbolt as WiFi (PhysicalConnectionType=2)', async () => { + it('should identify Thunderbolt as non-WiFi (PhysicalConnectionType=2)', async () => { const mockEnumResponse = { Body: { PullResponse: { @@ -707,7 +707,7 @@ describe('Device Action Tests', () => { const result = await device.validateWiFiPort('Intel(r) AMT Ethernet Port Settings 2') - expect(result?.isWiFi).toBe(true) + expect(result?.isWiFi).toBe(false) expect(result?.connectionType).toBe(2) }) From 310bf12faf441363dac1dcb8951de7b97a3ffe57 Mon Sep 17 00:00:00 2001 From: "Cheah, Kit Hwa" Date: Wed, 19 Nov 2025 09:46:01 +0800 Subject: [PATCH 13/23] feat: auto retrieve instanceID for WiFi port Signed-off-by: Cheah, Kit Hwa --- src/amt/DeviceAction.ts | 73 +++++++++++++++++- src/amt/deviceAction.test.ts | 105 ++++++++++++++++++++++++++ src/routes/amt/linkPreference.test.ts | 40 ++++++++++ src/routes/amt/linkPreference.ts | 12 ++- 4 files changed, 222 insertions(+), 8 deletions(-) diff --git a/src/amt/DeviceAction.ts b/src/amt/DeviceAction.ts index 4d24d12cc..43dc1f4fa 100644 --- a/src/amt/DeviceAction.ts +++ b/src/amt/DeviceAction.ts @@ -688,6 +688,46 @@ export class DeviceAction { return pullResponse.Envelope } + /** + * Finds the first WiFi port by checking PhysicalConnectionType + * @returns InstanceID of the first WiFi port found, or null if none found + */ + async findWiFiPort(): Promise { + logger.silly('findWiFiPort: searching for WiFi port') + try { + const enumResult = await this.enumerateEthernetPortSettings() + 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 wifiPort.InstanceID + } catch (err) { + logger.error(`findWiFiPort error: ${(err as Error).message}`) + return null + } + } + /** * Validates if a given instance is a WiFi port by checking PhysicalConnectionType * @returns Object with isWiFi boolean and connectionType number, or null if not found @@ -734,19 +774,40 @@ export class DeviceAction { async setEthernetLinkPreference( linkPreference: AMT.Types.EthernetPortSettings.LinkPreference, timeoutSeconds: number, - instanceID: string = 'Intel(r) AMT Ethernet Port Settings 0' + instanceID?: string ): Promise> { logger.silly(`setEthernetLinkPreference ${messages.REQUEST}`) + // If no instanceID provided, auto-detect the WiFi port + let targetInstanceID = instanceID + if (targetInstanceID == null || targetInstanceID.trim() === '') { + logger.silly('setEthernetLinkPreference: No instanceID provided, auto-detecting WiFi port') + targetInstanceID = await this.findWiFiPort() + if (targetInstanceID == 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: ${targetInstanceID}`) + } + // Validate that the target instance is a WiFi port - const validation = await this.validateWiFiPort(instanceID) + const validation = await this.validateWiFiPort(targetInstanceID) if (validation == null) { logger.error('setEthernetLinkPreference: Failed to validate port') return null } if (!validation.isWiFi) { - const errorMsg = `SetLinkPreference is only applicable for WiFi ports. InstanceID "${instanceID}" has PhysicalConnectionType=${validation.connectionType} (0=Integrated LAN, 1=Discrete LAN, 2=Thunderbolt). WiFi ports have type 3 (Wireless LAN).` + const errorMsg = `SetLinkPreference is only applicable for WiFi ports. InstanceID "${targetInstanceID}" has PhysicalConnectionType=${validation.connectionType} (0=Integrated LAN, 1=Discrete LAN, 2=Thunderbolt). WiFi ports have type 3 (Wireless LAN).` logger.error(`setEthernetLinkPreference: ${errorMsg}`) // Return an error envelope structure that handlers can recognize return { @@ -760,9 +821,13 @@ export class DeviceAction { } as any } - const xmlRequestBody = this.amt.EthernetPortSettings.SetLinkPreference(linkPreference, timeoutSeconds, instanceID) + const xmlRequestBody = this.amt.EthernetPortSettings.SetLinkPreference(linkPreference, timeoutSeconds, targetInstanceID) const result = await this.ciraHandler.Get(this.ciraSocket, xmlRequestBody) logger.silly(`setEthernetLinkPreference ${messages.COMPLETE}`) + // Add the detected instanceID to the result for the handler to use + if (result?.Envelope != null) { + (result.Envelope as any)._detectedInstanceID = targetInstanceID + } return result.Envelope } diff --git a/src/amt/deviceAction.test.ts b/src/amt/deviceAction.test.ts index c2ac89098..61246a2f3 100644 --- a/src/amt/deviceAction.test.ts +++ b/src/amt/deviceAction.test.ts @@ -635,6 +635,111 @@ describe('Device Action Tests', () => { enumerateEthernetPortSettingsSpy = spyOn(device, 'enumerateEthernetPortSettings') }) + 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' + } + ] + } + } + } + } + enumerateEthernetPortSettingsSpy.mockResolvedValue(mockEnumResponse as any) + + const result = await device.findWiFiPort() + + expect(result).toBe('Intel(r) AMT Ethernet Port Settings 1') + }) + + 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 + } + ] + } + } + } + } + enumerateEthernetPortSettingsSpy.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 + } + ] + } + } + } + } + enumerateEthernetPortSettingsSpy.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 + } + } + } + } + } + enumerateEthernetPortSettingsSpy.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 validate WiFi port (PhysicalConnectionType=3)', async () => { const mockEnumResponse = { Body: { diff --git a/src/routes/amt/linkPreference.test.ts b/src/routes/amt/linkPreference.test.ts index 328c5d97a..8bce49758 100644 --- a/src/routes/amt/linkPreference.test.ts +++ b/src/routes/amt/linkPreference.test.ts @@ -168,4 +168,44 @@ describe('Link Preference', () => { expect(resSpy.status).toHaveBeenCalledWith(500) expect(resSpy.json).toHaveBeenCalledWith(ErrorResponse(500, 'Failed to validate ethernet port')) }) + + 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' + } + setEthernetLinkPreferenceSpy.mockResolvedValue(mockResponse) + + await setLinkPreference(req as any, resSpy) + + expect(setEthernetLinkPreferenceSpy).toHaveBeenCalledWith(1, 300, undefined) + 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' + }) + }) + + 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 index f4398480d..7245d65d0 100644 --- a/src/routes/amt/linkPreference.ts +++ b/src/routes/amt/linkPreference.ts @@ -18,11 +18,12 @@ export async function setLinkPreference(req: Request, res: Response): Promise Date: Wed, 19 Nov 2025 10:35:04 +0800 Subject: [PATCH 14/23] fix: unit tests updates Signed-off-by: Cheah, Kit Hwa --- src/routes/amt/linkPreference.test.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/routes/amt/linkPreference.test.ts b/src/routes/amt/linkPreference.test.ts index 8bce49758..c3d653295 100644 --- a/src/routes/amt/linkPreference.test.ts +++ b/src/routes/amt/linkPreference.test.ts @@ -44,7 +44,10 @@ describe('Link Preference', () => { resSpy.json.mockReturnThis() resSpy.send.mockReturnThis() mqttSpy = spyOn(MqttProvider, 'publishEvent') - setEthernetLinkPreferenceSpy = spyOn(device, 'setEthernetLinkPreference').mockResolvedValue({} as any) + // Mock with _detectedInstanceID to simulate auto-detection fallback + setEthernetLinkPreferenceSpy = spyOn(device, 'setEthernetLinkPreference').mockResolvedValue({ + _detectedInstanceID: 'Intel(r) AMT Ethernet Port Settings 0' + } as any) }) it('should set link preference to ME (1) with timeout', async () => { @@ -85,6 +88,11 @@ describe('Link Preference', () => { req.body.linkPreference = 1 req.body.timeout = 120 req.query.instanceID = 'Intel(r) AMT Ethernet Port Settings 1' + + // Mock response with the custom instanceID + setEthernetLinkPreferenceSpy.mockResolvedValue({ + _detectedInstanceID: 'Intel(r) AMT Ethernet Port Settings 1' + } as any) await setLinkPreference(req as any, resSpy) From 19ee5554797534fd51b924a06eea2b6ef7dce038 Mon Sep 17 00:00:00 2001 From: "Cheah, Kit Hwa" Date: Wed, 19 Nov 2025 10:51:40 +0800 Subject: [PATCH 15/23] docs: update swagger yaml file Signed-off-by: Cheah, Kit Hwa --- swagger.yaml | 69 ++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 56 insertions(+), 13 deletions(-) diff --git a/swagger.yaml b/swagger.yaml index 25c65326a..8034d89bc 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -741,8 +741,12 @@ paths: description: 'Internal server error' /api/v1/amt/network/ethernetPortSettings/{guid}: get: - summary: Get Ethernet Port Settings - description: Retrieve Ethernet Port Settings including link preference, link policy, and other network configurations. + summary: Get Specific Ethernet Port Settings + description: | + Retrieve detailed Ethernet Port Settings for a specific port by InstanceID. + + Includes link preference, link policy, MAC address, and other network configurations. + Use `/enumerate/{guid}` first to discover available port InstanceIDs. tags: - AMT parameters: @@ -755,11 +759,11 @@ paths: type: string - name: instanceID in: query - description: InstanceID of the Ethernet port (e.g., "Intel(r) AMT Ethernet Port Settings 0") - required: false + description: InstanceID of the Ethernet port (e.g., "Intel(r) AMT Ethernet Port Settings 0" for wired, "Intel(r) AMT Ethernet Port Settings 1" for WiFi) + required: true schema: type: string - default: Intel(r) AMT Ethernet Port Settings 0 + example: Intel(r) AMT Ethernet Port Settings 1 responses: 200: description: 'Ethernet Port Settings retrieved successfully' @@ -807,8 +811,17 @@ paths: description: 'Internal server error' /api/v1/amt/network/ethernetPortSettings/enumerate/{guid}: get: - summary: Enumerate all Ethernet Port Settings - description: Retrieve all Ethernet Port Settings instances (all network ports). + summary: Enumerate All Ethernet Port Settings + description: | + Retrieve all Ethernet Port Settings instances for all network ports (wired and wireless). + + **PhysicalConnectionType values**: + - 0 = Integrated LAN + - 1 = Discrete LAN + - 2 = Thunderbolt + - 3 = Wireless LAN (WiFi) + + Use this endpoint to discover available ports and their types before setting link preferences. tags: - AMT parameters: @@ -855,6 +868,16 @@ paths: MACAddress: type: string example: "00:11:22:33:44:55" + PhysicalConnectionType: + type: integer + description: 0=Integrated LAN, 1=Discrete LAN, 2=Thunderbolt, 3=Wireless LAN + example: 3 + LinkIsUp: + type: boolean + example: true + SharedMAC: + type: boolean + example: true 404: description: 'Device not found' content: @@ -865,8 +888,16 @@ paths: description: 'Internal server error' /api/v1/amt/network/linkPreference/{guid}: post: - summary: Set link preference with timeout - description: Set Ethernet Link Preference to ME (1) for a specified duration, or to HOST (2). + 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: @@ -879,11 +910,14 @@ paths: type: string - name: instanceID in: query - description: InstanceID of the Ethernet port (e.g., "Intel(r) AMT Ethernet Port Settings 0") + 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 - default: Intel(r) AMT Ethernet Port Settings 0 + example: Intel(r) AMT Ethernet Port Settings 1 requestBody: description: Link preference and timeout settings required: true @@ -923,9 +957,18 @@ paths: example: 300 instanceID: type: string - example: Intel(r) AMT Ethernet Port Settings 0 + description: The WiFi port InstanceID that was used (auto-detected or specified) + example: Intel(r) AMT Ethernet Port Settings 1 400: - description: 'Invalid parameters' + 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: From c4b1e724ec59131007765945f86b7a5eb5e34c2b Mon Sep 17 00:00:00 2001 From: "Cheah, Kit Hwa" Date: Wed, 19 Nov 2025 13:33:50 +0800 Subject: [PATCH 16/23] test: add API test for new network APIs Signed-off-by: Cheah, Kit Hwa --- .../collections/MPS.postman_collection.json | 240 ++++++++++++++++++ 1 file changed, 240 insertions(+) diff --git a/src/test/collections/MPS.postman_collection.json b/src/test/collections/MPS.postman_collection.json index 97234435d..7cf24a208 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", "" ], @@ -2958,6 +2959,245 @@ } }, "response": [] + }, + { + "name": "Enumerate Ethernet Port Settings", + "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": "GET", + "header": [], + "url": { + "raw": "{{protocol}}://{{host}}/api/v1/amt/network/ethernetPortSettings/enumerate/test-guid-123", + "protocol": "{{protocol}}", + "host": [ + "{{host}}" + ], + "path": [ + "api", + "v1", + "amt", + "network", + "ethernetPortSettings", + "enumerate", + "test-guid-123" + ] + } + }, + "response": [] + }, + { + "name": "Get Ethernet Port Settings", + "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": "GET", + "header": [], + "url": { + "raw": "{{protocol}}://{{host}}/api/v1/amt/network/ethernetPortSettings/test-guid-123?instanceID=Intel(r) AMT Ethernet Port Settings 0", + "protocol": "{{protocol}}", + "host": [ + "{{host}}" + ], + "path": [ + "api", + "v1", + "amt", + "network", + "ethernetPortSettings", + "test-guid-123" + ], + "query": [ + { + "key": "instanceID", + "value": "Intel(r) AMT Ethernet Port Settings 0" + } + ] + } + }, + "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}}/api/v1/amt/network/linkPreference/test-guid-123", + "protocol": "{{protocol}}", + "host": [ + "{{host}}" + ], + "path": [ + "api", + "v1", + "amt", + "network", + "linkPreference", + "test-guid-123" + ] + } + }, + "response": [] + }, + { + "name": "Set Link Preference (Specific InstanceID)", + "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\": 2,\n \"timeout\": 0\n}" + }, + "url": { + "raw": "{{protocol}}://{{host}}/api/v1/amt/network/linkPreference/test-guid-123?instanceID=Intel(r) AMT Ethernet Port Settings 1", + "protocol": "{{protocol}}", + "host": [ + "{{host}}" + ], + "path": [ + "api", + "v1", + "amt", + "network", + "linkPreference", + "test-guid-123" + ], + "query": [ + { + "key": "instanceID", + "value": "Intel(r) AMT Ethernet Port Settings 1" + } + ] + } + }, + "response": [] } ], "auth": { From 9f26d7f072214f999ca693c2f73a494152465c10 Mon Sep 17 00:00:00 2001 From: "Cheah, Kit Hwa" Date: Wed, 19 Nov 2025 14:22:16 +0800 Subject: [PATCH 17/23] cfg: update env details to run API test on dev machine Signed-off-by: Cheah, Kit Hwa --- .../collections/MPS.postman_collection.json | 18 ++++++++++++------ .../collections/MPS.postman_environment.json | 4 ++-- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/test/collections/MPS.postman_collection.json b/src/test/collections/MPS.postman_collection.json index 7cf24a208..7c94aec7a 100644 --- a/src/test/collections/MPS.postman_collection.json +++ b/src/test/collections/MPS.postman_collection.json @@ -2695,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" @@ -2703,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" @@ -2995,12 +2997,13 @@ "method": "GET", "header": [], "url": { - "raw": "{{protocol}}://{{host}}/api/v1/amt/network/ethernetPortSettings/enumerate/test-guid-123", + "raw": "{{protocol}}://{{host}}/mps/api/v1/amt/network/ethernetPortSettings/enumerate/test-guid-123", "protocol": "{{protocol}}", "host": [ "{{host}}" ], "path": [ + "mps", "api", "v1", "amt", @@ -3048,12 +3051,13 @@ "method": "GET", "header": [], "url": { - "raw": "{{protocol}}://{{host}}/api/v1/amt/network/ethernetPortSettings/test-guid-123?instanceID=Intel(r) AMT Ethernet Port Settings 0", + "raw": "{{protocol}}://{{host}}/mps/api/v1/amt/network/ethernetPortSettings/test-guid-123?instanceID=Intel(r) AMT Ethernet Port Settings 0", "protocol": "{{protocol}}", "host": [ "{{host}}" ], "path": [ + "mps", "api", "v1", "amt", @@ -3115,12 +3119,13 @@ "raw": "{\n \"linkPreference\": 1,\n \"timeout\": 300\n}" }, "url": { - "raw": "{{protocol}}://{{host}}/api/v1/amt/network/linkPreference/test-guid-123", + "raw": "{{protocol}}://{{host}}/mps/api/v1/amt/network/linkPreference/test-guid-123", "protocol": "{{protocol}}", "host": [ "{{host}}" ], "path": [ + "mps", "api", "v1", "amt", @@ -3176,12 +3181,13 @@ "raw": "{\n \"linkPreference\": 2,\n \"timeout\": 0\n}" }, "url": { - "raw": "{{protocol}}://{{host}}/api/v1/amt/network/linkPreference/test-guid-123?instanceID=Intel(r) AMT Ethernet Port Settings 1", + "raw": "{{protocol}}://{{host}}/mps/api/v1/amt/network/linkPreference/test-guid-123?instanceID=Intel(r) AMT Ethernet Port Settings 1", "protocol": "{{protocol}}", "host": [ "{{host}}" ], "path": [ + "mps", "api", "v1", "amt", 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 }, { From 4effd14e7d22a0ea700b21185ef32e684e7106cb Mon Sep 17 00:00:00 2001 From: "Cheah, Kit Hwa" Date: Wed, 19 Nov 2025 23:47:43 +0800 Subject: [PATCH 18/23] fix: rm getEthernetPortSettings and enumerateEthernetPortSettings APIs Signed-off-by: Cheah, Kit Hwa --- .../amt/enumerateEthernetPortSettings.test.ts | 118 ------------------ .../amt/enumerateEthernetPortSettings.ts | 32 ----- .../amt/getEthernetPortSettings.test.ts | 94 -------------- src/routes/amt/getEthernetPortSettings.ts | 27 ---- src/routes/amt/index.ts | 6 - .../collections/MPS.postman_collection.json | 113 ----------------- 6 files changed, 390 deletions(-) delete mode 100644 src/routes/amt/enumerateEthernetPortSettings.test.ts delete mode 100644 src/routes/amt/enumerateEthernetPortSettings.ts delete mode 100644 src/routes/amt/getEthernetPortSettings.test.ts delete mode 100644 src/routes/amt/getEthernetPortSettings.ts diff --git a/src/routes/amt/enumerateEthernetPortSettings.test.ts b/src/routes/amt/enumerateEthernetPortSettings.test.ts deleted file mode 100644 index fc2d909f6..000000000 --- a/src/routes/amt/enumerateEthernetPortSettings.test.ts +++ /dev/null @@ -1,118 +0,0 @@ -/********************************************************************* - * 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 { enumerateEthernetPortSettings } from './enumerateEthernetPortSettings.js' - -describe('Enumerate Ethernet Port Settings', () => { - let req: Express.Request - let resSpy - let mqttSpy: SpyInstance - let enumerateEthernetPortSettingsSpy: SpyInstance - let device: DeviceAction - let mockEnumerateResponse - - beforeEach(() => { - const handler = new CIRAHandler(new HttpHandler(), 'admin', 'P@ssw0rd') - device = new DeviceAction(handler, null) - - mockEnumerateResponse = { - Body: { - PullResponse: { - Items: { - AMT_EthernetPortSettings: [ - { - InstanceID: 'Intel(r) AMT Ethernet Port Settings 0', - ElementName: 'Intel(r) AMT Ethernet Port Settings', - MACAddress: 'a4-ae-11-1c-02-4d', - LinkIsUp: true, - LinkPolicy: [1, 14, 16], - LinkPreference: 2, - LinkControl: 2 - }, - { - InstanceID: 'Intel(r) AMT Ethernet Port Settings 1', - ElementName: 'Intel(r) AMT Ethernet Port Settings', - MACAddress: 'a4-ae-11-1c-02-4e', - LinkIsUp: false, - LinkPolicy: [1, 14], - LinkPreference: 1, - LinkControl: 1 - } - ] - } - } - } - } - - req = { - params: { - guid: '123456' - }, - deviceAction: device - } - resSpy = createSpyObj('Response', [ - 'status', - 'json', - 'end', - 'send' - ]) - resSpy.status.mockReturnThis() - resSpy.json.mockReturnThis() - resSpy.send.mockReturnThis() - mqttSpy = spyOn(MqttProvider, 'publishEvent') - enumerateEthernetPortSettingsSpy = spyOn(device, 'enumerateEthernetPortSettings').mockResolvedValue(mockEnumerateResponse as any) - }) - - it('should enumerate all ethernet port settings', async () => { - await enumerateEthernetPortSettings(req as any, resSpy) - - expect(enumerateEthernetPortSettingsSpy).toHaveBeenCalled() - expect(mqttSpy).toHaveBeenCalledWith('success', ['AMT_EthernetPortSettings'], 'Ethernet Port Settings enumerated') - expect(resSpy.status).toHaveBeenCalledWith(200) - expect(resSpy.json).toHaveBeenCalledWith(mockEnumerateResponse.Body.PullResponse.Items.AMT_EthernetPortSettings) - }) - - it('should handle single port in enumerate response', async () => { - const singlePortResponse = { - Body: { - PullResponse: { - Items: { - AMT_EthernetPortSettings: { - InstanceID: 'Intel(r) AMT Ethernet Port Settings 0', - ElementName: 'Intel(r) AMT Ethernet Port Settings', - MACAddress: 'a4-ae-11-1c-02-4d' - } - } - } - } - } - enumerateEthernetPortSettingsSpy.mockResolvedValue(singlePortResponse as any) - - await enumerateEthernetPortSettings(req as any, resSpy) - - expect(resSpy.status).toHaveBeenCalledWith(200) - // Should wrap single object in array - expect(resSpy.json).toHaveBeenCalled() - }) - - it('should handle errors gracefully', async () => { - const errorMessage = 'Failed to enumerate ethernet port settings' - enumerateEthernetPortSettingsSpy.mockRejectedValue(new Error(errorMessage)) - - await enumerateEthernetPortSettings(req as any, resSpy) - - expect(mqttSpy).toHaveBeenCalledWith('fail', ['AMT_EthernetPortSettings'], messages.INTERNAL_SERVICE_ERROR) - expect(resSpy.status).toHaveBeenCalledWith(500) - expect(resSpy.json).toHaveBeenCalledWith(ErrorResponse(500, 'Exception during Enumerate Ethernet Port Settings')) - }) -}) diff --git a/src/routes/amt/enumerateEthernetPortSettings.ts b/src/routes/amt/enumerateEthernetPortSettings.ts deleted file mode 100644 index 06a00f4b2..000000000 --- a/src/routes/amt/enumerateEthernetPortSettings.ts +++ /dev/null @@ -1,32 +0,0 @@ -/********************************************************************* - * 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 enumerateEthernetPortSettings(req: Request, res: Response): Promise { - try { - const guid: string = req.params.guid - const deviceAction: DeviceAction = req.deviceAction as DeviceAction - - logger.debug(`Enumerate Ethernet Port Settings for ${guid}`) - const result = await deviceAction.enumerateEthernetPortSettings() - - // Extract the AMT_EthernetPortSettings from the response - const settings = (result?.Body?.PullResponse?.Items as any)?.AMT_EthernetPortSettings - // Ensure settings is always an array - const settingsArray = Array.isArray(settings) ? settings : (settings ? [settings] : []) - - MqttProvider.publishEvent('success', ['AMT_EthernetPortSettings'], 'Ethernet Port Settings enumerated') - res.status(200).json(settingsArray).end() - } catch (error) { - logger.error(`Exception during Enumerate Ethernet Port Settings: ${error}`) - MqttProvider.publishEvent('fail', ['AMT_EthernetPortSettings'], messages.INTERNAL_SERVICE_ERROR) - res.status(500).json(ErrorResponse(500, 'Exception during Enumerate Ethernet Port Settings')).end() - } -} diff --git a/src/routes/amt/getEthernetPortSettings.test.ts b/src/routes/amt/getEthernetPortSettings.test.ts deleted file mode 100644 index 5742a1a4b..000000000 --- a/src/routes/amt/getEthernetPortSettings.test.ts +++ /dev/null @@ -1,94 +0,0 @@ -/********************************************************************* - * 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 { getEthernetPortSettings } from './getEthernetPortSettings.js' - -describe('Get Ethernet Port Settings', () => { - let req - let resSpy - let mqttSpy: SpyInstance - let getEthernetPortSettingsSpy: SpyInstance - let device: DeviceAction - let mockEthernetPortSettings - - beforeEach(() => { - const handler = new CIRAHandler(new HttpHandler(), 'admin', 'P@ssw0rd') - device = new DeviceAction(handler, null) - - mockEthernetPortSettings = { - AMT_EthernetPortSettings: { - InstanceID: 'Intel(r) AMT Ethernet Port Settings 0', - ElementName: 'Intel(r) AMT Ethernet Port Settings', - MACAddress: 'a4-ae-11-1c-02-4d', - LinkIsUp: true, - LinkPolicy: [1, 14, 16], - LinkPreference: 2, - LinkControl: 2, - SharedMAC: true, - SharedStaticIp: false, - SharedDynamicIP: true, - IpSyncEnabled: true, - DHCPEnabled: true, - PhysicalConnectionType: 0 - } - } - - req = { - params: { - guid: '123456' - }, - query: {}, - deviceAction: device - } - resSpy = createSpyObj('Response', [ - 'status', - 'json', - 'end', - 'send' - ]) - resSpy.status.mockReturnThis() - resSpy.json.mockReturnThis() - resSpy.send.mockReturnThis() - mqttSpy = spyOn(MqttProvider, 'publishEvent') - getEthernetPortSettingsSpy = spyOn(device, 'getEthernetPortSettings').mockResolvedValue(mockEthernetPortSettings as any) - }) - - it('should get ethernet port settings with default instanceID', async () => { - await getEthernetPortSettings(req as any, resSpy) - - expect(getEthernetPortSettingsSpy).toHaveBeenCalledWith(undefined) - expect(mqttSpy).toHaveBeenCalledWith('success', ['AMT_EthernetPortSettings'], 'Ethernet Port Settings retrieved') - expect(resSpy.status).toHaveBeenCalledWith(200) - expect(resSpy.json).toHaveBeenCalledWith(mockEthernetPortSettings) - }) - - it('should get ethernet port settings with custom instanceID', async () => { - req.query.instanceID = 'Intel(r) AMT Ethernet Port Settings 1' - - await getEthernetPortSettings(req as any, resSpy) - - expect(getEthernetPortSettingsSpy).toHaveBeenCalledWith('Intel(r) AMT Ethernet Port Settings 1') - expect(resSpy.status).toHaveBeenCalledWith(200) - }) - - it('should handle errors gracefully', async () => { - const errorMessage = 'Failed to get ethernet port settings' - getEthernetPortSettingsSpy.mockRejectedValue(new Error(errorMessage)) - - await getEthernetPortSettings(req as any, resSpy) - - expect(mqttSpy).toHaveBeenCalledWith('fail', ['AMT_EthernetPortSettings'], messages.INTERNAL_SERVICE_ERROR) - expect(resSpy.status).toHaveBeenCalledWith(500) - expect(resSpy.json).toHaveBeenCalledWith(ErrorResponse(500, 'Exception during Get Ethernet Port Settings')) - }) -}) diff --git a/src/routes/amt/getEthernetPortSettings.ts b/src/routes/amt/getEthernetPortSettings.ts deleted file mode 100644 index 25c2fa8ac..000000000 --- a/src/routes/amt/getEthernetPortSettings.ts +++ /dev/null @@ -1,27 +0,0 @@ -/********************************************************************* - * 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 getEthernetPortSettings(req: Request, res: Response): Promise { - try { - const guid: string = req.params.guid - const instanceID: string | undefined = req.query.instanceID as string - const deviceAction: DeviceAction = req.deviceAction as DeviceAction - - logger.debug(`Get Ethernet Port Settings for ${guid}, instanceID: ${instanceID ?? 'default'}`) - const result = await deviceAction.getEthernetPortSettings(instanceID) - MqttProvider.publishEvent('success', ['AMT_EthernetPortSettings'], 'Ethernet Port Settings retrieved') - res.status(200).json(result).end() - } catch (error) { - logger.error(`Exception during Get Ethernet Port Settings: ${error}`) - MqttProvider.publishEvent('fail', ['AMT_EthernetPortSettings'], messages.INTERNAL_SERVICE_ERROR) - res.status(500).json(ErrorResponse(500, 'Exception during Get Ethernet Port Settings')).end() - } -} diff --git a/src/routes/amt/index.ts b/src/routes/amt/index.ts index f80cc46c7..7768ac3ac 100644 --- a/src/routes/amt/index.ts +++ b/src/routes/amt/index.ts @@ -40,8 +40,6 @@ import { getScreenSettingData } from './kvm/get.js' import { setKVMRedirectionSettingData } from './kvm/set.js' import { setLinkPreference } from './linkPreference.js' import { linkPreferenceValidator } from './linkPreferenceValidator.js' -import { getEthernetPortSettings } from './getEthernetPortSettings.js' -import { enumerateEthernetPortSettings } from './enumerateEthernetPortSettings.js' const amtRouter: Router = Router() @@ -72,10 +70,6 @@ amtRouter.post('/certificates/:guid', certValidator(), validateMiddleware, ciraM amtRouter.get('/kvm/displays/:guid', ciraMiddleware, getScreenSettingData) amtRouter.put('/kvm/displays/:guid', validator(), ciraMiddleware, setKVMRedirectionSettingData) -// Ethernet Port Settings -amtRouter.get('/network/ethernetPortSettings/:guid', ciraMiddleware, getEthernetPortSettings) -amtRouter.get('/network/ethernetPortSettings/enumerate/:guid', ciraMiddleware, enumerateEthernetPortSettings) - // Link Preference (ME/HOST) amtRouter.post('/network/linkPreference/:guid', linkPreferenceValidator(), validateMiddleware, ciraMiddleware, setLinkPreference) diff --git a/src/test/collections/MPS.postman_collection.json b/src/test/collections/MPS.postman_collection.json index 7c94aec7a..5a90e06e0 100644 --- a/src/test/collections/MPS.postman_collection.json +++ b/src/test/collections/MPS.postman_collection.json @@ -2962,119 +2962,6 @@ }, "response": [] }, - { - "name": "Enumerate Ethernet Port Settings", - "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": "GET", - "header": [], - "url": { - "raw": "{{protocol}}://{{host}}/mps/api/v1/amt/network/ethernetPortSettings/enumerate/test-guid-123", - "protocol": "{{protocol}}", - "host": [ - "{{host}}" - ], - "path": [ - "mps", - "api", - "v1", - "amt", - "network", - "ethernetPortSettings", - "enumerate", - "test-guid-123" - ] - } - }, - "response": [] - }, - { - "name": "Get Ethernet Port Settings", - "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": "GET", - "header": [], - "url": { - "raw": "{{protocol}}://{{host}}/mps/api/v1/amt/network/ethernetPortSettings/test-guid-123?instanceID=Intel(r) AMT Ethernet Port Settings 0", - "protocol": "{{protocol}}", - "host": [ - "{{host}}" - ], - "path": [ - "mps", - "api", - "v1", - "amt", - "network", - "ethernetPortSettings", - "test-guid-123" - ], - "query": [ - { - "key": "instanceID", - "value": "Intel(r) AMT Ethernet Port Settings 0" - } - ] - } - }, - "response": [] - }, { "name": "Set Link Preference (Auto-detect WiFi)", "event": [ From e4966e03c81067bc8a332798de9e2d1eda7b2989 Mon Sep 17 00:00:00 2001 From: "Cheah, Kit Hwa" Date: Thu, 20 Nov 2025 00:33:57 +0800 Subject: [PATCH 19/23] fix: rm validate and other dangling functions Signed-off-by: Cheah, Kit Hwa --- src/amt/DeviceAction.ts | 115 +++-------------------- src/amt/deviceAction.test.ts | 130 ++------------------------ src/routes/amt/linkPreference.test.ts | 35 ++----- src/routes/amt/linkPreference.ts | 24 +++-- 4 files changed, 41 insertions(+), 263 deletions(-) diff --git a/src/amt/DeviceAction.ts b/src/amt/DeviceAction.ts index 43dc1f4fa..174667b78 100644 --- a/src/amt/DeviceAction.ts +++ b/src/amt/DeviceAction.ts @@ -661,30 +661,19 @@ export class DeviceAction { return result.Envelope.Body } - async getEthernetPortSettings( - instanceID: string = 'Intel(r) AMT Ethernet Port Settings 0' - ): Promise { - logger.silly(`getEthernetPortSettings ${messages.REQUEST}`) - const selector = { name: 'InstanceID', value: instanceID } - const xmlRequestBody = this.amt.EthernetPortSettings.Get(selector) - const result = await this.ciraHandler.Get(this.ciraSocket, xmlRequestBody) - logger.silly(`getEthernetPortSettings ${messages.COMPLETE}`) - return result.Envelope.Body - } - - async enumerateEthernetPortSettings(): Promise< + async getEthernetPortSettings(): Promise< Common.Models.Envelope> > { - logger.silly(`enumerateEthernetPortSettings ${messages.REQUEST}`) + 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(`enumerateEthernetPortSettings failed. Reason: ${messages.ENUMERATION_RESPONSE_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(`enumerateEthernetPortSettings ${messages.COMPLETE}`) + logger.silly(`getEthernetPortSettings ${messages.COMPLETE}`) return pullResponse.Envelope } @@ -695,7 +684,7 @@ export class DeviceAction { async findWiFiPort(): Promise { logger.silly('findWiFiPort: searching for WiFi port') try { - const enumResult = await this.enumerateEthernetPortSettings() + const enumResult = await this.getEthernetPortSettings() if (enumResult?.Body?.PullResponse?.Items == null) { logger.error('findWiFiPort: No ethernet port settings found') return null @@ -728,98 +717,29 @@ export class DeviceAction { } } - /** - * Validates if a given instance is a WiFi port by checking PhysicalConnectionType - * @returns Object with isWiFi boolean and connectionType number, or null if not found - */ - async validateWiFiPort(instanceID: string): Promise<{ isWiFi: boolean; connectionType: number; instanceID: string } | null> { - logger.silly(`validateWiFiPort for ${instanceID}`) - try { - // Enumerate all ethernet port settings - const enumResult = await this.enumerateEthernetPortSettings() - if (enumResult?.Body?.PullResponse?.Items == null) { - logger.error('validateWiFiPort: No ethernet port settings found') - return null - } - - // Extract AMT_EthernetPortSettings from Items - const settings: any = (enumResult.Body.PullResponse.Items as any).AMT_EthernetPortSettings - if (settings == null) { - logger.error('validateWiFiPort: AMT_EthernetPortSettings not found in response') - return null - } - - // Ensure ports is always an array - const ports = Array.isArray(settings) ? settings : [settings] - - // Find the port matching the instanceID - const targetPort = ports.find((port: any) => port.InstanceID === instanceID) - if (targetPort == null) { - logger.error(`validateWiFiPort: InstanceID ${instanceID} not found`) - return null - } - - const connectionType = parseInt(targetPort.PhysicalConnectionType, 10) - // PhysicalConnectionType: 0=Integrated LAN, 1=Discrete LAN, 2=Thunderbolt, 3=Wireless LAN - // PhysicalConnectionType 3 (Wireless LAN) is WiFi - const isWiFi = connectionType === 3 - logger.silly(`validateWiFiPort: ${instanceID} connectionType=${connectionType} isWiFi=${isWiFi}`) - return { isWiFi, connectionType, instanceID } - } catch (err) { - logger.error(`validateWiFiPort error: ${(err as Error).message}`) - return null - } - } - async setEthernetLinkPreference( linkPreference: AMT.Types.EthernetPortSettings.LinkPreference, - timeoutSeconds: number, - instanceID?: string + timeoutSeconds: number ): Promise> { logger.silly(`setEthernetLinkPreference ${messages.REQUEST}`) - // If no instanceID provided, auto-detect the WiFi port - let targetInstanceID = instanceID - if (targetInstanceID == null || targetInstanceID.trim() === '') { - logger.silly('setEthernetLinkPreference: No instanceID provided, auto-detecting WiFi port') - targetInstanceID = await this.findWiFiPort() - if (targetInstanceID == 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: ${targetInstanceID}`) - } - - // Validate that the target instance is a WiFi port - const validation = await this.validateWiFiPort(targetInstanceID) - if (validation == null) { - logger.error('setEthernetLinkPreference: Failed to validate port') - return null - } - - if (!validation.isWiFi) { - const errorMsg = `SetLinkPreference is only applicable for WiFi ports. InstanceID "${targetInstanceID}" has PhysicalConnectionType=${validation.connectionType} (0=Integrated LAN, 1=Discrete LAN, 2=Thunderbolt). WiFi ports have type 3 (Wireless LAN).` + // Auto-detect the WiFi port + logger.silly('setEthernetLinkPreference: Auto-detecting WiFi port') + const targetInstanceID = await this.findWiFiPort() + if (targetInstanceID == null) { + const errorMsg = 'No WiFi port found on this device. SetLinkPreference requires a WiFi interface (PhysicalConnectionType=3).' logger.error(`setEthernetLinkPreference: ${errorMsg}`) - // Return an error envelope structure that handlers can recognize return { Header: {}, Body: { Fault: { - Code: { Value: 'ValidationError' }, + Code: { Value: 'NoWiFiPort' }, Reason: { Text: errorMsg } } } } as any } + logger.info(`setEthernetLinkPreference: Auto-detected WiFi port: ${targetInstanceID}`) const xmlRequestBody = this.amt.EthernetPortSettings.SetLinkPreference(linkPreference, timeoutSeconds, targetInstanceID) const result = await this.ciraHandler.Get(this.ciraSocket, xmlRequestBody) @@ -830,13 +750,4 @@ export class DeviceAction { } return result.Envelope } - - async setLinkPreferenceME(timeoutSeconds: number, instanceID?: string): Promise> { - return await this.setEthernetLinkPreference(1, timeoutSeconds, instanceID) - } - - async cancelLinkPreference(instanceID?: string): Promise> { - // Set preference back to HOST with timeout 0 - return await this.setEthernetLinkPreference(2, 0, instanceID) - } } diff --git a/src/amt/deviceAction.test.ts b/src/amt/deviceAction.test.ts index 61246a2f3..1c85443c5 100644 --- a/src/amt/deviceAction.test.ts +++ b/src/amt/deviceAction.test.ts @@ -629,10 +629,10 @@ describe('Device Action Tests', () => { }) describe('WiFi port validation and link preference', () => { - let enumerateEthernetPortSettingsSpy: SpyInstance + let getEthernetPortSettingsSpy: SpyInstance beforeEach(() => { - enumerateEthernetPortSettingsSpy = spyOn(device, 'enumerateEthernetPortSettings') + getEthernetPortSettingsSpy = spyOn(device, 'getEthernetPortSettings') }) it('should find WiFi port automatically', async () => { @@ -656,7 +656,7 @@ describe('Device Action Tests', () => { } } } - enumerateEthernetPortSettingsSpy.mockResolvedValue(mockEnumResponse as any) + getEthernetPortSettingsSpy.mockResolvedValue(mockEnumResponse as any) const result = await device.findWiFiPort() @@ -682,7 +682,7 @@ describe('Device Action Tests', () => { } } } - enumerateEthernetPortSettingsSpy.mockResolvedValue(mockEnumResponse as any) + getEthernetPortSettingsSpy.mockResolvedValue(mockEnumResponse as any) const result = await device.findWiFiPort() @@ -708,7 +708,7 @@ describe('Device Action Tests', () => { } } } - enumerateEthernetPortSettingsSpy.mockResolvedValue(mockEnumResponse as any) + getEthernetPortSettingsSpy.mockResolvedValue(mockEnumResponse as any) getSpy.mockResolvedValue({ Envelope: { Body: { SetLinkPreference_OUTPUT: { ReturnValue: '0' } } } }) const result = await device.setEthernetLinkPreference(1, 300) @@ -731,7 +731,7 @@ describe('Device Action Tests', () => { } } } - enumerateEthernetPortSettingsSpy.mockResolvedValue(mockEnumResponse as any) + getEthernetPortSettingsSpy.mockResolvedValue(mockEnumResponse as any) const result = await device.setEthernetLinkPreference(1, 300) @@ -740,7 +740,7 @@ describe('Device Action Tests', () => { expect(result?.Body?.Fault?.Reason?.Text).toContain('No WiFi port found') }) - it('should validate WiFi port (PhysicalConnectionType=3)', async () => { + it('should auto-detect WiFi port and call SetLinkPreference', async () => { const mockEnumResponse = { Body: { PullResponse: { @@ -761,124 +761,14 @@ describe('Device Action Tests', () => { } } } - enumerateEthernetPortSettingsSpy.mockResolvedValue(mockEnumResponse as any) - - const result = await device.validateWiFiPort('Intel(r) AMT Ethernet Port Settings 1') - - expect(result).not.toBeNull() - expect(result?.isWiFi).toBe(true) - expect(result?.connectionType).toBe(3) - expect(result?.instanceID).toBe('Intel(r) AMT Ethernet Port Settings 1') - }) - - it('should identify non-WiFi port (PhysicalConnectionType=0)', async () => { - const mockEnumResponse = { - Body: { - PullResponse: { - Items: { - AMT_EthernetPortSettings: { - InstanceID: 'Intel(r) AMT Ethernet Port Settings 0', - PhysicalConnectionType: '0', // Integrated LAN - ElementName: 'LAN Port' - } - } - } - } - } - enumerateEthernetPortSettingsSpy.mockResolvedValue(mockEnumResponse as any) - - const result = await device.validateWiFiPort('Intel(r) AMT Ethernet Port Settings 0') - - expect(result).not.toBeNull() - expect(result?.isWiFi).toBe(false) - expect(result?.connectionType).toBe(0) - }) - - it('should identify Thunderbolt as non-WiFi (PhysicalConnectionType=2)', async () => { - const mockEnumResponse = { - Body: { - PullResponse: { - Items: { - AMT_EthernetPortSettings: { - InstanceID: 'Intel(r) AMT Ethernet Port Settings 2', - PhysicalConnectionType: '2', // Thunderbolt - ElementName: 'Thunderbolt' - } - } - } - } - } - enumerateEthernetPortSettingsSpy.mockResolvedValue(mockEnumResponse as any) - - const result = await device.validateWiFiPort('Intel(r) AMT Ethernet Port Settings 2') - - expect(result?.isWiFi).toBe(false) - expect(result?.connectionType).toBe(2) - }) - - it('should return null when instanceID not found', async () => { - const mockEnumResponse = { - Body: { - PullResponse: { - Items: { - AMT_EthernetPortSettings: { - InstanceID: 'Intel(r) AMT Ethernet Port Settings 0', - PhysicalConnectionType: '0' - } - } - } - } - } - enumerateEthernetPortSettingsSpy.mockResolvedValue(mockEnumResponse as any) - - const result = await device.validateWiFiPort('NonExistent Instance') - - expect(result).toBeNull() - }) - - it('should reject setLinkPreference on non-WiFi port', async () => { - const mockEnumResponse = { - Body: { - PullResponse: { - Items: { - AMT_EthernetPortSettings: { - InstanceID: 'Intel(r) AMT Ethernet Port Settings 0', - PhysicalConnectionType: '0' // Integrated LAN - } - } - } - } - } - enumerateEthernetPortSettingsSpy.mockResolvedValue(mockEnumResponse as any) - - const result = await device.setEthernetLinkPreference(1, 300, 'Intel(r) AMT Ethernet Port Settings 0') - - expect(result?.Body?.Fault).toBeDefined() - expect(result?.Body?.Fault?.Code?.Value).toBe('ValidationError') - expect(result?.Body?.Fault?.Reason?.Text).toContain('only applicable for WiFi ports') - expect(result?.Body?.Fault?.Reason?.Text).toContain('PhysicalConnectionType=0') - }) - - it('should allow setLinkPreference on WiFi port', async () => { - const mockEnumResponse = { - Body: { - PullResponse: { - Items: { - AMT_EthernetPortSettings: { - InstanceID: 'Intel(r) AMT Ethernet Port Settings 1', - PhysicalConnectionType: '3' // Wireless LAN - } - } - } - } - } - enumerateEthernetPortSettingsSpy.mockResolvedValue(mockEnumResponse as any) + getEthernetPortSettingsSpy.mockResolvedValue(mockEnumResponse as any) getSpy.mockResolvedValue({ Envelope: { Body: { SetLinkPreference_OUTPUT: { ReturnValue: '0' } } } }) - const result = await device.setEthernetLinkPreference(1, 300, 'Intel(r) AMT Ethernet Port Settings 1') + 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') }) }) }) diff --git a/src/routes/amt/linkPreference.test.ts b/src/routes/amt/linkPreference.test.ts index c3d653295..0d2d36d13 100644 --- a/src/routes/amt/linkPreference.test.ts +++ b/src/routes/amt/linkPreference.test.ts @@ -56,7 +56,7 @@ describe('Link Preference', () => { await setLinkPreference(req as any, resSpy) - expect(setEthernetLinkPreferenceSpy).toHaveBeenCalledWith(1, 300, undefined) + 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({ @@ -73,7 +73,7 @@ describe('Link Preference', () => { await setLinkPreference(req as any, resSpy) - expect(setEthernetLinkPreferenceSpy).toHaveBeenCalledWith(2, 600, undefined) + 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({ @@ -84,34 +84,13 @@ describe('Link Preference', () => { }) }) - it('should set link preference with custom instanceID', async () => { - req.body.linkPreference = 1 - req.body.timeout = 120 - req.query.instanceID = 'Intel(r) AMT Ethernet Port Settings 1' - - // Mock response with the custom instanceID - setEthernetLinkPreferenceSpy.mockResolvedValue({ - _detectedInstanceID: 'Intel(r) AMT Ethernet Port Settings 1' - } as any) - - await setLinkPreference(req as any, resSpy) - - expect(setEthernetLinkPreferenceSpy).toHaveBeenCalledWith(1, 120, 'Intel(r) AMT Ethernet Port Settings 1') - expect(resSpy.json).toHaveBeenCalledWith({ - status: 'Link Preference set to ME', - linkPreference: 1, - timeout: 120, - instanceID: 'Intel(r) AMT Ethernet Port Settings 1' - }) - }) - it('should set link preference to ME with timeout 0', async () => { req.body.linkPreference = 1 req.body.timeout = 0 await setLinkPreference(req as any, resSpy) - expect(setEthernetLinkPreferenceSpy).toHaveBeenCalledWith(1, 0, undefined) + expect(setEthernetLinkPreferenceSpy).toHaveBeenCalledWith(1, 0) expect(resSpy.json).toHaveBeenCalledWith({ status: 'Link Preference set to ME', linkPreference: 1, @@ -126,7 +105,7 @@ describe('Link Preference', () => { await setLinkPreference(req as any, resSpy) - expect(setEthernetLinkPreferenceSpy).toHaveBeenCalledWith(2, 0, undefined) + expect(setEthernetLinkPreferenceSpy).toHaveBeenCalledWith(2, 0) expect(resSpy.json).toHaveBeenCalledWith({ status: 'Link Preference set to HOST', linkPreference: 2, @@ -172,9 +151,9 @@ describe('Link Preference', () => { await setLinkPreference(req as any, resSpy) - expect(mqttSpy).toHaveBeenCalledWith('fail', ['AMT_LinkPreference'], 'Port validation failed') + expect(mqttSpy).toHaveBeenCalledWith('fail', ['AMT_LinkPreference'], 'Unexpected error') expect(resSpy.status).toHaveBeenCalledWith(500) - expect(resSpy.json).toHaveBeenCalledWith(ErrorResponse(500, 'Failed to validate ethernet port')) + expect(resSpy.json).toHaveBeenCalledWith(ErrorResponse(500, 'Failed to set link preference')) }) it('should auto-detect WiFi port when instanceID not provided', async () => { @@ -187,7 +166,7 @@ describe('Link Preference', () => { await setLinkPreference(req as any, resSpy) - expect(setEthernetLinkPreferenceSpy).toHaveBeenCalledWith(1, 300, undefined) + expect(setEthernetLinkPreferenceSpy).toHaveBeenCalledWith(1, 300) expect(resSpy.status).toHaveBeenCalledWith(200) expect(resSpy.json).toHaveBeenCalledWith({ status: 'Link Preference set to ME', diff --git a/src/routes/amt/linkPreference.ts b/src/routes/amt/linkPreference.ts index 7245d65d0..d15961fc0 100644 --- a/src/routes/amt/linkPreference.ts +++ b/src/routes/amt/linkPreference.ts @@ -14,34 +14,32 @@ export async function setLinkPreference(req: Request, res: Response): Promise Date: Thu, 20 Nov 2025 00:50:32 +0800 Subject: [PATCH 20/23] feat: print WiFiPort settings in setLinkPreference response Signed-off-by: Cheah, Kit Hwa --- src/amt/DeviceAction.ts | 22 ++++---- src/amt/deviceAction.test.ts | 9 +++- src/routes/amt/linkPreference.test.ts | 72 +++++++++++++-------------- src/routes/amt/linkPreference.ts | 6 ++- swagger.yaml | 22 +++++++- 5 files changed, 82 insertions(+), 49 deletions(-) diff --git a/src/amt/DeviceAction.ts b/src/amt/DeviceAction.ts index 174667b78..77abe55ab 100644 --- a/src/amt/DeviceAction.ts +++ b/src/amt/DeviceAction.ts @@ -679,9 +679,9 @@ export class DeviceAction { /** * Finds the first WiFi port by checking PhysicalConnectionType - * @returns InstanceID of the first WiFi port found, or null if none found + * @returns Object with instanceID and full port settings, or null if none found */ - async findWiFiPort(): Promise { + async findWiFiPort(): Promise<{ instanceID: string; settings: any } | null> { logger.silly('findWiFiPort: searching for WiFi port') try { const enumResult = await this.getEthernetPortSettings() @@ -710,7 +710,10 @@ export class DeviceAction { } logger.silly(`findWiFiPort: Found WiFi port ${wifiPort.InstanceID}`) - return wifiPort.InstanceID + return { + instanceID: wifiPort.InstanceID, + settings: wifiPort + } } catch (err) { logger.error(`findWiFiPort error: ${(err as Error).message}`) return null @@ -725,8 +728,8 @@ export class DeviceAction { // Auto-detect the WiFi port logger.silly('setEthernetLinkPreference: Auto-detecting WiFi port') - const targetInstanceID = await this.findWiFiPort() - if (targetInstanceID == null) { + 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 { @@ -739,14 +742,15 @@ export class DeviceAction { } } as any } - logger.info(`setEthernetLinkPreference: Auto-detected WiFi port: ${targetInstanceID}`) + logger.info(`setEthernetLinkPreference: Auto-detected WiFi port: ${wifiPortInfo.instanceID}`) - const xmlRequestBody = this.amt.EthernetPortSettings.SetLinkPreference(linkPreference, timeoutSeconds, targetInstanceID) + const xmlRequestBody = this.amt.EthernetPortSettings.SetLinkPreference(linkPreference, timeoutSeconds, wifiPortInfo.instanceID) const result = await this.ciraHandler.Get(this.ciraSocket, xmlRequestBody) logger.silly(`setEthernetLinkPreference ${messages.COMPLETE}`) - // Add the detected instanceID to the result for the handler to use + // Add the detected instanceID and WiFi port settings to the result for the handler to use if (result?.Envelope != null) { - (result.Envelope as any)._detectedInstanceID = targetInstanceID + (result.Envelope as any)._detectedInstanceID = wifiPortInfo.instanceID; + (result.Envelope as any)._wifiPortSettings = wifiPortInfo.settings } return result.Envelope } diff --git a/src/amt/deviceAction.test.ts b/src/amt/deviceAction.test.ts index 1c85443c5..34dcaaf62 100644 --- a/src/amt/deviceAction.test.ts +++ b/src/amt/deviceAction.test.ts @@ -660,7 +660,11 @@ describe('Device Action Tests', () => { const result = await device.findWiFiPort() - expect(result).toBe('Intel(r) AMT Ethernet Port Settings 1') + 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 () => { @@ -769,6 +773,9 @@ describe('Device Action Tests', () => { 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/linkPreference.test.ts b/src/routes/amt/linkPreference.test.ts index 0d2d36d13..a2a056fd7 100644 --- a/src/routes/amt/linkPreference.test.ts +++ b/src/routes/amt/linkPreference.test.ts @@ -44,9 +44,15 @@ describe('Link Preference', () => { resSpy.json.mockReturnThis() resSpy.send.mockReturnThis() mqttSpy = spyOn(MqttProvider, 'publishEvent') - // Mock with _detectedInstanceID to simulate auto-detection fallback + // Mock with _detectedInstanceID and _wifiPortSettings to simulate auto-detection setEthernetLinkPreferenceSpy = spyOn(device, 'setEthernetLinkPreference').mockResolvedValue({ - _detectedInstanceID: 'Intel(r) AMT Ethernet Port Settings 0' + _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) }) @@ -63,7 +69,13 @@ describe('Link Preference', () => { status: 'Link Preference set to ME', linkPreference: 1, timeout: 300, - instanceID: 'Intel(r) AMT Ethernet Port Settings 0' + 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' + } }) }) @@ -80,37 +92,13 @@ describe('Link Preference', () => { status: 'Link Preference set to HOST', linkPreference: 2, timeout: 600, - instanceID: 'Intel(r) AMT Ethernet Port Settings 0' - }) - }) - - it('should set link preference to ME with timeout 0', async () => { - req.body.linkPreference = 1 - req.body.timeout = 0 - - await setLinkPreference(req as any, resSpy) - - expect(setEthernetLinkPreferenceSpy).toHaveBeenCalledWith(1, 0) - expect(resSpy.json).toHaveBeenCalledWith({ - status: 'Link Preference set to ME', - linkPreference: 1, - timeout: 0, - instanceID: 'Intel(r) AMT Ethernet Port Settings 0' - }) - }) - - it('should set link preference to HOST with timeout 0', async () => { - req.body.linkPreference = 2 - req.body.timeout = 0 - - await setLinkPreference(req as any, resSpy) - - expect(setEthernetLinkPreferenceSpy).toHaveBeenCalledWith(2, 0) - expect(resSpy.json).toHaveBeenCalledWith({ - status: 'Link Preference set to HOST', - linkPreference: 2, - timeout: 0, - instanceID: 'Intel(r) AMT Ethernet Port Settings 0' + 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' + } }) }) @@ -160,7 +148,13 @@ describe('Link Preference', () => { req.query.instanceID = undefined const mockResponse = { Body: { SetLinkPreference_OUTPUT: { ReturnValue: '0' } }, - _detectedInstanceID: 'Intel(r) AMT Ethernet Port Settings 1' + _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) @@ -172,7 +166,13 @@ describe('Link Preference', () => { status: 'Link Preference set to ME', linkPreference: 1, timeout: 300, - instanceID: 'Intel(r) AMT Ethernet Port Settings 1' + 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' + } }) }) diff --git a/src/routes/amt/linkPreference.ts b/src/routes/amt/linkPreference.ts index d15961fc0..5b5ea3b65 100644 --- a/src/routes/amt/linkPreference.ts +++ b/src/routes/amt/linkPreference.ts @@ -38,15 +38,17 @@ export async function setLinkPreference(req: Request, res: Response): Promise Date: Thu, 20 Nov 2025 00:58:16 +0800 Subject: [PATCH 21/23] docs: rm ethernetPortSettings APIs from swagger yaml Signed-off-by: Cheah, Kit Hwa --- swagger.yaml | 147 --------------------------------------------------- 1 file changed, 147 deletions(-) diff --git a/swagger.yaml b/swagger.yaml index 748c86658..9e5aed040 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -739,153 +739,6 @@ paths: $ref: '#/components/schemas/DisconnectErrorResponse' 500: description: 'Internal server error' - /api/v1/amt/network/ethernetPortSettings/{guid}: - get: - summary: Get Specific Ethernet Port Settings - description: | - Retrieve detailed Ethernet Port Settings for a specific port by InstanceID. - - Includes link preference, link policy, MAC address, and other network configurations. - Use `/enumerate/{guid}` first to discover available port InstanceIDs. - 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 Ethernet port (e.g., "Intel(r) AMT Ethernet Port Settings 0" for wired, "Intel(r) AMT Ethernet Port Settings 1" for WiFi) - required: true - schema: - type: string - example: Intel(r) AMT Ethernet Port Settings 1 - responses: - 200: - description: 'Ethernet Port Settings retrieved successfully' - content: - application/json: - schema: - type: object - properties: - InstanceID: - type: string - example: Intel(r) AMT Ethernet Port Settings 0 - ElementName: - type: string - example: Intel(r) AMT Ethernet Port Settings - LinkPreference: - type: integer - description: 1 = ME, 2 = HOST - example: 2 - LinkControl: - type: integer - description: 1 = ME, 2 = HOST - example: 2 - LinkPolicy: - type: array - items: - type: integer - description: Array of link policy values - example: [1, 14, 16] - LinkProtection: - type: integer - example: 1 - SharedMAC: - type: boolean - example: true - MACAddress: - type: string - example: "00:11:22:33:44:55" - 404: - description: 'Device not found' - content: - application/json: - schema: - $ref: '#/components/schemas/DisconnectErrorResponse' - 500: - description: 'Internal server error' - /api/v1/amt/network/ethernetPortSettings/enumerate/{guid}: - get: - summary: Enumerate All Ethernet Port Settings - description: | - Retrieve all Ethernet Port Settings instances for all network ports (wired and wireless). - - **PhysicalConnectionType values**: - - 0 = Integrated LAN - - 1 = Discrete LAN - - 2 = Thunderbolt - - 3 = Wireless LAN (WiFi) - - Use this endpoint to discover available ports and their types before setting link preferences. - tags: - - AMT - parameters: - - name: guid - in: path - description: GUID of device - example: 123e4567-e89b-12d3-a456-426614174000 - required: true - schema: - type: string - responses: - 200: - description: 'All Ethernet Port Settings retrieved successfully' - content: - application/json: - schema: - type: object - properties: - Body: - type: object - properties: - PullResponse: - type: object - properties: - Items: - type: array - items: - type: object - properties: - InstanceID: - type: string - example: Intel(r) AMT Ethernet Port Settings 0 - ElementName: - type: string - example: Intel(r) AMT Ethernet Port Settings - LinkPreference: - type: integer - description: 1 = ME, 2 = HOST - example: 2 - LinkControl: - type: integer - description: 1 = ME, 2 = HOST - example: 2 - MACAddress: - type: string - example: "00:11:22:33:44:55" - PhysicalConnectionType: - type: integer - description: 0=Integrated LAN, 1=Discrete LAN, 2=Thunderbolt, 3=Wireless LAN - example: 3 - LinkIsUp: - type: boolean - example: true - SharedMAC: - type: boolean - example: true - 404: - description: 'Device not found' - content: - application/json: - schema: - $ref: '#/components/schemas/DisconnectErrorResponse' - 500: - description: 'Internal server error' /api/v1/amt/network/linkPreference/{guid}: post: summary: Set WiFi Link Preference From 6a19d1466476882361eb1bee302f22ffc17e3a6f Mon Sep 17 00:00:00 2001 From: "Cheah, Kit Hwa" Date: Thu, 20 Nov 2025 01:13:38 +0800 Subject: [PATCH 22/23] test: rm API test for setLinkPreference specific instanceID Signed-off-by: Cheah, Kit Hwa --- .../collections/MPS.postman_collection.json | 68 ------------------- 1 file changed, 68 deletions(-) diff --git a/src/test/collections/MPS.postman_collection.json b/src/test/collections/MPS.postman_collection.json index 5a90e06e0..7832cb0db 100644 --- a/src/test/collections/MPS.postman_collection.json +++ b/src/test/collections/MPS.postman_collection.json @@ -3023,74 +3023,6 @@ } }, "response": [] - }, - { - "name": "Set Link Preference (Specific InstanceID)", - "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\": 2,\n \"timeout\": 0\n}" - }, - "url": { - "raw": "{{protocol}}://{{host}}/mps/api/v1/amt/network/linkPreference/test-guid-123?instanceID=Intel(r) AMT Ethernet Port Settings 1", - "protocol": "{{protocol}}", - "host": [ - "{{host}}" - ], - "path": [ - "mps", - "api", - "v1", - "amt", - "network", - "linkPreference", - "test-guid-123" - ], - "query": [ - { - "key": "instanceID", - "value": "Intel(r) AMT Ethernet Port Settings 1" - } - ] - } - }, - "response": [] } ], "auth": { From 237d4c29b2f26d9c3819a43c9a076005baf60ebd Mon Sep 17 00:00:00 2001 From: "Cheah, Kit Hwa" Date: Thu, 20 Nov 2025 02:05:32 +0800 Subject: [PATCH 23/23] fix: read status after set Signed-off-by: Cheah, Kit Hwa --- src/amt/DeviceAction.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/amt/DeviceAction.ts b/src/amt/DeviceAction.ts index 77abe55ab..5ed89aa76 100644 --- a/src/amt/DeviceAction.ts +++ b/src/amt/DeviceAction.ts @@ -746,11 +746,16 @@ export class DeviceAction { 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 WiFi port settings to the result for the handler to use + // 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 = wifiPortInfo.settings + (result.Envelope as any)._wifiPortSettings = updatedWifiPortInfo?.settings ?? wifiPortInfo.settings } return result.Envelope }