Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
4cc8ba3
feat: mps API for AMT linkPreference
kithwa Oct 31, 2025
d11b7ee
fix: add instanceID as API input param
kithwa Nov 13, 2025
14dfe5a
feat: add Get() MPS API for EthernetPortSettings
kithwa Nov 14, 2025
6b2fb1d
fix: set instanceID as REST Query param
kithwa Nov 14, 2025
ea951df
feat: add Enumerate API for EthernetPortSettings
kithwa Nov 14, 2025
3ee78fd
fix: remove cancelLinkPreferenc API in MPS
kithwa Nov 14, 2025
ad292fb
fix: timeout by default 0 if LinkPreference Host
kithwa Nov 14, 2025
a88c205
fix: revert auto-configure timeout to 0
kithwa Nov 14, 2025
d55d3e8
test: add Unit Tests for new network APIs
kithwa Nov 14, 2025
bf23279
fix: fix unit test issues
kithwa Nov 14, 2025
55b30d9
fix: validate instanceID as WiFi port before proceeding
kithwa Nov 18, 2025
de529ac
fix: Wireless LAN is PhysicalConnectionType 3
kithwa Nov 18, 2025
310bf12
feat: auto retrieve instanceID for WiFi port
kithwa Nov 19, 2025
e48da0b
fix: unit tests updates
kithwa Nov 19, 2025
19ee555
docs: update swagger yaml file
kithwa Nov 19, 2025
c4b1e72
test: add API test for new network APIs
kithwa Nov 19, 2025
9f26d7f
cfg: update env details to run API test on dev machine
kithwa Nov 19, 2025
4effd14
fix: rm getEthernetPortSettings and enumerateEthernetPortSettings APIs
kithwa Nov 19, 2025
e4966e0
fix: rm validate and other dangling functions
kithwa Nov 19, 2025
a52b197
feat: print WiFiPort settings in setLinkPreference response
kithwa Nov 19, 2025
0b4a0ae
docs: rm ethernetPortSettings APIs from swagger yaml
kithwa Nov 19, 2025
6a19d14
test: rm API test for setLinkPreference specific instanceID
kithwa Nov 19, 2025
237d4c2
fix: read status after set
kithwa Nov 19, 2025
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
99 changes: 99 additions & 0 deletions src/amt/DeviceAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -660,4 +660,103 @@ export class DeviceAction {
logger.silly(`putKVMRedirectionSettingData ${messages.COMPLETE}`)
return result.Envelope.Body
}

async getEthernetPortSettings(): Promise<
Common.Models.Envelope<Common.Models.Pull<AMT.Models.EthernetPortSettings>>
> {
logger.silly(`getEthernetPortSettings ${messages.REQUEST}`)
let xmlRequestBody = this.amt.EthernetPortSettings.Enumerate()
const enumResponse = await this.ciraHandler.Enumerate(this.ciraSocket, xmlRequestBody)
if (enumResponse == null) {
logger.error(`getEthernetPortSettings failed. Reason: ${messages.ENUMERATION_RESPONSE_NULL}`)
return null
}
xmlRequestBody = this.amt.EthernetPortSettings.Pull(enumResponse.Envelope.Body.EnumerateResponse.EnumerationContext)
const pullResponse = await this.ciraHandler.Pull<AMT.Models.EthernetPortSettings>(this.ciraSocket, xmlRequestBody)
logger.silly(`getEthernetPortSettings ${messages.COMPLETE}`)
return pullResponse.Envelope
}

/**
* Finds the first WiFi port by checking PhysicalConnectionType
* @returns Object with instanceID and full port settings, or null if none found
*/
async findWiFiPort(): Promise<{ instanceID: string; settings: any } | null> {
logger.silly('findWiFiPort: searching for WiFi port')
try {
const enumResult = await this.getEthernetPortSettings()
if (enumResult?.Body?.PullResponse?.Items == null) {
logger.error('findWiFiPort: No ethernet port settings found')
return null
}

const settings: any = (enumResult.Body.PullResponse.Items as any).AMT_EthernetPortSettings
if (settings == null) {
logger.error('findWiFiPort: AMT_EthernetPortSettings not found in response')
return null
}

const ports = Array.isArray(settings) ? settings : [settings]

// Find the first port with PhysicalConnectionType = 3 (Wireless LAN)
const wifiPort = ports.find((port: any) => {
const connectionType = parseInt(port.PhysicalConnectionType, 10)
return connectionType === 3
})

if (wifiPort == null) {
logger.error('findWiFiPort: No WiFi port found')
return null
}

logger.silly(`findWiFiPort: Found WiFi port ${wifiPort.InstanceID}`)
return {
instanceID: wifiPort.InstanceID,
settings: wifiPort
}
} catch (err) {
logger.error(`findWiFiPort error: ${(err as Error).message}`)
return null
}
}

