Skip to content

Commit 30b7693

Browse files
committed
review: refresh upgrade docs to validated state; assert appendData params in test
Addresses Qodo review on #3: - upgrade_firmware docstring + confirm ValueError no longer claim the path is byte-unproven / never run on hardware (it is validated end-to-end on a GK7205V510); docstring now states the {"length":N}+binary contract and the prepare/appendData/execute state machine. - test_upgrade_streams_file_in_chunks now records each appendData params and the real trailing-binary length and asserts params == {"length": N} with N equal to the payload for every chunk. FakeDHIPServer exposes the binary payload (__data__) so handlers can verify it. A revert to {"Offset","Length"} now fails the test (previously it passed).
1 parent 183cc45 commit 30b7693

3 files changed

Lines changed: 30 additions & 12 deletions

File tree

‎dahua/client.py‎

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -472,18 +472,23 @@ def upgrade_firmware(self, path: str, *, confirm: bool = False,
472472
to). *path* is the vendor upgrade package (a Dahua "zzip" — see
473473
``tools/zzip.py`` in the zenointel project), not a raw partition image.
474474
475+
``appendData`` is sent params ``{"length": len(chunk)}`` (lowercase) with
476+
the chunk as the trailing binary payload — the handler rejects the call
477+
with ``400 "param error"`` unless ``params.length`` equals the actual
478+
payload length. State machine: ``prepare`` (0→2) → ``appendData`` (2/4,
479+
repeatable) → ``execute`` (4→0), with a ~60 s idle timeout.
480+
475481
.. danger::
476-
This can permanently **brick** the device. The orchestration is
477-
validated against a mock and the method names are reversed from
478-
firmware, but the JSON param names are not byte-proven and this has
479-
deliberately never been run on hardware. Probe :meth:`firmware_state`
480-
first, keep a UART/backup recovery path ready, and pass
481-
``confirm=True`` to proceed.
482+
This can permanently **brick** the device. The flow is validated
483+
end-to-end on hardware (a GK7205V510 flashed an OpenIPC package and
484+
rebooted into it), but any wrong package for the target still bricks
485+
it. Probe :meth:`firmware_state` first, keep a UART/backup recovery
486+
path ready, and pass ``confirm=True`` to proceed.
482487
"""
483488
if not confirm:
484489
raise ValueError(
485-
"upgrade_firmware can brick the device and is untested on "
486-
"hardware; pass confirm=True to proceed")
490+
"upgrade_firmware writes device partitions and can brick the "
491+
"device; pass confirm=True to proceed")
487492
import os
488493
total = os.path.getsize(path)
489494
self.call(const.UPGRADER_PREPARE, {"Type": fw_type})

‎tests/fake_server.py‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,12 @@ def _read_frame(self, sock):
5757
_, magic, sess, rid, pkg, idx, mlen, dlen = struct.unpack(HEADER_FMT, hdr)
5858
assert magic == DHIP_MAGIC
5959
body = self._recv(sock, pkg) if pkg else b""
60-
return json.loads(body[:mlen].decode()), sess
60+
obj = json.loads(body[:mlen].decode())
61+
if dlen:
62+
# expose the trailing binary payload so handlers can verify a request
63+
# whose params must describe it (e.g. upgrader.appendData length).
64+
obj["__data__"] = body[mlen:mlen + dlen]
65+
return obj, sess
6166

6267
def _send(self, sock, obj, data=b"", index=0):
6368
body = json.dumps(obj, separators=(",", ":")).encode()

‎tests/test_dahua.py‎

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -316,18 +316,20 @@ def test_upgrade_requires_confirm(self):
316316
cam.upgrade_firmware(__file__) # no confirm=True
317317

318318
def test_upgrade_streams_file_in_chunks(self):
319-
received = bytearray()
319+
append_params = []
320320

321321
def send(req):
322+
# The device rejects appendData unless params is exactly
323+
# {"length": <payload length>}. Record params + the actual binary
324+
# payload length so a regression to {"Offset","Length"} is caught.
325+
append_params.append((req.get("params"), len(req.get("__data__", b""))))
322326
return {"result": True}
323327

324328
handlers = {
325329
"upgrader.prepare": lambda r: {"result": True},
326330
"upgrader.appendData": send,
327331
"upgrader.execute": lambda r: {"result": True},
328332
}
329-
# The fake server doesn't expose binary bodies to handlers, so assert
330-
# the orchestration (start/send*/execute) and chunk count instead.
331333
with FakeDHIPServer(handlers) as srv:
332334
with DahuaClient("127.0.0.1", srv.port) as cam:
333335
cam.login(USER, PASS, keep_alive=False)
@@ -344,6 +346,12 @@ def send(req):
344346
self.assertIn("upgrader.prepare", srv.received)
345347
self.assertIn("upgrader.execute", srv.received)
346348
self.assertEqual(seen[-1], (10000, 10000))
349+
# params must be exactly {"length": N} with N == the real payload
350+
# length for every chunk (4096, 4096, 1808) — nothing else.
351+
self.assertEqual([p for p, _ in append_params],
352+
[{"length": 4096}, {"length": 4096}, {"length": 1808}])
353+
self.assertEqual([(p["length"], n) for p, n in append_params],
354+
[(4096, 4096), (4096, 4096), (1808, 1808)])
347355

348356

349357
class TestDiscovery(unittest.TestCase):

0 commit comments

Comments
 (0)