Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions src/amt/DeviceAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,32 @@ export class DeviceAction {
return result.Envelope
}

async getBootCapabilities(): Promise<Common.Models.Envelope<AMT.Models.BootCapabilities>> {
logger.silly(`getBootCapabilities ${messages.REQUEST}`)
const xmlRequestBody = this.amt.BootCapabilities.Get()
const result = await this.ciraHandler.Get<AMT.Models.BootCapabilities>(this.ciraSocket, xmlRequestBody)
logger.silly(`getBootCapabilities ${messages.COMPLETE}`)
return result.Envelope
}

async setRPEEnabled(enabled: boolean): Promise<void> {
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<void> {
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<Common.Models.Envelope<IPS.Models.StartOptIn_OUTPUT>> {
logger.silly(`requestUserConsentCode ${messages.REQUEST}`)
const xmlRequestBody = this.ips.OptInService.StartOptIn()
Expand Down
28 changes: 28 additions & 0 deletions src/amt/deviceAction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 2 additions & 1 deletion src/routes/amt/amtFeatureValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
]
14 changes: 10 additions & 4 deletions src/routes/amt/getAMTFeatures.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,8 @@ describe('get amt features', () => {
ForcedProgressEvents: true,
IDER: true,
InstanceID: 'Intel(r) AMT:BootCapabilities 0',
SOL: true
SOL: true,
PlatformErase: 3
}
}
},
Expand All @@ -156,7 +157,8 @@ describe('get amt features', () => {
IDERBootDevice: 0,
InstanceID: 'Intel(r) AMT:BootSettingData 0',
UseIDER: false,
UseSOL: false
UseSOL: false,
PlatformErase: true
}
}
})
Expand All @@ -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)
})
Expand Down Expand Up @@ -271,7 +275,9 @@ describe('get amt features', () => {
httpsBootSupported: false,
winREBootSupported: false,
localPBABootSupported: false,
remoteErase: false
remoteEraseEnabled: false,
remoteEraseSupported: false,
platformEraseCaps: 0
})
})
})
10 changes: 8 additions & 2 deletions src/routes/amt/getAMTFeatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,12 @@ export async function getAMTFeatures(req: Request, res: Response): Promise<void>
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,
Expand All @@ -43,7 +47,9 @@ export async function getAMTFeatures(req: Request, res: Response): Promise<void>
httpsBootSupported: ocrProcessResult.HTTPSBootSupported,
winREBootSupported: ocrProcessResult.WinREBootSupported,
localPBABootSupported: ocrProcessResult.LocalPBABootSupported,
remoteErase: false
remoteEraseEnabled,
remoteEraseSupported,
platformEraseCaps
})
.end()
} catch (error) {
Expand Down
65 changes: 65 additions & 0 deletions src/routes/amt/getBootCapabilities.test.ts
Original file line number Diff line number Diff line change
@@ -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<any>
let bootCapsSpy: Spied<any>
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()
})
})
27 changes: 27 additions & 0 deletions src/routes/amt/getBootCapabilities.ts
Original file line number Diff line number Diff line change
@@ -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<void> {
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()
}
}
6 changes: 6 additions & 0 deletions src/routes/amt/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand All @@ -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)
Expand Down
88 changes: 88 additions & 0 deletions src/routes/amt/sendRemoteErase.test.ts
Original file line number Diff line number Diff line change
@@ -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<any>
let bootCapsSpy: Spied<any>
let sendEraseSpy: Spied<any>
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))
})
})
44 changes: 44 additions & 0 deletions src/routes/amt/sendRemoteErase.ts
Original file line number Diff line number Diff line change
@@ -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<void> {
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()
}
}
}
Loading
Loading