import { XMLParser } from 'fast-xml-parser' import type { CwmpMessage, SoapEnvelope, SoapHeader } from './tr069_types.js' export { type SoapEnvelope, type SoapHeader, type CwmpMessage } from './tr069_types.js' export const CWMP_VERSIONS: Record = { '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 function parseSoapXml(xml: string): SoapEnvelope { const parsed = xmlParser.parse(xml) as Record const envelope = (parsed['soap-env:Envelope'] as Record) || (parsed['SOAP-ENV:Envelope'] as Record) || (parsed['soapenv:Envelope'] as Record) || {} const header = (envelope['soap-env:Header'] as Record) || (envelope['SOAP-ENV:Header'] as Record) || (envelope['soapenv:Header'] as Record) || {} const body = (envelope['soap-env:Body'] as Record) || (envelope['SOAP-ENV:Body'] as Record) || (envelope['soapenv:Body'] as Record) || {} // Detect CWMP version from namespace attributes 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 as { _?: string })?._ || String(idNode) } return { header: soapHeader, body, cwmpVersion } } /** Types de méthodes CWMP acceptés en entrée (CPE→ACS) */ const ALLOWED_CWMP_TYPES = new Set([ 'Inform', 'TransferComplete', 'GetParameterNamesResponse', 'GetParameterValuesResponse', 'GetParameterAttributesResponse', 'SetParameterValuesResponse', 'SetParameterAttributesResponse', 'RebootResponse', 'FactoryResetResponse', 'DownloadResponse', 'UploadResponse', 'ScheduleInformResponse', 'Fault', ]) export function extractCwmpMessage(body: Record): CwmpMessage | null { for (const key of Object.keys(body)) { if (key.startsWith('cwmp:')) { const type = key.replace('cwmp:', '') if (!ALLOWED_CWMP_TYPES.has(type)) return null const data = body[key] return { type, data } as CwmpMessage } } // SOAP Fault envoyé par le CPE (soap-env:Fault / SOAP-ENV:Fault) const faultKey = Object.keys(body).find( (k) => k.toLowerCase().endsWith(':fault') || k === 'Fault' ) if (faultKey) { return { type: 'Fault', data: body[faultKey] } as CwmpMessage } return null } export function buildSoapEnvelope(cwmpId: string | undefined, bodyContent: string): string { const id = cwmpId || Math.random().toString(36).substring(2, 10) return ` ${id} ${bodyContent} ` }