Files
adam-acs/app/protocol/soap_envelope.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

200 lines
7.0 KiB
TypeScript

/**
* Classes SOAP Envelope (extraites de cwmp-connector/src/protocol/soap.ts)
*
* Séparées de soap.ts (le module de parsing d'adam-acs) pour éviter les conflits de nom.
*/
import { Cwmp_1_0 } from './cwmp-1-0.js'
import { Cwmp_1_1 } from './cwmp-1-1.js'
import { Cwmp_1_2 } from './cwmp-1-2.js'
import { Cwmp_1_3, SessionTimeout } from './cwmp-1-3.js'
import { Cwmp_1_4, SupportedCWMPVersions, UseCWMPVersion } from './cwmp-1-4.js'
type NamespaceDescriptor = {
SoapEnc: string
SoapEnv: string
Xsd: string
Xsi: string
Cwmp: string
}
export const NAMESPACES: Record<string, NamespaceDescriptor> = {
'1.0': {
SoapEnc: 'http://schemas.xmlsoap.org/soap/encoding/',
SoapEnv: 'http://schemas.xmlsoap.org/soap/envelope/',
Xsd: 'http://www.w3.org/2001/XMLSchema',
Xsi: 'http://www.w3.org/2001/XMLSchema-instance',
Cwmp: 'urn:dslforum-org:cwmp-1-0',
},
'1.1': {
SoapEnc: 'http://schemas.xmlsoap.org/soap/encoding/',
SoapEnv: 'http://schemas.xmlsoap.org/soap/envelope/',
Xsd: 'http://www.w3.org/2001/XMLSchema',
Xsi: 'http://www.w3.org/2001/XMLSchema-instance',
Cwmp: 'urn:dslforum-org:cwmp-1-1',
},
'1.2': {
SoapEnc: 'http://schemas.xmlsoap.org/soap/encoding/',
SoapEnv: 'http://schemas.xmlsoap.org/soap/envelope/',
Xsd: 'http://www.w3.org/2001/XMLSchema',
Xsi: 'http://www.w3.org/2001/XMLSchema-instance',
Cwmp: 'urn:dslforum-org:cwmp-1-2',
},
'1.3': {
SoapEnc: 'http://schemas.xmlsoap.org/soap/encoding/',
SoapEnv: 'http://schemas.xmlsoap.org/soap/envelope/',
Xsd: 'http://www.w3.org/2001/XMLSchema',
Xsi: 'http://www.w3.org/2001/XMLSchema-instance',
Cwmp: 'urn:dslforum-org:cwmp-1-3',
},
'1.4': {
SoapEnc: 'http://schemas.xmlsoap.org/soap/encoding/',
SoapEnv: 'http://schemas.xmlsoap.org/soap/envelope/',
Xsd: 'http://www.w3.org/2001/XMLSchema',
Xsi: 'http://www.w3.org/2001/XMLSchema-instance',
Cwmp: 'urn:dslforum-org:cwmp-1-4',
},
}
export class SoapEnvelopeAttribute {
public SoapEnc: string
public SoapEnv: string
public Xsd: string
public Xsi: string
public Cwmp: string
constructor(props: NamespaceDescriptor | Record<string, string> = NAMESPACES['1.0']) {
this.SoapEnc = props?.SoapEnc || 'http://schemas.xmlsoap.org/soap/encoding/'
this.SoapEnv = props?.SoapEnv || 'http://schemas.xmlsoap.org/soap/envelope/'
this.Xsd = props?.Xsd || 'http://www.w3.org/2001/XMLSchema'
this.Xsi = props?.Xsi || 'http://www.w3.org/2001/XMLSchema-instance'
this.Cwmp = props?.Cwmp || 'urn:dslforum-org:cwmp-1-0'
}
public isEqualTo(namespace: NamespaceDescriptor): boolean {
return (
this.SoapEnc === namespace.SoapEnc &&
this.SoapEnv === namespace.SoapEnv &&
this.Xsd === namespace.Xsd &&
this.Xsi === namespace.Xsi &&
this.Cwmp === namespace.Cwmp
)
}
}
export class SoapCwmpIdAttribute {
MustUnderstand: string
constructor(props: { MustUnderstand?: string } = {}) {
this.MustUnderstand = props.MustUnderstand || '1'
}
getXml(): string {
return ` soap-env:mustUnderstand="${this.MustUnderstand}"`
}
}
export class CwmpID {
$?: SoapCwmpIdAttribute
_?: string
constructor(props: { $?: { MustUnderstand?: string }; _?: string } = {}) {
this.$ = props?.$ ? new SoapCwmpIdAttribute(props.$) : undefined
this._ = props._ || ''
}
getXml(): string {
return `<cwmp:ID${this.$?.getXml() || ''}>${this._}</cwmp:ID>`
}
}
export type CwmpBody = Cwmp_1_0 | Cwmp_1_1 | Cwmp_1_2 | Cwmp_1_3 | Cwmp_1_4
export class SoapEnvelopeHeader {
ID: CwmpID
NoMoreRequests: number
SessionTimeout?: SessionTimeout
SupportedCWMPVersions?: SupportedCWMPVersions
UseCWMPVersion?: UseCWMPVersion
constructor(props: Record<string, unknown> = {}) {
this.ID = new CwmpID(props?.ID as any)
this.NoMoreRequests = (props?.NoMoreRequests as number) || 0
this.SessionTimeout = props?.SessionTimeout ? new SessionTimeout(props.SessionTimeout) : undefined
this.SupportedCWMPVersions = props?.SupportedCWMPVersions
? new SupportedCWMPVersions(props.SupportedCWMPVersions)
: undefined
this.UseCWMPVersion = props?.UseCWMPVersion ? new UseCWMPVersion(props.UseCWMPVersion) : undefined
}
}
export class SoapEnvelope {
$?: SoapEnvelopeAttribute
Header: SoapEnvelopeHeader
Body: CwmpBody
version: '1.0' | '1.1' | '1.2' | '1.3' | '1.4'
constructor(props: Record<string, unknown> = {}) {
this.version = (props?.version as '1.0' | '1.1' | '1.2' | '1.3' | '1.4') || '1.0'
this.$ = props?.$ ? new SoapEnvelopeAttribute(props.$ as any) : new SoapEnvelopeAttribute(NAMESPACES[this.version])
this.Header = new SoapEnvelopeHeader((props.Header as Record<string, unknown>) || {})
const bodyProps = (props.Body as Record<string, unknown>) || {}
switch (this.version) {
case '1.4': this.Body = new Cwmp_1_4(bodyProps); break
case '1.3': this.Body = new Cwmp_1_3(bodyProps); break
case '1.2': this.Body = new Cwmp_1_2(bodyProps); break
case '1.1': this.Body = new Cwmp_1_1(bodyProps); break
default: this.Body = new Cwmp_1_0(bodyProps); break
}
}
getXml(): string {
const headerParts: string[] = []
if (this.Header.ID) headerParts.push(this.Header.ID.getXml())
if (this.Header.SessionTimeout) {
headerParts.push(
`<cwmp:SessionTimeout soap-env:mustUnderstand="${this.Header.SessionTimeout.MustUnderstand}">${this.Header.SessionTimeout._}</cwmp:SessionTimeout>`
)
}
if (this.Header.SupportedCWMPVersions) {
headerParts.push(
`<cwmp:SupportedCWMPVersions${this.Header.SupportedCWMPVersions.MustUnderstand !== undefined ? ` soap-env:mustUnderstand="${this.Header.SupportedCWMPVersions.MustUnderstand}"` : ''}>${this.Header.SupportedCWMPVersions._}</cwmp:SupportedCWMPVersions>`
)
}
if (this.Header.UseCWMPVersion) {
headerParts.push(
`<cwmp:UseCWMPVersion soap-env:mustUnderstand="${this.Header.UseCWMPVersion.MustUnderstand}">${this.Header.UseCWMPVersion._}</cwmp:UseCWMPVersion>`
)
}
// noMoreRequests est un header CPE→ACS uniquement (TR-069 §3.7.1.4) — ne pas l'émettre côté ACS
return `<soap-env:Envelope
xmlns:soap-enc="${this.$?.SoapEnc}"
xmlns:soap-env="${this.$?.SoapEnv}"
xmlns:xsd="${this.$?.Xsd}"
xmlns:xsi="${this.$?.Xsi}"
xmlns:cwmp="${this.$?.Cwmp}">
<soap-env:Header>
${headerParts.join('\n ')}
</soap-env:Header>
<soap-env:Body>
${
this.Body.InformResponse?.getXml() ||
this.Body.GetParameterValues?.getXml() ||
this.Body.GetParameterNames?.getXml() ||
this.Body.GetParameterAttributes?.getXml() ||
this.Body.SetParameterValues?.getXml() ||
this.Body.SetParameterAttributes?.getXml() ||
this.Body.ScheduleInform?.getXml() ||
this.Body.Download?.getXml() ||
this.Body.Upload?.getXml() ||
this.Body.Reboot?.getXml() ||
this.Body.FactoryReset?.getXml() ||
this.Body.TransferCompleteResponse?.getXml() ||
''
}
</soap-env:Body>
</soap-env:Envelope>`
}
}