async setEthernetLinkPreference(
linkPreference: AMT.Types.EthernetPortSettings.LinkPreference,
timeoutSeconds: number
): Promise<Common.Models.Envelope<any>> {
logger.silly(`setEthernetLinkPreference ${messages.REQUEST}`)

// Auto-detect the WiFi port
logger.silly('setEthernetLinkPreference: Auto-detecting WiFi port')
const wifiPortInfo = await this.findWiFiPort()
if (wifiPortInfo == null) {
const errorMsg = 'No WiFi port found on this device. SetLinkPreference requires a WiFi interface (PhysicalConnectionType=3).'
logger.error(`setEthernetLinkPreference: ${errorMsg}`)
return {
Header: {},
Body: {
Fault: {
Code: { Value: 'NoWiFiPort' },
Reason: { Text: errorMsg }
}
}
} as any
}
logger.info(`setEthernetLinkPreference: Auto-detected WiFi port: ${wifiPortInfo.instanceID}`)

const xmlRequestBody = this.amt.EthernetPortSettings.SetLinkPreference(linkPreference, timeoutSeconds, wifiPortInfo.instanceID)
const result = await this.ciraHandler.Get(this.ciraSocket, xmlRequestBody)

// Fetch the updated WiFi port settings after the operation
logger.silly('setEthernetLinkPreference: Fetching updated WiFi port settings')
const updatedWifiPortInfo = await this.findWiFiPort()

logger.silly(`setEthernetLinkPreference ${messages.COMPLETE}`)
// Add the detected instanceID and updated WiFi port settings to the result for the handler to use
if (result?.Envelope != null) {
(result.Envelope as any)._detectedInstanceID = wifiPortInfo.instanceID;
(result.Envelope as any)._wifiPortSettings = updatedWifiPortInfo?.settings ?? wifiPortInfo.settings
}
return result.Envelope
}
}
151 changes: 151 additions & 0 deletions src/amt/deviceAction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -627,4 +627,155 @@ describe('Device Action Tests', () => {
expect(result).toEqual(putKVMRedirectionSettingDataResponse.Envelope.Body)
})
})

