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
@@ -2,10 +2,21 @@ import { DeviceId } from '../../domain/device/device_id.js'
|
||||
import { EventEmitter } from 'node:events'
|
||||
import { CWMPMessageBuilder } from '../../protocol/common/cwmp_message_builder.js'
|
||||
import logger from '@adonisjs/core/services/logger'
|
||||
import type {
|
||||
CWMPTaskType,
|
||||
CWMPTaskPayload,
|
||||
CWMPTaskResultData,
|
||||
SetParameterValuesTaskPayload,
|
||||
SetParameterAttributesTaskPayload,
|
||||
ScheduleInformTaskPayload,
|
||||
DownloadTaskPayload,
|
||||
UploadTaskPayload,
|
||||
RebootTaskPayload,
|
||||
} from '../../protocol/tr069_types.js'
|
||||
|
||||
export class TaskResult {
|
||||
status!: ResultStatus
|
||||
data: any
|
||||
data: CWMPTaskResultData = {} as CWMPTaskResultData
|
||||
timestamp!: Date
|
||||
}
|
||||
|
||||
@@ -17,12 +28,12 @@ export enum ResultStatus {
|
||||
export interface CWMPTask {
|
||||
id: string
|
||||
deviceId: DeviceId
|
||||
type: string
|
||||
payload: any
|
||||
type: CWMPTaskType
|
||||
payload: CWMPTaskPayload
|
||||
status: TaskStatus
|
||||
createdAt: Date
|
||||
completedAt?: Date
|
||||
result?: any
|
||||
result?: CWMPTaskResultData
|
||||
error?: string
|
||||
}
|
||||
|
||||
@@ -38,53 +49,49 @@ export enum TaskStatus {
|
||||
* Maps tasks to CWMP message builders and manages callbacks
|
||||
*/
|
||||
export class CWMPTaskQueue extends EventEmitter {
|
||||
static task_map: Record<string, (props: any) => string> = {
|
||||
get_parameter_names: (parameterPath: string) => {
|
||||
// Use NextLevel=1 for root paths (e.g. "Device." or "InternetGatewayDevice.")
|
||||
// to avoid fetching the entire parameter tree in a single response.
|
||||
// For deeper paths (e.g. "Device.DeviceInfo.") use NextLevel=0 to get all leaves.
|
||||
const segments = parameterPath.replace(/\.$/, '').split('.')
|
||||
static task_map: Record<CWMPTaskType, (props: CWMPTaskPayload) => string> = {
|
||||
get_parameter_names: (parameterPath) => {
|
||||
const path = parameterPath as string
|
||||
const segments = path.replace(/\.$/, '').split('.')
|
||||
const nextLevel = segments.length <= 1
|
||||
return CWMPMessageBuilder.getParameterNames(undefined, parameterPath, nextLevel)
|
||||
return CWMPMessageBuilder.getParameterNames(undefined, path, nextLevel)
|
||||
},
|
||||
get_parameter_attributes: (parameterNames: string[]) => {
|
||||
return CWMPMessageBuilder.getParameterAttributes(undefined, parameterNames)
|
||||
get_parameter_attributes: (parameterNames) => {
|
||||
return CWMPMessageBuilder.getParameterAttributes(undefined, parameterNames as string[])
|
||||
},
|
||||
get_parameter_values: (parameterNames: string[]) => {
|
||||
return CWMPMessageBuilder.getParameterValues(undefined, parameterNames)
|
||||
get_parameter_values: (parameterNames) => {
|
||||
return CWMPMessageBuilder.getParameterValues(undefined, parameterNames as string[])
|
||||
},
|
||||
set_parameter_values: (props: { parameterValueList: any[]; parameterKey?: string }) => {
|
||||
const params = (props.parameterValueList || []).map((p: any) => ({
|
||||
name: p.Name,
|
||||
value: String(p.Value?._ ?? ''),
|
||||
type: p.Value?.$?.Type || 'xsd:string',
|
||||
set_parameter_values: (props) => {
|
||||
const p = props as SetParameterValuesTaskPayload
|
||||
const params = (p.parameterValueList || []).map((pv) => ({
|
||||
name: pv.Name,
|
||||
value: String(pv.Value?._ ?? ''),
|
||||
type: pv.Value?.$?.Type || 'xsd:string',
|
||||
}))
|
||||
return CWMPMessageBuilder.setParameterValues(undefined, params)
|
||||
},
|
||||
set_parameter_attributes: (attrs: any[]) => {
|
||||
const mapped = attrs.map((a: any) => ({
|
||||
name: a.name || a.Name,
|
||||
notificationChange: !!(a.notificationChange ?? a.NotificationChange),
|
||||
notification: a.notification ?? a.Notification ?? 0,
|
||||
accessListChange: !!(a.accessListChange ?? a.AccessListChange),
|
||||
accessList: a.accessList ?? a.AccessList ?? [],
|
||||
}))
|
||||
return CWMPMessageBuilder.setParameterAttributes(undefined, mapped)
|
||||
set_parameter_attributes: (attrs) => {
|
||||
return CWMPMessageBuilder.setParameterAttributes(
|
||||
undefined,
|
||||
attrs as SetParameterAttributesTaskPayload[]
|
||||
)
|
||||
},
|
||||
schedule_inform: (props: { delayInSeconds: number; commandKey?: string }) => {
|
||||
return CWMPMessageBuilder.scheduleInform(undefined, props.delayInSeconds || 300, props.commandKey)
|
||||
schedule_inform: (props) => {
|
||||
const p = props as ScheduleInformTaskPayload
|
||||
return CWMPMessageBuilder.scheduleInform(undefined, p.delayInSeconds || 300, p.commandKey)
|
||||
},
|
||||
download: (props: any) => {
|
||||
return CWMPMessageBuilder.download(undefined, props)
|
||||
download: (props) => {
|
||||
return CWMPMessageBuilder.download(undefined, props as DownloadTaskPayload)
|
||||
},
|
||||
upload: (props: any) => {
|
||||
return CWMPMessageBuilder.upload(undefined, props)
|
||||
upload: (props) => {
|
||||
return CWMPMessageBuilder.upload(undefined, props as UploadTaskPayload)
|
||||
},
|
||||
factory_reset: () => {
|
||||
return CWMPMessageBuilder.factoryReset(undefined)
|
||||
},
|
||||
reboot: (props: any) => {
|
||||
return CWMPMessageBuilder.reboot(undefined, props?.commandKey)
|
||||
reboot: (props) => {
|
||||
return CWMPMessageBuilder.reboot(undefined, (props as RebootTaskPayload)?.commandKey)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -100,8 +107,8 @@ export class CWMPTaskQueue extends EventEmitter {
|
||||
*/
|
||||
addTask(
|
||||
deviceId: DeviceId,
|
||||
taskType: string,
|
||||
props: any,
|
||||
taskType: CWMPTaskType,
|
||||
props: CWMPTaskPayload,
|
||||
callback: (result: TaskResult) => Promise<string>
|
||||
): { xml: string; taskId: string } {
|
||||
const cwmpTask = this.addCWMPTask(deviceId, taskType, props)
|
||||
@@ -115,10 +122,8 @@ export class CWMPTaskQueue extends EventEmitter {
|
||||
cwmpTask.status = TaskStatus.SENT
|
||||
}
|
||||
|
||||
// Track the most recent task created for this device so callers can retrieve its ID
|
||||
this.lastCreatedTaskIdByDevice.set(JSON.stringify(deviceId), cwmpTask.id)
|
||||
|
||||
// Generate CWMP envelope
|
||||
const builder = CWMPTaskQueue.task_map[taskType]
|
||||
if (typeof builder !== 'function') {
|
||||
logger.warn(`No CWMP builder found for task '${taskType}'`)
|
||||
@@ -128,7 +133,7 @@ export class CWMPTaskQueue extends EventEmitter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a callback for a task already in the queue (e.g. added via addCWMPTask)
|
||||
* Registers a callback for a task already in the queue
|
||||
*/
|
||||
registerCallback(taskId: string, callback: (result: TaskResult) => Promise<string>): void {
|
||||
this.taskCallbacks.set(taskId, callback)
|
||||
@@ -137,7 +142,7 @@ export class CWMPTaskQueue extends EventEmitter {
|
||||
/**
|
||||
* Adds a CWMP task (internal)
|
||||
*/
|
||||
addCWMPTask(deviceId: DeviceId, type: string, payload: any): CWMPTask {
|
||||
addCWMPTask(deviceId: DeviceId, type: CWMPTaskType, payload: CWMPTaskPayload): CWMPTask {
|
||||
const taskId = this.generateTaskId(deviceId, type)
|
||||
const task: CWMPTask = {
|
||||
id: taskId,
|
||||
@@ -163,7 +168,7 @@ export class CWMPTaskQueue extends EventEmitter {
|
||||
/**
|
||||
* Retrieve and mark a pending task as SENT for a device
|
||||
*/
|
||||
getTaskForDevice(deviceId: DeviceId): { id: string; task: string; props: any } | undefined {
|
||||
getTaskForDevice(deviceId: DeviceId): { id: string; task: CWMPTaskType; props: CWMPTaskPayload } | undefined {
|
||||
const deviceKey = JSON.stringify(deviceId)
|
||||
const queue = this.deviceQueues.get(deviceKey)
|
||||
if (!queue || queue.length === 0) return undefined
|
||||
@@ -172,7 +177,6 @@ export class CWMPTaskQueue extends EventEmitter {
|
||||
const cwmpTask = this.tasks.get(taskId)
|
||||
if (!cwmpTask) return undefined
|
||||
|
||||
// Dequeue
|
||||
queue.shift()
|
||||
|
||||
if (cwmpTask.status === TaskStatus.PENDING) {
|
||||
@@ -182,7 +186,7 @@ export class CWMPTaskQueue extends EventEmitter {
|
||||
return { id: taskId, task: cwmpTask.type, props: cwmpTask.payload }
|
||||
}
|
||||
|
||||
completeTask(taskId: string, result: any): void {
|
||||
completeTask(taskId: string, result: CWMPTaskResultData): void {
|
||||
const task = this.tasks.get(taskId)
|
||||
if (!task) return
|
||||
|
||||
@@ -206,7 +210,7 @@ export class CWMPTaskQueue extends EventEmitter {
|
||||
this.emit('taskFailed', task)
|
||||
}
|
||||
|
||||
async executeTaskCallback(taskId: string, status: ResultStatus, data: any): Promise<string> {
|
||||
async executeTaskCallback(taskId: string, status: ResultStatus, data: CWMPTaskResultData): Promise<string> {
|
||||
const callback = this.taskCallbacks.get(taskId)
|
||||
if (callback) {
|
||||
const result = new TaskResult()
|
||||
@@ -227,11 +231,6 @@ export class CWMPTaskQueue extends EventEmitter {
|
||||
return ''
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ID of the most recently created task for a device (via addTask).
|
||||
* Used by the state machine manager to correlate responses with in-flight tasks
|
||||
* that were created directly by state handlers (not served from the external queue).
|
||||
*/
|
||||
getLastCreatedTaskId(deviceId: DeviceId): string | undefined {
|
||||
return this.lastCreatedTaskIdByDevice.get(JSON.stringify(deviceId))
|
||||
}
|
||||
@@ -239,10 +238,6 @@ export class CWMPTaskQueue extends EventEmitter {
|
||||
clearPendingTasksForDevice(deviceId: DeviceId): void {
|
||||
const deviceKey = JSON.stringify(deviceId)
|
||||
const queue = this.deviceQueues.get(deviceKey) || []
|
||||
// Clear ALL tasks (PENDING and SENT) — after session completion, no task
|
||||
// from the previous session should bleed into the next one. Tasks that were
|
||||
// already SENT (created by state-machine states via addTask()) are ghost
|
||||
// entries that would otherwise be served to the next session.
|
||||
for (const taskId of queue) {
|
||||
this.tasks.delete(taskId)
|
||||
this.taskCallbacks.delete(taskId)
|
||||
|
||||
Reference in new issue
Block a user