From fb25a98fcc951b7ee55eb8f7ad62f0f99d23f81c Mon Sep 17 00:00:00 2001 From: Natalie Gaston Date: Tue, 24 Mar 2026 17:49:49 -0700 Subject: [PATCH] feat: Remote Platform Erase capability --- src/amt/DeviceAction.ts | 26 +++++++ src/amt/deviceAction.test.ts | 28 +++++++ src/routes/amt/amtFeatureValidator.ts | 3 +- src/routes/amt/getAMTFeatures.test.ts | 14 +++- src/routes/amt/getAMTFeatures.ts | 10 ++- src/routes/amt/getBootCapabilities.test.ts | 65 ++++++++++++++++ src/routes/amt/getBootCapabilities.ts | 27 +++++++ src/routes/amt/index.ts | 6 ++ src/routes/amt/sendRemoteErase.test.ts | 88 ++++++++++++++++++++++ src/routes/amt/sendRemoteErase.ts | 44 +++++++++++ src/routes/amt/setAMTFeatures.ts | 9 +++ src/routes/amt/setRPEEnabled.test.ts | 68 +++++++++++++++++ src/routes/amt/setRPEEnabled.ts | 39 ++++++++++ 13 files changed, 420 insertions(+), 7 deletions(-) create mode 100644 src/routes/amt/getBootCapabilities.test.ts create mode 100644 src/routes/amt/getBootCapabilities.ts create mode 100644 src/routes/amt/sendRemoteErase.test.ts create mode 100644 src/routes/amt/sendRemoteErase.ts create mode 100644 src/routes/amt/setRPEEnabled.test.ts create mode 100644 src/routes/amt/setRPEEnabled.ts diff --git a/src/amt/DeviceAction.ts b/src/amt/DeviceAction.ts index 718aeb42c..8b8338f08 100644 --- a/src/amt/DeviceAction.ts +++ b/src/amt/DeviceAction.ts @@ -199,6 +199,32 @@ export class DeviceAction { return result.Envelope } + async getBootCapabilities(): Promise> { + logger.silly(`getBootCapabilities ${messages.REQUEST}`) + const xmlRequestBody = this.amt.BootCapabilities.Get() + const result = await this.ciraHandler.Get(this.ciraSocket, xmlRequestBody) + logger.silly(`getBootCapabilities ${messages.COMPLETE}`) + return result.Envelope + } + + async setRPEEnabled(enabled: boolean): Promise { + logger.silly(`setRPEEnabled ${messages.REQUEST}`) + const bootOptions = await this.getBootOptions() + const current = bootOptions.AMT_BootSettingData + current.PlatformErase = enabled + await this.setBootConfiguration(current) + logger.silly(`setRPEEnabled ${messages.COMPLETE}`) + } + + async sendRemoteErase(eraseMask: number): Promise { + logger.silly(`sendRemoteErase ${messages.REQUEST}`) + const bootOptions = await this.getBootOptions() + const current = bootOptions.AMT_BootSettingData + current.PlatformErase = eraseMask !== 0 + await this.setBootConfiguration(current) + logger.silly(`sendRemoteErase ${messages.COMPLETE}`) + } + async requestUserConsentCode(): Promise> { logger.silly(`requestUserConsentCode ${messages.REQUEST}`) const xmlRequestBody = this.ips.OptInService.StartOptIn() diff --git a/src/amt/deviceAction.test.ts b/src/amt/deviceAction.test.ts index c1e544cfe..6d0482457 100644 --- a/src/amt/deviceAction.test.ts +++ b/src/amt/deviceAction.test.ts @@ -389,6 +389,34 @@ describe('Device Action Tests', () => { expect(result).toEqual(bootCapabilities.Envelope) }) }) + describe('boot capabilities and RPE', () => { + it('should get boot capabilities', async () => { + getSpy.mockResolvedValueOnce(bootCapabilities) + const result = await device.getBootCapabilities() + expect(result).toEqual(bootCapabilities.Envelope) + }) + it('should set RPE enabled', async () => { + getSpy.mockResolvedValueOnce({ Envelope: { Body: { AMT_BootSettingData: { ElementName: 'test', PlatformErase: false } } } }) + sendSpy.mockResolvedValueOnce({ Envelope: { Body: {} } }) + await device.setRPEEnabled(true) + expect(getSpy).toHaveBeenCalled() + expect(sendSpy).toHaveBeenCalled() + }) + it('should send remote erase with non-zero mask', async () => { + getSpy.mockResolvedValueOnce({ Envelope: { Body: { AMT_BootSettingData: { ElementName: 'test', PlatformErase: false } } } }) + sendSpy.mockResolvedValueOnce({ Envelope: { Body: {} } }) + await device.sendRemoteErase(3) + expect(getSpy).toHaveBeenCalled() + expect(sendSpy).toHaveBeenCalled() + }) + it('should send remote erase with zero mask sets PlatformErase to false', async () => { + getSpy.mockResolvedValueOnce({ Envelope: { Body: { AMT_BootSettingData: { ElementName: 'test', PlatformErase: true } } } }) + sendSpy.mockResolvedValueOnce({ Envelope: { Body: {} } }) + await device.sendRemoteErase(0) + expect(getSpy).toHaveBeenCalled() + expect(sendSpy).toHaveBeenCalled() + }) + }) describe('alarm occurrences', () => { it('should return null when enumerate call to getAlarmClockOccurrences fails', async () => { enumerateSpy.mockResolvedValueOnce(null) diff --git a/src/routes/amt/amtFeatureValidator.ts b/src/routes/amt/amtFeatureValidator.ts index 342edd8e9..d808290c4 100644 --- a/src/routes/amt/amtFeatureValidator.ts +++ b/src/routes/amt/amtFeatureValidator.ts @@ -16,5 +16,6 @@ export const amtFeaturesValidator = (): any => [ check('enableSOL').isBoolean().toBoolean(), check('enableIDER').isBoolean().toBoolean(), check('enableKVM').isBoolean().toBoolean(), - check('ocr').optional().isBoolean().toBoolean() + check('ocr').optional().isBoolean().toBoolean(), + check('enablePlatformErase').optional().isBoolean().toBoolean() ] diff --git a/src/routes/amt/getAMTFeatures.test.ts b/src/routes/amt/getAMTFeatures.test.ts index 0f0867544..b9658ca6e 100644 --- a/src/routes/amt/getAMTFeatures.test.ts +++ b/src/routes/amt/getAMTFeatures.test.ts @@ -141,7 +141,8 @@ describe('get amt features', () => { ForcedProgressEvents: true, IDER: true, InstanceID: 'Intel(r) AMT:BootCapabilities 0', - SOL: true + SOL: true, + PlatformErase: 3 } } }, @@ -156,7 +157,8 @@ describe('get amt features', () => { IDERBootDevice: 0, InstanceID: 'Intel(r) AMT:BootSettingData 0', UseIDER: false, - UseSOL: false + UseSOL: false, + PlatformErase: true } } }) @@ -175,7 +177,9 @@ describe('get amt features', () => { httpsBootSupported: true, winREBootSupported: true, localPBABootSupported: false, - remoteErase: false + remoteEraseEnabled: true, + remoteEraseSupported: true, + platformEraseCaps: 3 }) expect(mqttSpy).toHaveBeenCalledTimes(2) }) @@ -271,7 +275,9 @@ describe('get amt features', () => { httpsBootSupported: false, winREBootSupported: false, localPBABootSupported: false, - remoteErase: false + remoteEraseEnabled: false, + remoteEraseSupported: false, + platformEraseCaps: 0 }) }) }) diff --git a/src/routes/amt/getAMTFeatures.ts b/src/routes/amt/getAMTFeatures.ts index d7cfa37bc..abc1d8613 100644 --- a/src/routes/amt/getAMTFeatures.ts +++ b/src/routes/amt/getAMTFeatures.ts @@ -28,8 +28,12 @@ export async function getAMTFeatures(req: Request, res: Response): Promise const userConsent = Object.keys(UserConsentOptions).find((key) => UserConsentOptions[key] === value) const ocrProcessResult = processOCRData(OCRData) + const platformEraseCaps = OCRData.capabilities?.Body?.AMT_BootCapabilities?.PlatformErase ?? 0 + const remoteEraseEnabled = !!(OCRData.bootData?.AMT_BootSettingData?.PlatformErase) + const remoteEraseSupported = platformEraseCaps !== 0 + MqttProvider.publishEvent('success', ['AMT_GetFeatures'], messages.AMT_FEATURES_GET_SUCCESS, guid) - res + res .status(200) .json({ userConsent, @@ -43,7 +47,9 @@ export async function getAMTFeatures(req: Request, res: Response): Promise httpsBootSupported: ocrProcessResult.HTTPSBootSupported, winREBootSupported: ocrProcessResult.WinREBootSupported, localPBABootSupported: ocrProcessResult.LocalPBABootSupported, - remoteErase: false + remoteEraseEnabled, + remoteEraseSupported, + platformEraseCaps }) .end() } catch (error) { diff --git a/src/routes/amt/getBootCapabilities.test.ts b/src/routes/amt/getBootCapabilities.test.ts new file mode 100644 index 000000000..73d707316 --- /dev/null +++ b/src/routes/amt/getBootCapabilities.test.ts @@ -0,0 +1,65 @@ +/********************************************************************* + * Copyright (c) Intel Corporation 2022 + * SPDX-License-Identifier: Apache-2.0 + **********************************************************************/ + +import { ErrorResponse } from '../../utils/amtHelper.js' +import { MqttProvider } from '../../utils/MqttProvider.js' +import { getBootCapabilities } from './getBootCapabilities.js' +import { createSpyObj } from '../../test/helper/jest.js' +import { DeviceAction } from '../../amt/DeviceAction.js' +import { CIRAHandler } from '../../amt/CIRAHandler.js' +import { HttpHandler } from '../../amt/HttpHandler.js' +import { messages } from '../../logging/index.js' +import { type Spied, spyOn } from 'jest-mock' + +describe('Get Boot Capabilities', () => { + let req: any + let resSpy: any + let mqttSpy: Spied + let bootCapsSpy: Spied + let device: DeviceAction + + beforeEach(() => { + const handler = new CIRAHandler(new HttpHandler(), 'admin', 'P@ssw0rd') + device = new DeviceAction(handler, null) + req = { + params: { guid: '4c4c4544-004b-4210-8033-b6c04f504633' }, + deviceAction: device + } + resSpy = createSpyObj('Response', ['status', 'json', 'end', 'send']) + resSpy.status.mockReturnThis() + resSpy.json.mockReturnThis() + resSpy.send.mockReturnThis() + + mqttSpy = spyOn(MqttProvider, 'publishEvent') + bootCapsSpy = spyOn(device, 'getBootCapabilities') + }) + + it('should return boot capabilities', async () => { + const bootCaps = { + IDER: true, + SOL: true, + BIOSSetup: true, + PlatformErase: 3 + } + bootCapsSpy.mockResolvedValue({ + Body: { AMT_BootCapabilities: bootCaps } + }) + + await getBootCapabilities(req, resSpy) + expect(resSpy.status).toHaveBeenCalledWith(200) + expect(resSpy.json).toHaveBeenCalledWith(bootCaps) + expect(resSpy.end).toHaveBeenCalled() + expect(mqttSpy).toHaveBeenCalledTimes(2) + }) + + it('should return 500 on error', async () => { + bootCapsSpy.mockRejectedValue(new Error('AMT error')) + + await getBootCapabilities(req, resSpy) + expect(resSpy.status).toHaveBeenCalledWith(500) + expect(resSpy.json).toHaveBeenCalledWith(ErrorResponse(500, messages.POWER_CAPABILITIES_EXCEPTION)) + expect(resSpy.end).toHaveBeenCalled() + }) +}) diff --git a/src/routes/amt/getBootCapabilities.ts b/src/routes/amt/getBootCapabilities.ts new file mode 100644 index 000000000..945228883 --- /dev/null +++ b/src/routes/amt/getBootCapabilities.ts @@ -0,0 +1,27 @@ +/********************************************************************* + * Copyright (c) Intel Corporation 2022 + * 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' + +export async function getBootCapabilities(req: Request, res: Response): Promise { + try { + const guid: string = req.params.guid + + MqttProvider.publishEvent('request', ['AMT_BootCapabilities'], messages.POWER_CAPABILITIES_REQUESTED, guid) + + const result = await req.deviceAction.getBootCapabilities() + const capabilities = result.Body?.AMT_BootCapabilities + + MqttProvider.publishEvent('success', ['AMT_BootCapabilities'], messages.POWER_CAPABILITIES_SUCCESS, guid) + res.status(200).json(capabilities).end() + } catch (error) { + logger.error(`${messages.POWER_CAPABILITIES_EXCEPTION} : ${error}`) + MqttProvider.publishEvent('fail', ['AMT_BootCapabilities'], messages.INTERNAL_SERVICE_ERROR) + res.status(500).json(ErrorResponse(500, messages.POWER_CAPABILITIES_EXCEPTION)).end() + } +} diff --git a/src/routes/amt/index.ts b/src/routes/amt/index.ts index 9e361d0e6..79caed38b 100644 --- a/src/routes/amt/index.ts +++ b/src/routes/amt/index.ts @@ -40,6 +40,9 @@ import { getScreenSettingData } from './kvm/get.js' import { setKVMRedirectionSettingData } from './kvm/set.js' import { setLinkPreference } from './setLinkPreference.js' import { linkPreferenceValidator } from './linkPreferenceValidator.js' +import { getBootCapabilities } from './getBootCapabilities.js' +import { setRPEEnabled } from './setRPEEnabled.js' +import { sendRemoteErase } from './sendRemoteErase.js' const amtRouter: Router = Router() @@ -53,6 +56,9 @@ amtRouter.get('/power/capabilities/:guid', ciraMiddleware, powerCapabilities) amtRouter.get('/power/state/:guid', ciraMiddleware, powerState) amtRouter.get('/features/:guid', ciraMiddleware, getAMTFeatures) amtRouter.post('/features/:guid', amtFeaturesValidator(), validateMiddleware, ciraMiddleware, setAMTFeatures) +amtRouter.get('/boot/capabilities/:guid', ciraMiddleware, getBootCapabilities) +amtRouter.post('/boot/rpe/:guid', ciraMiddleware, setRPEEnabled) +amtRouter.post('/remoteErase/:guid', ciraMiddleware, sendRemoteErase) amtRouter.get('/version/:guid', ciraMiddleware, version) amtRouter.delete('/deactivate/:guid', ciraMiddleware, deactivate) amtRouter.get('/power/bootSources/:guid', ciraMiddleware, bootSources) diff --git a/src/routes/amt/sendRemoteErase.test.ts b/src/routes/amt/sendRemoteErase.test.ts new file mode 100644 index 000000000..ca08233dd --- /dev/null +++ b/src/routes/amt/sendRemoteErase.test.ts @@ -0,0 +1,88 @@ +/********************************************************************* + * Copyright (c) Intel Corporation 2022 + * SPDX-License-Identifier: Apache-2.0 + **********************************************************************/ + +import { ErrorResponse } from '../../utils/amtHelper.js' +import { MqttProvider } from '../../utils/MqttProvider.js' +import { sendRemoteErase } from './sendRemoteErase.js' +import { createSpyObj } from '../../test/helper/jest.js' +import { DeviceAction } from '../../amt/DeviceAction.js' +import { CIRAHandler } from '../../amt/CIRAHandler.js' +import { HttpHandler } from '../../amt/HttpHandler.js' +import { messages } from '../../logging/index.js' +import { type Spied, spyOn } from 'jest-mock' + +describe('Send Remote Erase', () => { + let req: any + let resSpy: any + let mqttSpy: Spied + let bootCapsSpy: Spied + let sendEraseSpy: Spied + let device: DeviceAction + + beforeEach(() => { + const handler = new CIRAHandler(new HttpHandler(), 'admin', 'P@ssw0rd') + device = new DeviceAction(handler, null) + req = { + params: { guid: '4c4c4544-004b-4210-8033-b6c04f504633' }, + body: { eraseMask: 3 }, + deviceAction: device + } + resSpy = createSpyObj('Response', ['status', 'json', 'end', 'send']) + resSpy.status.mockReturnThis() + resSpy.json.mockReturnThis() + resSpy.send.mockReturnThis() + + mqttSpy = spyOn(MqttProvider, 'publishEvent') + bootCapsSpy = spyOn(device, 'getBootCapabilities') + sendEraseSpy = spyOn(device, 'sendRemoteErase') + }) + + it('should send remote erase when device supports the requested mask', async () => { + bootCapsSpy.mockResolvedValue({ Body: { AMT_BootCapabilities: { PlatformErase: 3 } } }) + sendEraseSpy.mockResolvedValue(undefined) + + await sendRemoteErase(req, resSpy) + expect(sendEraseSpy).toHaveBeenCalledWith(3) + expect(resSpy.status).toHaveBeenCalledWith(200) + expect(resSpy.json).toHaveBeenCalledWith({ status: 'success' }) + }) + + it('should send remote erase with zero mask (no specific capability check)', async () => { + req.body.eraseMask = 0 + bootCapsSpy.mockResolvedValue({ Body: { AMT_BootCapabilities: { PlatformErase: 3 } } }) + sendEraseSpy.mockResolvedValue(undefined) + + await sendRemoteErase(req, resSpy) + expect(sendEraseSpy).toHaveBeenCalledWith(0) + expect(resSpy.status).toHaveBeenCalledWith(200) + }) + + it('should return 400 when device does not support platform erase', async () => { + bootCapsSpy.mockResolvedValue({ Body: { AMT_BootCapabilities: { PlatformErase: 0 } } }) + + await sendRemoteErase(req, resSpy) + expect(sendEraseSpy).not.toHaveBeenCalled() + expect(resSpy.status).toHaveBeenCalledWith(400) + expect(resSpy.json).toHaveBeenCalledWith(ErrorResponse(400, 'Device does not support Remote Platform Erase')) + }) + + it('should return 400 when requested mask is not supported by device', async () => { + req.body.eraseMask = 4 + bootCapsSpy.mockResolvedValue({ Body: { AMT_BootCapabilities: { PlatformErase: 3 } } }) + + await sendRemoteErase(req, resSpy) + expect(sendEraseSpy).not.toHaveBeenCalled() + expect(resSpy.status).toHaveBeenCalledWith(400) + expect(resSpy.json).toHaveBeenCalledWith(ErrorResponse(400, 'Requested erase capabilities are not supported by this device')) + }) + + it('should return 500 on unexpected error', async () => { + bootCapsSpy.mockRejectedValue(new Error('AMT error')) + + await sendRemoteErase(req, resSpy) + expect(resSpy.status).toHaveBeenCalledWith(500) + expect(resSpy.json).toHaveBeenCalledWith(ErrorResponse(500, messages.AMT_FEATURES_SET_EXCEPTION)) + }) +}) diff --git a/src/routes/amt/sendRemoteErase.ts b/src/routes/amt/sendRemoteErase.ts new file mode 100644 index 000000000..1a5f3e031 --- /dev/null +++ b/src/routes/amt/sendRemoteErase.ts @@ -0,0 +1,44 @@ +/********************************************************************* + * Copyright (c) Intel Corporation 2022 + * 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 { MPSValidationError } from '../../utils/MPSValidationError.js' + +export async function sendRemoteErase(req: Request, res: Response): Promise { + try { + const guid: string = req.params.guid + const { eraseMask } = req.body + const mask: number = eraseMask ?? 0 + + MqttProvider.publishEvent('request', ['AMT_BootSettingData'], messages.AMT_FEATURES_SET_REQUESTED, guid) + + const bootCaps = await req.deviceAction.getBootCapabilities() + const platformEraseCaps = bootCaps.Body?.AMT_BootCapabilities?.PlatformErase ?? 0 + + if (platformEraseCaps === 0) { + throw new MPSValidationError('Device does not support Remote Platform Erase', 400) + } + + if (mask !== 0 && (platformEraseCaps & mask) === 0) { + throw new MPSValidationError('Requested erase capabilities are not supported by this device', 400) + } + + await req.deviceAction.sendRemoteErase(mask) + + MqttProvider.publishEvent('success', ['AMT_BootSettingData'], messages.AMT_FEATURES_SET_SUCCESS, guid) + res.status(200).json({ status: 'success' }).end() + } catch (error) { + logger.error(`sendRemoteErase failed: ${error}`) + if (error instanceof MPSValidationError) { + res.status(error.status ?? 400).json(ErrorResponse(error.status ?? 400, error.message)) + } else { + MqttProvider.publishEvent('fail', ['AMT_BootSettingData'], messages.INTERNAL_SERVICE_ERROR) + res.status(500).json(ErrorResponse(500, messages.AMT_FEATURES_SET_EXCEPTION)).end() + } + } +} diff --git a/src/routes/amt/setAMTFeatures.ts b/src/routes/amt/setAMTFeatures.ts index 841be8c50..f49a90a4d 100644 --- a/src/routes/amt/setAMTFeatures.ts +++ b/src/routes/amt/setAMTFeatures.ts @@ -92,6 +92,15 @@ export async function setAMTFeatures(req: Request, res: Response): Promise await req.deviceAction.BootServiceStateChange(requestedState) } + // Configure Remote Platform Erase (RPE) + if (payload.enablePlatformErase !== undefined) { + const bootCaps = await req.deviceAction.getBootCapabilities() + const platformEraseCaps = bootCaps.Body?.AMT_BootCapabilities?.PlatformErase ?? 0 + if (platformEraseCaps !== 0) { + await req.deviceAction.setRPEEnabled(!!payload.enablePlatformErase) + } + } + MqttProvider.publishEvent('success', ['AMT_SetFeatures'], messages.AMT_FEATURES_SET_SUCCESS, guid) res.status(200).json({ status: messages.AMT_FEATURES_SET_SUCCESS }).end() } catch (error) { diff --git a/src/routes/amt/setRPEEnabled.test.ts b/src/routes/amt/setRPEEnabled.test.ts new file mode 100644 index 000000000..2a225c8d8 --- /dev/null +++ b/src/routes/amt/setRPEEnabled.test.ts @@ -0,0 +1,68 @@ +/********************************************************************* + * Copyright (c) Intel Corporation 2022 + * SPDX-License-Identifier: Apache-2.0 + **********************************************************************/ + +import { ErrorResponse } from '../../utils/amtHelper.js' +import { MqttProvider } from '../../utils/MqttProvider.js' +import { setRPEEnabled } from './setRPEEnabled.js' +import { createSpyObj } from '../../test/helper/jest.js' +import { DeviceAction } from '../../amt/DeviceAction.js' +import { CIRAHandler } from '../../amt/CIRAHandler.js' +import { HttpHandler } from '../../amt/HttpHandler.js' +import { messages } from '../../logging/index.js' +import { type Spied, spyOn } from 'jest-mock' + +describe('Set RPE Enabled', () => { + let req: any + let resSpy: any + let mqttSpy: Spied + let bootCapsSpy: Spied + let setRPESpy: Spied + let device: DeviceAction + + beforeEach(() => { + const handler = new CIRAHandler(new HttpHandler(), 'admin', 'P@ssw0rd') + device = new DeviceAction(handler, null) + req = { + params: { guid: '4c4c4544-004b-4210-8033-b6c04f504633' }, + body: { enabled: true }, + deviceAction: device + } + resSpy = createSpyObj('Response', ['status', 'json', 'end', 'send']) + resSpy.status.mockReturnThis() + resSpy.json.mockReturnThis() + resSpy.send.mockReturnThis() + + mqttSpy = spyOn(MqttProvider, 'publishEvent') + bootCapsSpy = spyOn(device, 'getBootCapabilities') + setRPESpy = spyOn(device, 'setRPEEnabled') + }) + + it('should enable RPE when device supports platform erase', async () => { + bootCapsSpy.mockResolvedValue({ Body: { AMT_BootCapabilities: { PlatformErase: 3 } } }) + setRPESpy.mockResolvedValue(undefined) + + await setRPEEnabled(req, resSpy) + expect(setRPESpy).toHaveBeenCalledWith(true) + expect(resSpy.status).toHaveBeenCalledWith(200) + expect(resSpy.json).toHaveBeenCalledWith({ status: 'success' }) + }) + + it('should return 400 when device does not support platform erase', async () => { + bootCapsSpy.mockResolvedValue({ Body: { AMT_BootCapabilities: { PlatformErase: 0 } } }) + + await setRPEEnabled(req, resSpy) + expect(setRPESpy).not.toHaveBeenCalled() + expect(resSpy.status).toHaveBeenCalledWith(400) + expect(resSpy.json).toHaveBeenCalledWith(ErrorResponse(400, 'Device does not support Remote Platform Erase')) + }) + + it('should return 500 on unexpected error', async () => { + bootCapsSpy.mockRejectedValue(new Error('AMT error')) + + await setRPEEnabled(req, resSpy) + expect(resSpy.status).toHaveBeenCalledWith(500) + expect(resSpy.json).toHaveBeenCalledWith(ErrorResponse(500, messages.AMT_FEATURES_SET_EXCEPTION)) + }) +}) diff --git a/src/routes/amt/setRPEEnabled.ts b/src/routes/amt/setRPEEnabled.ts new file mode 100644 index 000000000..fbb58ea9c --- /dev/null +++ b/src/routes/amt/setRPEEnabled.ts @@ -0,0 +1,39 @@ +/********************************************************************* + * Copyright (c) Intel Corporation 2022 + * 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 { MPSValidationError } from '../../utils/MPSValidationError.js' + +export async function setRPEEnabled(req: Request, res: Response): Promise { + try { + const guid: string = req.params.guid + const { enabled } = req.body + + MqttProvider.publishEvent('request', ['AMT_BootSettingData'], messages.AMT_FEATURES_SET_REQUESTED, guid) + + const bootCaps = await req.deviceAction.getBootCapabilities() + const platformEraseCaps = bootCaps.Body?.AMT_BootCapabilities?.PlatformErase ?? 0 + + if (platformEraseCaps === 0) { + throw new MPSValidationError('Device does not support Remote Platform Erase', 400) + } + + await req.deviceAction.setRPEEnabled(!!enabled) + + MqttProvider.publishEvent('success', ['AMT_BootSettingData'], messages.AMT_FEATURES_SET_SUCCESS, guid) + res.status(200).json({ status: 'success' }).end() + } catch (error) { + logger.error(`setRPEEnabled failed: ${error}`) + if (error instanceof MPSValidationError) { + res.status(error.status ?? 400).json(ErrorResponse(error.status ?? 400, error.message)) + } else { + MqttProvider.publishEvent('fail', ['AMT_BootSettingData'], messages.INTERNAL_SERVICE_ERROR) + res.status(500).json(ErrorResponse(500, messages.AMT_FEATURES_SET_EXCEPTION)).end() + } + } +}