Files
Isaac ConnorandClaude Opus 5 c3346da464 fix: release the AMLink session, and refuse to send without a mask key
The control daemon filled its log with

  failed to decode the reply to CoaxialControlIO.control: malformed JSON

on every lightOn, while the light itself still worked. Three faults in this
module compounding, all mine.

Nothing ever logged out. close() was inherited from Dahua_RPC, which only sets
a flag, so every login abandoned a session. The camera counts connections and
reclaims them slowly, and zmcontrol keeps one object for the life of the daemon
while Dahua_RPC re-logins whenever the camera times the session out - the log
shows that happening every half hour. Each one leaked a slot until the camera
answered global.login with "too many connections!", which it is still doing
here hours later.

login() then made a transient failure permanent. It cleared session and
mask_key up front, so a login that failed left the object with no key. Several
Dahua_RPC callers re-login on error and carry on without checking the result,
so every later command was sent unmasked, came back masked, and failed to
base64-decode. The logged error therefore described the symptom two steps
downstream of the cause and never mentioned the session at all.

So:
  - logout() gives the session back, and close() calls it
  - login() releases the previous session before taking another
  - rpc_call refuses to send on the masked channel with no key, and says the
    session is gone rather than emitting an unreadable decode error
  - a login refused for "too many connections" is reported as that

global.logout, with no parameters on the ordinary Request channel, confirmed
against the camera's own web bundle rather than guessed.

Tested: 5 new assertions that a Request with no key returns undef and puts
nothing on the wire, while Login, OutsideCmd and the key exchange - which
legitimately have no key yet - are still sent. Perl suite 15 files / 266
assertions.

Not yet verified against the camera: it is still refusing logins with "too many
connections" from the sessions already leaked, so the logout path could not be
exercised end to end. It needs a retry once the camera has reclaimed them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UvTCzCbvGt8xKQRNCSA7o8
2026-09-11 20:13:03 -05:00
..