- main_state_machine.ts: add to CriticalReadsState GPV:
ProvisioningCode (confirm CPE persisted value), UpTime (detect reboots),
ManagementServer.URL (verify ACS URL), ConnectionRequestUsername/Password
(ensure wake credentials are always available)
- Home.vue: add Last Seen column (device.updatedAt)
- pages_controller.ts: fix modelPaths bug — was never passed to DeviceDetail,
now auto-detected from dataModel and passed as predefined TR-181/TR-098 paths
- device_record.ts: add lastRemoteIp field to store the CPE public IP
from each Inform (X-Forwarded-For or TCP remote IP)
- cwmp_controller.ts: persist lastRemoteIp on every Inform alongside
lastPeriodicInformAt (single operation)
- device_service.ts: sendWakeOnWlan() now tries the original URL first
(may be IPv6), then falls back to a rebuilt URL using lastRemoteIp
(IPv4 public IP) if the ACS server has no IPv6 connectivity
- start/routes.ts: mirror references routes into /internal group so the
DeviceDetail.vue resolve buttons work (were returning 404)
- device.ts: isGpnAllowed() now clears cooldownUntil when transitioning
open→half-open, avoiding stale date in persisted snapshots
Add Device.ManagementServer.PeriodicInformInterval and
Device.ManagementServer.PeriodicInformEnable to the GPV performed
during CriticalReadsState (applies to both TR-181 and TR-098).
Two complementary fixes:
- IdleState: skip root-level parent paths (Device., InternetGatewayDevice.)
when building the GPN queue. Params like Device.RootDataModelVersion have
Device. as parent but sending GPN on Device. with NextLevel=0 returns the
entire TR-181 tree in one response (potentially several MB).
These root-level params are classified via deeper subtree GPN or catalog.
- task_queue: use NextLevel=1 for root paths (1 segment) as a safety net,
NextLevel=0 for deeper paths to get all leaves of that subtree.
TR-069 CPEs routinely open a new TCP connection (new port) for the empty
POST that follows InformResponse. ensureSessionForRemote() only did an
exact remoteKey match, so hasSession() returned false and the state
machine was never continued — GPN discovery was never triggered.
Add IP-only fallback in ensureSessionForRemote(): when the exact
remoteKey is not found, scan RUNNING machines for a unique IP match,
rebind the new remoteKey, and remap sessionMachines to the new sessionId.
Ambiguous cases (NAT/CGNAT with multiple active devices on same IP)
return false safely.
- state_machine_manager: add deviceToRemotes reverse index (Map<deviceKeyJson, Set<remoteKey>>)
so completeMachine/failMachine clean up remoteToDevice in O(1) — fixes unbounded memory leak
- state_machine_manager: getHashIdForRemoteIp() now only matches RUNNING sessions and
returns undefined when multiple candidates exist (NAT/CGNAT safety)
- state_machine_manager: IPv6 bracket normalization in IP comparison
- cwmp_controller: apply IP fallback to TransferComplete and generic response handler
(same new-TCP-port scenario as empty POST after firmware reboot)
- cwmp_logger_middleware: reuse request.cwmp.sessionId instead of recomputing
from socket to avoid formula drift
When a CPE sends an empty POST (polling) on a new TCP connection, the
remoteKey (IP:port) differs from the one registered during Inform.
Add StateMachineManager.getHashIdForRemoteIp() to find the device by
IP alone as a fallback, so cwmpDeviceId is resolved and the exchange
is properly stored in the trace (cwmpMethod: Empty, dashed arrow).
- Home.vue: extend RE_IPV4/RE_IPV6 to match alias-based interface names
like Device.IP.Interface.[BR_LAN].IPv6Address.[GUA_LAN].IPAddress
(used by Sagemcom Livebox 4 with AliasBasedAddressing=1)
- reference_engine.ts: add resetReferenceEngine() to allow config reload
without restart (wassup provider disabled via config/references.json)
- cwmp_middleware: log XML brut recu (bodyPreview 500 chars), contentType,
bodyLength, et message extrait (type, cwmpVersion, cwmpId, messageKeys)
+ log body vide (CPE polling)
- cwmp_logger_middleware: log XML pretty-print complet CPE->ACS et ACS->CPE
en debug, independamment du statut trace
- cwmp_controller: log routage message (messageType), InformResponse,
TransferCompleteResponse, reponses state machine avec longueur
- Ajout de destroy() dans CommandsController (DELETE /commands/:id)
- Routes DELETE /commands/:id dans les groupes /api/v1 et /internal
- Bouton supprimer dans Commands.vue visible uniquement sur les commandes PENDING
- Utilise fetchWithCsrf pour la protection CSRF
After OperationalState, the state machine was marked completed but leftover
tasks in the CWMPTaskQueue (queued by ConfigurationEvaluationState callbacks)
were being served to the CPE on subsequent empty POSTs via the continue()
task fallback path, causing spurious GetParameterNames + SetParameterValues
exchanges after the session officially ended.
Fix: call taskQueue.clearPendingTasksForDevice() in completeMachine() so no
tasks bleed into the next session.
Generated with [Continue](https://continue.dev)
Co-Authored-By: Continue <noreply@continue.dev>
The GPN response (ParameterInfoStruct list) was received but never
processed — organizeParameters() was never called, so discovered
parameters were lost and gpnStatus was never set to 'supported'.
Now on each successful GPN response:
- device.organizeParameters() is called to store leaf params/props
- device.setGpnStatus('supported') marks the device as GPN-capable
This fixes: parameters not showing in UI, wrong dataModel detection,
and PeriodicInformInterval not appearing after SPV.
Generated with [Continue](https://continue.dev)
Co-Authored-By: Continue <noreply@continue.dev>
Previously /^[a-z0-9]+(-[a-z0-9]+)*$/ rejected identifiers like 'BM632w-standard'.
Updated to /^[a-zA-Z0-9]+(-[a-zA-Z0-9]+)*$/ to allow mixed case.
Generated with [Continue](https://continue.dev)
Co-Authored-By: Continue <noreply@continue.dev>
When the CPE responds with a CWMP Fault to GetParameterNames,
mark gpnStatus as 'unsupported' and continue to ConfigurationEvaluationState
instead of hanging the session.
- Add type field to all 7727 entries in tr181-catalog.json (xsd:string/unsignedInt/boolean/int)
- reclassifyDeviceFromCatalog applies catalog type when device type is UNKNOWN
- Export getCatalogType() utility
- Compliance comparison: numeric types compared as Number, boolean as bool, fallback to String
- Handle NaN gracefully (fallback to string comparison)
- Extract computeNextPeriodicInform to app/utils/periodic_inform.ts
- 11 unit tests for the periodic computation logic
- DeviceDetail: 3 date columns on same row (created, last seen, next estimated)
- configurations_v1_controller: use createConfigurationValidator/updateConfigurationValidator
- firmware_catalog_controller: use createFirmwareEntryValidator/updateFirmwareEntryValidator/createMigrationRouteValidator
- Remove dead isModeType/parseMode from mode.ts
- Remove dead NAMESPACES from soap.ts
- Remove unused NAMESPACES export from soap.ts
- Remove unused isModeType/parseMode from mode.ts (device_service does inline parsing)
- Remove flaky model unit tests (covered by integration tests)
- Keep validators (to be wired into controllers later)
- Fix state machine start timing: don't call start() in startStateMachine,
let the CPE's first empty body trigger the state machine via continue().
This ensures GPN is sent at the correct point in the TR-069 session.
- Add unit tests for reclassifyDeviceFromCatalog (10 tests)
- Add unit tests for MainStateMachine transitions and GPN callbacks (11 tests)
- Test both scenarios: Huawei (GPN supported) and Livebox (GPN unsupported + fallback)
- Fix tr069_trace_service test path assertion (config/ → data/)
- Update steering doc with Livebox bootstrap command