Files
adam-acs/app/protocol/common/cwmp_message_builder.ts
T
Kevin L'Hereec f3eb8e1650 feat: restore strong typing on TR-069/CWMP protocol layer
- Copy cwmp-1-0..4.ts source classes from cwmp-connector (papaye) into
  app/protocol/ as local files (ts-nocheck, external dependency)
- Add soap_envelope.ts: SoapEnvelope, SoapEnvelopeHeader, CwmpID, NAMESPACES
- Add tr069_types.ts: re-exports all CWMP classes + CwmpMessage discriminated
  union + typed task payload interfaces (CWMPTaskType, CWMPTaskPayload, etc.)
- Rewrite cwmp_message_builder.ts to use class-based builders instead of raw
  XML template strings; add transferCompleteResponse() and soapFault()
- soap.ts: extractCwmpMessage returns CwmpMessage union, adds runtime allowlist
  guard against unknown CWMP method names from CPE
- cwmp_middleware.ts: envelope typed SoapEnvelope|null, message CwmpMessage|null
- cwmp_controller.ts: handleInform(informData: Inform), remove any, migrate
  TransferComplete and fault to new builder path, drop buildSoapEnvelope import
- task_queue.ts: CWMPTask.type → CWMPTaskType literal union, payload →
  CWMPTaskPayload, result → CWMPTaskResultData
- device_action_executor.ts: GPV/GPN/GPA responses typed, no more any
- main_state_machine.ts: DeviceContext.message.data → CWMPTaskResultData,
  organizeParameters receives ParameterInfoStruct[] directly
- device.ts: tmpParamValues typed ParameterValueStruct[], organizeParameters
  signature updated, updateFromValues typed
- device_id.ts: fromInform typed with DeviceIdStruct
- soap_envelope.ts: remove unconditional <cwmp:noMoreRequests> header
  (CPE→ACS only per TR-069 §3.7.1.4)
2026-07-20 15:25:26 +02:00

196 lines
6.1 KiB
TypeScript

