This commit is contained in:
Kevin L'Hereec committed 2026-06-08 16:54:37 +02:00
1 parent 44f7d9acfc
commit ec7e66555d
4 files changed
+100 -4

No files matched your search

+4
View File
@@ -131,6 +131,10 @@ export default class CwmpController {
const paramValues = informData?.ParameterList?.ParameterValueStruct || []
if (paramValues.length > 0) {
device.updateFromValues(paramValues)
// Some writable parameters arrive in the Inform before GPN has classified them.
// updateFromValues falls back to storing unknowns as read-only properties.
// Reclassify known-writable Inform parameters as parameters (writable).
device.reclassifyInformWritables(paramValues)
}
device.setTmpParamValues(paramValues)
+27
View File
@@ -255,6 +255,33 @@ export class Device {
this._properties.delete(name)
}
/**
* Parameters that arrive in the Inform payload are writable by definition
* (the CPE reports them because the ACS is supposed to manage them).
* updateFromValues() defaults unknowns to properties (read-only), so we
* promote the known-writable Inform params back to parameters here.
*
* Known writable Inform parameters (TR-098 and TR-181):
* - *.ProvisioningCode
* - *.ParameterKey
*/
public reclassifyInformWritables(paramValues: any[]): void {
const WRITABLE_PATTERNS = [
/(^|\.)ProvisioningCode$/,
/(^|\.)ParameterKey$/,
]
for (const pv of paramValues || []) {
const name = typeof pv?.Name === 'string' ? pv.Name : pv?.Name?._ || null
if (!name) continue
if (!WRITABLE_PATTERNS.some((re) => re.test(name))) continue
// Only act if it ended up as a property (wrong classification)
if (!this._properties.has(name)) continue
const prop = this._properties.get(name)!
this._properties.delete(name)
this._parameters.set(name, new DeviceParameter(name, prop.value, prop.type))
}
}
public getConnectionRequestURL(): URL | null {
for (const property of this._properties.values()) {
const url = property.getConnectionRequestURL()
+5 -4
View File
@@ -59,9 +59,10 @@ class IdleState extends DeviceState {
'[IdleState] GPN allowed — proceeding to discovery'
)
// Discover mandatory subtrees via GPN.
// DeviceInfo is always included so that HardwareVersion, SoftwareVersion,
// ModelName, etc. are classified (writable vs read-only) on every session.
// GPN is supported: discover mandatory subtrees to classify parameters.
// DeviceInfo. is always included so HardwareVersion, SoftwareVersion, ModelName etc.
// are classified (writable vs read-only) from the device itself.
// If GPN is not supported, the catalog handles classification (reclassifyDeviceFromCatalog).
const dataModel = device.dataModel
if (dataModel === 'tr-181') {
return {
@@ -80,7 +81,7 @@ class IdleState extends DeviceState {
response: '',
}
}
// Unknown data model — skip GPN test, go to CriticalReadsState
// Unknown data model — skip GPN, go to CriticalReadsState
return { nextState: new CriticalReadsState(), response: '' }
}
}
+64
View File
@@ -226,3 +226,67 @@ describe('Device', () => {
expect(d.getParameter('Device.X')!.value).toBe('new')
})
})
describe('Device.reclassifyInformWritables', () => {
it('promotes ProvisioningCode from property to parameter', () => {
const d = makeDevice()
// Simulate what updateFromValues does on Inform: stores as property (unknown writability)
d.setProperty(new DeviceProperty('InternetGatewayDevice.DeviceInfo.ProvisioningCode', 'abc'))
d.reclassifyInformWritables([
{ Name: 'InternetGatewayDevice.DeviceInfo.ProvisioningCode', Value: { _: 'abc' } },
])
expect(d.getParameter('InternetGatewayDevice.DeviceInfo.ProvisioningCode')).not.toBeNull()
expect(d.getProperty('InternetGatewayDevice.DeviceInfo.ProvisioningCode')).toBeNull()
})
it('promotes ParameterKey from property to parameter', () => {
const d = makeDevice()
d.setProperty(new DeviceProperty('InternetGatewayDevice.ManagementServer.ParameterKey', ''))
d.reclassifyInformWritables([
{ Name: 'InternetGatewayDevice.ManagementServer.ParameterKey', Value: { _: '' } },
])
expect(d.getParameter('InternetGatewayDevice.ManagementServer.ParameterKey')).not.toBeNull()
expect(d.getProperty('InternetGatewayDevice.ManagementServer.ParameterKey')).toBeNull()
})
it('does not touch params that are already parameters', () => {
const d = makeDevice()
d.setParameter(new DeviceParameter('Device.DeviceInfo.ProvisioningCode', 'existing'))
d.reclassifyInformWritables([
{ Name: 'Device.DeviceInfo.ProvisioningCode', Value: { _: 'existing' } },
])
expect(d.getParameter('Device.DeviceInfo.ProvisioningCode')!.value).toBe('existing')
expect(d.getProperty('Device.DeviceInfo.ProvisioningCode')).toBeNull()
})
it('does not reclassify unknown names (e.g. SoftwareVersion stays property)', () => {
const d = makeDevice()
d.setProperty(new DeviceProperty('InternetGatewayDevice.DeviceInfo.SoftwareVersion', 'v1'))
d.reclassifyInformWritables([
{ Name: 'InternetGatewayDevice.DeviceInfo.SoftwareVersion', Value: { _: 'v1' } },
])
// SoftwareVersion is NOT in the writable list — should stay as property
expect(d.getProperty('InternetGatewayDevice.DeviceInfo.SoftwareVersion')).not.toBeNull()
expect(d.getParameter('InternetGatewayDevice.DeviceInfo.SoftwareVersion')).toBeNull()
})
it('handles Name as object node (XMLParser artefact)', () => {
const d = makeDevice()
d.setProperty(new DeviceProperty('Device.DeviceInfo.ProvisioningCode', 'code'))
d.reclassifyInformWritables([
{ Name: { _: 'Device.DeviceInfo.ProvisioningCode' }, Value: { _: 'code' } },
])
expect(d.getParameter('Device.DeviceInfo.ProvisioningCode')).not.toBeNull()
expect(d.getProperty('Device.DeviceInfo.ProvisioningCode')).toBeNull()
})
})