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)
This commit is contained in:
1 parent
8969355688
commit
f3eb8e1650
19 files changed
+2745
-384
No files matched your search
@@ -1,147 +1,192 @@
|
||||
import { buildSoapEnvelope } from '../soap.js'
|
||||
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 body = `<cwmp:InformResponse>
|
||||
<MaxEnvelopes>1</MaxEnvelopes>
|
||||
</cwmp:InformResponse>`
|
||||
return buildSoapEnvelope(cwmpId, body)
|
||||
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 body = `<cwmp:GetParameterNames>
|
||||
<ParameterPath>${parameterPath}</ParameterPath>
|
||||
<NextLevel>${nextLevel ? '1' : '0'}</NextLevel>
|
||||
</cwmp:GetParameterNames>`
|
||||
return buildSoapEnvelope(cwmpId, body)
|
||||
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 names = parameterNames.map((n) => `<string>${n}</string>`).join('\n ')
|
||||
const body = `<cwmp:GetParameterValues>
|
||||
<ParameterNames soap-enc:arrayType="xsd:string[${parameterNames.length}]">
|
||||
${names}
|
||||
</ParameterNames>
|
||||
</cwmp:GetParameterValues>`
|
||||
return buildSoapEnvelope(cwmpId, body)
|
||||
const enve = makeEnvelope(cwmpId)
|
||||
enve.Body.GetParameterValues = new GetParameterValues({ ParameterNames: parameterNames })
|
||||
return enve.getXml()
|
||||
}
|
||||
|
||||
static setParameterValues(cwmpId: string | undefined, parameters: { name: string; value: string; type: string }[]): string {
|
||||
const params = parameters
|
||||
.map(
|
||||
(p) => `<ParameterValueStruct>
|
||||
<Name>${p.name}</Name>
|
||||
<Value xsi:type="${p.type}">${p.value}</Value>
|
||||
</ParameterValueStruct>`
|
||||
)
|
||||
.join('\n ')
|
||||
const body = `<cwmp:SetParameterValues>
|
||||
<ParameterList soap-enc:arrayType="cwmp:ParameterValueStruct[${parameters.length}]">
|
||||
${params}
|
||||
</ParameterList>
|
||||
<ParameterKey>papaye-v2</ParameterKey>
|
||||
</cwmp:SetParameterValues>`
|
||||
return buildSoapEnvelope(cwmpId, body)
|
||||
}
|
||||
|
||||
static download(
|
||||
static setParameterValues(
|
||||
cwmpId: string | undefined,
|
||||
params: {
|
||||
commandKey?: string
|
||||
fileType?: string
|
||||
url: string
|
||||
username?: string
|
||||
password?: string
|
||||
fileSize?: number
|
||||
targetFileName?: string
|
||||
delaySeconds?: number
|
||||
successUrl?: string
|
||||
failureUrl?: string
|
||||
}
|
||||
parameters: Array<{ name: string; value: string; type: string }>
|
||||
): string {
|
||||
const body = `<cwmp:Download>
|
||||
<CommandKey>${params.commandKey || ''}</CommandKey>
|
||||
<FileType>${params.fileType || '1 Firmware Upgrade Image'}</FileType>
|
||||
<URL>${params.url}</URL>
|
||||
<Username>${params.username || ''}</Username>
|
||||
<Password>${params.password || ''}</Password>
|
||||
<FileSize>${params.fileSize || 0}</FileSize>
|
||||
<TargetFileName>${params.targetFileName || ''}</TargetFileName>
|
||||
<DelaySeconds>${params.delaySeconds || 0}</DelaySeconds>
|
||||
<SuccessURL>${params.successUrl || ''}</SuccessURL>
|
||||
<FailureURL>${params.failureUrl || ''}</FailureURL>
|
||||
</cwmp:Download>`
|
||||
return buildSoapEnvelope(cwmpId, body)
|
||||
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 upload(
|
||||
cwmpId: string | undefined,
|
||||
params: { commandKey?: string; fileType?: string; url: string; username?: string; password?: string; delaySeconds?: number }
|
||||
): string {
|
||||
const body = `<cwmp:Upload>
|
||||
<CommandKey>${params.commandKey || ''}</CommandKey>
|
||||
<FileType>${params.fileType || '3 Vendor Configuration File'}</FileType>
|
||||
<URL>${params.url}</URL>
|
||||
<Username>${params.username || ''}</Username>
|
||||
<Password>${params.password || ''}</Password>
|
||||
<DelaySeconds>${params.delaySeconds || 0}</DelaySeconds>
|
||||
</cwmp:Upload>`
|
||||
return buildSoapEnvelope(cwmpId, body)
|
||||
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 body = `<cwmp:Reboot>
|
||||
<CommandKey>${commandKey || ''}</CommandKey>
|
||||
</cwmp:Reboot>`
|
||||
return buildSoapEnvelope(cwmpId, body)
|
||||
const enve = makeEnvelope(cwmpId)
|
||||
enve.Body.Reboot = new Reboot({ CommandKey: commandKey || '' })
|
||||
return enve.getXml()
|
||||
}
|
||||
|
||||
static factoryReset(cwmpId?: string): string {
|
||||
const body = `<cwmp:FactoryReset></cwmp:FactoryReset>`
|
||||
return buildSoapEnvelope(cwmpId, body)
|
||||
const enve = makeEnvelope(cwmpId)
|
||||
enve.Body.FactoryReset = new FactoryReset({})
|
||||
return enve.getXml()
|
||||
}
|
||||
|
||||
static scheduleInform(cwmpId: string | undefined, delaySeconds: number, commandKey?: string): string {
|
||||
const body = `<cwmp:ScheduleInform>
|
||||
<DelaySeconds>${delaySeconds}</DelaySeconds>
|
||||
<CommandKey>${commandKey || ''}</CommandKey>
|
||||
</cwmp:ScheduleInform>`
|
||||
return buildSoapEnvelope(cwmpId, body)
|
||||
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 names = parameterNames.map((n) => `<string>${n}</string>`).join('\n ')
|
||||
const body = `<cwmp:GetParameterAttributes>
|
||||
<ParameterNames soap-enc:arrayType="xsd:string[${parameterNames.length}]">
|
||||
${names}
|
||||
</ParameterNames>
|
||||
</cwmp:GetParameterAttributes>`
|
||||
return buildSoapEnvelope(cwmpId, body)
|
||||
const enve = makeEnvelope(cwmpId)
|
||||
enve.Body.GetParameterAttributes = new GetParameterAttributes({ ParameterNames: parameterNames })
|
||||
return enve.getXml()
|
||||
}
|
||||
|
||||
static setParameterAttributes(
|
||||
cwmpId: string | undefined,
|
||||
attributes: { name: string; notificationChange: boolean; notification: number; accessListChange: boolean; accessList: string[] }[]
|
||||
attributes: SetParameterAttributesTaskPayload[]
|
||||
): string {
|
||||
const attrs = attributes
|
||||
.map(
|
||||
(a) => `<SetParameterAttributesStruct>
|
||||
<Name>${a.name}</Name>
|
||||
<NotificationChange>${a.notificationChange ? '1' : '0'}</NotificationChange>
|
||||
<Notification>${a.notification}</Notification>
|
||||
<AccessListChange>${a.accessListChange ? '1' : '0'}</AccessListChange>
|
||||
<AccessList soap-enc:arrayType="xsd:string[${a.accessList.length}]">
|
||||
${a.accessList.map((s) => `<string>${s}</string>`).join('')}
|
||||
</AccessList>
|
||||
</SetParameterAttributesStruct>`
|
||||
)
|
||||
.join('')
|
||||
const body = `<cwmp:SetParameterAttributes>
|
||||
<ParameterList soap-enc:arrayType="cwmp:SetParameterAttributesStruct[${attributes.length}]">
|
||||
${attrs}
|
||||
</ParameterList>
|
||||
</cwmp:SetParameterAttributes>`
|
||||
return buildSoapEnvelope(cwmpId, body)
|
||||
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 {
|
||||
|
||||
Reference in new issue
Block a user