#!/usr/bin/env python3 # SPDX-License-Identifier: GPL-3.0-or-later from __future__ import annotations import struct MAGIC=b"CHM10DS1";FRAME=struct.Struct("<8sIIiiIIQ24s");NAMES=("app.pbm","app.json","app.xml","app.crc");MAX_FILE=4096;MAX_WIRE=16768 class ProtocolError(ValueError):pass def frame(raw): if len(raw)!=64:raise ProtocolError("frame size") v=FRAME.unpack(raw) if v[0]!=MAGIC or v[1]!=1 or v[8]!=bytes(24):raise ProtocolError("frame identity") return v def parse_stream(raw:bytes)->dict[str,bytes]: if not isinstance(raw,bytes) or not 128<=len(raw)<=MAX_WIRE:raise ProtocolError("stream length") first=frame(raw[:64]);kind,status,saved,index,length,total=first[2:8] if kind==4:raise ProtocolError(f"target error {status}:{saved}") if (kind,status,saved,index,length,total)!=(1,0,0,4,0,0):raise ProtocolError("header invalid") offset=64;files={};transferred=0 for expected,name in enumerate(NAMES): if offset+64>len(raw):raise ProtocolError("file frame truncated") value=frame(raw[offset:offset+64]);offset+=64;kind,status,saved,index,length,total=value[2:8] if kind==4:raise ProtocolError(f"target error {status}:{saved}") if kind!=2 or status or saved or index!=expected or length>MAX_FILE or total!=transferred:raise ProtocolError("file frame invalid") if offset+length>len(raw):raise ProtocolError("file truncated") files[name]=raw[offset:offset+length];offset+=length;transferred+=length if offset+64!=len(raw):raise ProtocolError("terminal position") value=frame(raw[offset:]);kind,status,saved,index,length,total=value[2:8] if (kind,status,saved,index,length,total)!=(3,0,0,4,0,transferred):raise ProtocolError("terminal invalid") return files