Files
adam-acs/app/protocol/soap.ts
T
Kevin L'HereecandContinue aad93911e4 feat: TR-069 CWMP state machine hardening + exchange viewer improvements
- TR-069 CWMP state machine hardening (14 phases): shared event utility,
  multi-event routing, GPN circuit breaker, IdleState GPN guard, expanded
  GPN fault codes, CriticalReadsState, ConfigEval GPN guard, task queue
  race condition fix, explicit error handling, terminal states
  (OperationalState/PartialOperationalState), consolidated reclassify,
  crash pattern detection, removed enqueueImmediate, state flow wiring
- Fix SOAP Fault not processed by state machine (soap-env:Fault detection)
- Fix GPN circuit breaker not persisting after session drops
- Fix CriticalReadsState GPV response parsing (xsi:type normalization)
- Fix CriticalReadsState not processing GPV response values
- Skip SPV when device is already compliant (ProvisioningCode check)
- Add Device.DeviceInfo.ModelName to critical reads GPV
- Fix TypeError on DeviceDetail parameters view (null guards)
- Log empty 204 responses in exchange trace UI
- TR-181 catalog: type corrections and new entries (+6640 lines)
- SequenceViewer: replace session-nav buttons with compact dropdown select
- SequenceViewer: fix CPE/ACS lifeline headers wrapping to 2 lines
- Add 20 unit tests for state machine fixes (SOAP Fault, GPN circuit
  breaker, CriticalReads, stale session, ConfigEval/ComplianceUpdate skips)

Generated with [Continue](https://continue.dev)

Co-Authored-By: Continue <noreply@continue.dev>
2026-06-08 12:49:00 +00:00

99 lines
3.0 KiB
TypeScript

import { XMLParser } from 'fast-xml-parser'
export const CWMP_VERSIONS: Record<string, string> = {
'urn:dslforum-org:cwmp-1-0': '1.0',
'urn:dslforum-org:cwmp-1-1': '1.1',
'urn:dslforum-org:cwmp-1-2': '1.2',
'urn:dslforum-org:cwmp-1-3': '1.3',
'urn:dslforum-org:cwmp-1-4': '1.4',
}
const xmlParser = new XMLParser({
ignoreAttributes: false,
attributeNamePrefix: '$',
textNodeName: '_',
numberParseOptions: { leadingZeros: false, hex: false },
isArray: (name) => {
const arrayTags = ['ParameterValueStruct', 'ParameterInfoStruct', 'EventStruct', 'string']
return arrayTags.includes(name)
},
})
export interface SoapEnvelope {
header: SoapHeader
body: any
cwmpVersion: string
}
export interface SoapHeader {
id?: string
holdRequests?: boolean
noMoreRequests?: boolean
}
export interface CwmpMessage {
type: string
data: any
}
export function parseSoapXml(xml: string): SoapEnvelope {
const parsed = xmlParser.parse(xml)
const envelope = parsed['soap-env:Envelope'] || parsed['SOAP-ENV:Envelope'] || parsed['soapenv:Envelope'] || {}
const header = envelope['soap-env:Header'] || envelope['SOAP-ENV:Header'] || envelope['soapenv:Header'] || {}
const body = envelope['soap-env:Body'] || envelope['SOAP-ENV:Body'] || envelope['soapenv:Body'] || {}
// Detect CWMP version from namespace
let cwmpVersion = '1.0'
for (const [, value] of Object.entries(envelope)) {
if (typeof value === 'string' && value.startsWith('urn:dslforum-org:cwmp-')) {
cwmpVersion = CWMP_VERSIONS[value] || '1.0'
break
}
}
const soapHeader: SoapHeader = {}
const idNode = header['cwmp:ID'] || header['cwmp:Id']
if (idNode) {
soapHeader.id = typeof idNode === 'string' ? idNode : idNode._ || idNode
}
return { header: soapHeader, body, cwmpVersion }
}
export function extractCwmpMessage(body: any): CwmpMessage | null {
for (const key of Object.keys(body)) {
if (key.startsWith('cwmp:')) {
const type = key.replace('cwmp:', '')
return { type, data: body[key] }
}
}
// Detect SOAP Fault (CPE sends soap-env:Fault / SOAP-ENV:Fault when it cannot process a request)
const faultKey = Object.keys(body).find(
(k) => k.toLowerCase().endsWith(':fault') || k === 'Fault'
)
if (faultKey) {
return { type: 'Fault', data: body[faultKey] }
}
return null
}
export function buildSoapEnvelope(cwmpId: string | undefined, bodyContent: string): string {
const id = cwmpId || Math.random().toString(36).substring(2, 10)
return `<?xml version="1.0" encoding="UTF-8"?>
<soap-env:Envelope
xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:soap-enc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:cwmp="urn:dslforum-org:cwmp-1-0">
<soap-env:Header>
<cwmp:ID soap-env:mustUnderstand="1">${id}</cwmp:ID>
</soap-env:Header>
<soap-env:Body>
${bodyContent}
</soap-env:Body>
</soap-env:Envelope>`
}