import { randomBytes } from 'node:crypto'
import {
Cwmp_1_0,
Download,
FactoryReset,
FaultCodeType,
FaultDetail,
FaultStruct,
Fault,
GetParameterAttributes,
GetParameterNames,
GetParameterValues,
InformResponse,
ParameterValueList,
Reboot,
ScheduleInform,
SetParameterAttributes,
SetParameterAttributesList,
SetParameterAttributesStruct,
AccessList,
SetParameterValues,
TransferCompleteResponse,
Upload,
} from '../cwmp-1-0.js'
import {
CwmpID,
NAMESPACES,
SoapEnvelope,
SoapEnvelopeAttribute,
SoapEnvelopeHeader,
} from '../soap_envelope.js'
import type { DownloadTaskPayload, UploadTaskPayload, SetParameterValuesTaskPayload, SetParameterAttributesTaskPayload } from '../tr069_types.js'
function generateCwmpID(): string {
return randomBytes(4).toString('hex')
}
function makeEnvelope(cwmpId?: string): SoapEnvelope {
const enve = new SoapEnvelope()
enve.$ = NAMESPACES['1.0'] as SoapEnvelopeAttribute
enve.Header = new SoapEnvelopeHeader()
enve.Header.ID = new CwmpID({
$: { MustUnderstand: '1' },
_: cwmpId || generateCwmpID(),
})
enve.Body = new Cwmp_1_0()
return enve
}
export class CWMPMessageBuilder {
static informResponse(cwmpId?: string): string {
const enve = makeEnvelope(cwmpId)
enve.Body.InformResponse = new InformResponse({ MaxEnvelopes: 1 })
return enve.getXml()
}
static getParameterNames(cwmpId: string | undefined, parameterPath: string, nextLevel: boolean = true): string {
const enve = makeEnvelope(cwmpId)
enve.Body.GetParameterNames = new GetParameterNames({
ParameterPath: parameterPath,
NextLevel: nextLevel,
})
return enve.getXml()
}
static getParameterValues(cwmpId: string | undefined, parameterNames: string[]): string {
const enve = makeEnvelope(cwmpId)
enve.Body.GetParameterValues = new GetParameterValues({ ParameterNames: parameterNames })
return enve.getXml()
}
static setParameterValues(
cwmpId: string | undefined,
parameters: Array<{ name: string; value: string; type: string }>
): string {
const pvStructs = parameters.map((p) => ({
Name: p.name,
Value: { _: p.value, $: { Type: p.type } },
}))
const pvList = new ParameterValueList({
$: { ArrayType: `cwmp:ParameterValueStruct[${pvStructs.length}]` },
ParameterValueStruct: pvStructs,
})
const enve = makeEnvelope(cwmpId)
enve.Body.SetParameterValues = new SetParameterValues({
ParameterValueList: pvList,
ParameterKey: 'papaye-v2',
})
return enve.getXml()
}
static setParameterValuesFromPayload(cwmpId: string | undefined, payload: SetParameterValuesTaskPayload): string {
const params = (payload.parameterValueList || []).map((p) => ({
name: p.Name,
value: String(p.Value?._ ?? ''),
type: p.Value?.$?.Type || 'xsd:string',
}))
return CWMPMessageBuilder.setParameterValues(cwmpId, params)
}
static download(cwmpId: string | undefined, params: DownloadTaskPayload): string {
const enve = makeEnvelope(cwmpId)
enve.Body.Download = new Download({
CommandKey: params.commandKey || '',
FileType: params.fileType || '1 Firmware Upgrade Image',
URL: params.url,
Username: params.username || '',
Password: params.password || '',
FileSize: params.fileSize || 0,
TargetFileName: params.targetFileName || '',
DelaySeconds: params.delaySeconds || 0,
SuccessURL: params.successUrl || '',
FailureURL: params.failureUrl || '',
})
return enve.getXml()
}
static upload(cwmpId: string | undefined, params: UploadTaskPayload): string {
const enve = makeEnvelope(cwmpId)
enve.Body.Upload = new Upload({
CommandKey: params.commandKey || '',
FileType: params.fileType || '3 Vendor Configuration File',
URL: params.url,
Username: params.username || '',
Password: params.password || '',
DelaySeconds: params.delaySeconds || 0,
})
return enve.getXml()
}
static reboot(cwmpId?: string, commandKey?: string): string {
const enve = makeEnvelope(cwmpId)
enve.Body.Reboot = new Reboot({ CommandKey: commandKey || '' })
return enve.getXml()
}
static factoryReset(cwmpId?: string): string {
const enve = makeEnvelope(cwmpId)
enve.Body.FactoryReset = new FactoryReset({})
return enve.getXml()
}
static scheduleInform(cwmpId: string | undefined, delaySeconds: number, commandKey?: string): string {
const enve = makeEnvelope(cwmpId)
enve.Body.ScheduleInform = new ScheduleInform({
DelaySeconds: delaySeconds,
CommandKey: commandKey || '',
})
return enve.getXml()
}
static getParameterAttributes(cwmpId: string | undefined, parameterNames: string[]): string {
const enve = makeEnvelope(cwmpId)
enve.Body.GetParameterAttributes = new GetParameterAttributes({ ParameterNames: parameterNames })
return enve.getXml()
}
static setParameterAttributes(
cwmpId: string | undefined,
attributes: SetParameterAttributesTaskPayload[]
): string {
const structs = attributes.map(
(a) =>
new SetParameterAttributesStruct({
Name: a.name,
NotificationChange: a.notificationChange,
Notification: a.notification,
AccessListChange: a.accessListChange,
AccessList: new AccessList({ string: a.accessList }),
})
)
const list = new SetParameterAttributesList({ SetParameterAttributesStruct: structs })
const enve = makeEnvelope(cwmpId)
enve.Body.SetParameterAttributes = new SetParameterAttributes({ ParameterList: list })
return enve.getXml()
}
static transferCompleteResponse(cwmpId?: string): string {
const enve = makeEnvelope(cwmpId)
enve.Body.TransferCompleteResponse = new TransferCompleteResponse({})
return enve.getXml()
}
static soapFault(faultCode: FaultCodeType, reason: string): string {
const fault = new Fault()
fault.Faultcode = 'Server'
fault.Faultstring = reason
fault.Detail = new FaultDetail({ Fault: new FaultStruct({ FaultCode: faultCode, FaultString: reason }) })
return fault.getXml()
}
static emptyResponse(): string {
return ''
}
}