describe('WiFi port validation and link preference', () => {
let getEthernetPortSettingsSpy: SpyInstance<any>

beforeEach(() => {
getEthernetPortSettingsSpy = spyOn(device, 'getEthernetPortSettings')
})

it('should find WiFi port automatically', async () => {
const mockEnumResponse = {
Body: {
PullResponse: {
Items: {
AMT_EthernetPortSettings: [
{
InstanceID: 'Intel(r) AMT Ethernet Port Settings 0',
PhysicalConnectionType: '0', // Integrated LAN
ElementName: 'LAN Port'
},
{
InstanceID: 'Intel(r) AMT Ethernet Port Settings 1',
PhysicalConnectionType: '3', // Wireless LAN
ElementName: 'WiFi Port'
}
]
}
}
}
}
getEthernetPortSettingsSpy.mockResolvedValue(mockEnumResponse as any)

const result = await device.findWiFiPort()

expect(result).not.toBeNull()
expect(result?.instanceID).toBe('Intel(r) AMT Ethernet Port Settings 1')
expect(result?.settings.InstanceID).toBe('Intel(r) AMT Ethernet Port Settings 1')
expect(result?.settings.PhysicalConnectionType).toBe('3')
expect(result?.settings.ElementName).toBe('WiFi Port')
})

it('should return null when no WiFi port exists', async () => {
const mockEnumResponse = {
Body: {
PullResponse: {
Items: {
AMT_EthernetPortSettings: [
{
InstanceID: 'Intel(r) AMT Ethernet Port Settings 0',
PhysicalConnectionType: '0' // Only LAN
},
{
InstanceID: 'Intel(r) AMT Ethernet Port Settings 1',
PhysicalConnectionType: '1' // Only LAN
}
]
}
}
}
}
getEthernetPortSettingsSpy.mockResolvedValue(mockEnumResponse as any)

const result = await device.findWiFiPort()

expect(result).toBeNull()
})

it('should auto-detect WiFi port when instanceID not provided', async () => {
const mockEnumResponse = {
Body: {
PullResponse: {
Items: {
AMT_EthernetPortSettings: [
{
InstanceID: 'Intel(r) AMT Ethernet Port Settings 0',
PhysicalConnectionType: '0'
},
{
InstanceID: 'Intel(r) AMT Ethernet Port Settings 1',
PhysicalConnectionType: '3' // WiFi
}
]
}
}
}
}
getEthernetPortSettingsSpy.mockResolvedValue(mockEnumResponse as any)
getSpy.mockResolvedValue({ Envelope: { Body: { SetLinkPreference_OUTPUT: { ReturnValue: '0' } } } })

const result = await device.setEthernetLinkPreference(1, 300)

expect(result?.Body?.Fault).toBeUndefined()
expect(getSpy).toHaveBeenCalled()
expect((result as any)._detectedInstanceID).toBe('Intel(r) AMT Ethernet Port Settings 1')
})

it('should return error when no WiFi port found and instanceID not provided', async () => {
const mockEnumResponse = {
Body: {
PullResponse: {
Items: {
AMT_EthernetPortSettings: {
InstanceID: 'Intel(r) AMT Ethernet Port Settings 0',
PhysicalConnectionType: '0' // Only LAN
}
}
}
}
}
getEthernetPortSettingsSpy.mockResolvedValue(mockEnumResponse as any)

const result = await device.setEthernetLinkPreference(1, 300)

expect(result?.Body?.Fault).toBeDefined()
expect(result?.Body?.Fault?.Code?.Value).toBe('NoWiFiPort')
expect(result?.Body?.Fault?.Reason?.Text).toContain('No WiFi port found')
})

it('should auto-detect WiFi port and call SetLinkPreference', async () => {
const mockEnumResponse = {
Body: {
PullResponse: {
Items: {
AMT_EthernetPortSettings: [
{
InstanceID: 'Intel(r) AMT Ethernet Port Settings 0',
PhysicalConnectionType: '0', // Integrated LAN
ElementName: 'LAN Port'
},
{
InstanceID: 'Intel(r) AMT Ethernet Port Settings 1',
PhysicalConnectionType: '3', // Wireless LAN
ElementName: 'WiFi Port'
}
]
}
}
}
}
getEthernetPortSettingsSpy.mockResolvedValue(mockEnumResponse as any)
getSpy.mockResolvedValue({ Envelope: { Body: { SetLinkPreference_OUTPUT: { ReturnValue: '0' } } } })

const result = await device.setEthernetLinkPreference(1, 300)

expect(result?.Body?.Fault).toBeUndefined()
expect(getSpy).toHaveBeenCalled()
expect((result as any)?._detectedInstanceID).toBe('Intel(r) AMT Ethernet Port Settings 1')
expect((result as any)?._wifiPortSettings).toBeDefined()
expect((result as any)?._wifiPortSettings.InstanceID).toBe('Intel(r) AMT Ethernet Port Settings 1')
expect((result as any)?._wifiPortSettings.PhysicalConnectionType).toBe('3')
})
})
})
5 changes: 5 additions & 0 deletions src/routes/amt/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import { validator } from './kvm/validator.js'
import { get } from 'http'
import { getScreenSettingData } from './kvm/get.js'
import { setKVMRedirectionSettingData } from './kvm/set.js'
import { setLinkPreference } from './linkPreference.js'
import { linkPreferenceValidator } from './linkPreferenceValidator.js'

const amtRouter: Router = Router()

Expand Down Expand Up @@ -68,4 +70,7 @@ amtRouter.post('/certificates/:guid', certValidator(), validateMiddleware, ciraM
amtRouter.get('/kvm/displays/:guid', ciraMiddleware, getScreenSettingData)
amtRouter.put('/kvm/displays/:guid', validator(), ciraMiddleware, setKVMRedirectionSettingData)

// Link Preference (ME/HOST)
amtRouter.post('/network/linkPreference/:guid', linkPreferenceValidator(), validateMiddleware, ciraMiddleware, setLinkPreference)

export default amtRouter
Loading