36 lines
1.5 KiB
Python
36 lines
1.5 KiB
Python
#!/usr/bin/env python3
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
"""Offline parser for the bounded Phase-1.0DM snapshot stream."""
|
|
from __future__ import annotations
|
|
from dataclasses import dataclass
|
|
import hashlib,struct
|
|
|
|
MAGIC=b"CHS10DM1";FRAME=struct.Struct("<8sIIiiQQQQq");MAX_BYTES=64*1024*1024
|
|
BEGIN=1;END=2;ERROR=3
|
|
class ProtocolError(ValueError):pass
|
|
@dataclass(frozen=True)
|
|
class SnapshotResult:
|
|
data:bytes
|
|
sha256:str
|
|
size:int
|
|
device:int
|
|
inode:int
|
|
mtime:int
|
|
|
|
def _frame(data:bytes):
|
|
if len(data)!=64:raise ProtocolError("frame size")
|
|
values=FRAME.unpack(data)
|
|
if values[0]!=MAGIC or values[1]!=1:raise ProtocolError("frame identity")
|
|
return values
|
|
|
|
def parse_stream(raw:bytes)->SnapshotResult:
|
|
if not isinstance(raw,bytes) or len(raw)<128:raise ProtocolError("stream truncated")
|
|
begin=_frame(raw[:64]);kind,status,saved,size,sent,dev,ino,mtime=begin[2:]
|
|
if kind==ERROR:raise ProtocolError(f"target error {status}")
|
|
if kind!=BEGIN or status!=0 or saved!=0 or sent!=0 or not 0<size<=MAX_BYTES:raise ProtocolError("begin invalid")
|
|
if len(raw)!=64+size+64:raise ProtocolError("stream length")
|
|
payload=raw[64:64+size];end=_frame(raw[64+size:]);ekind,estatus,esaved,esize,esent,edev,eino,emtime=end[2:]
|
|
if ekind!=END or estatus!=0 or esaved!=0 or esize!=size or esent!=size or (edev,eino,emtime)!=(dev,ino,mtime):raise ProtocolError("terminal invalid")
|
|
if payload[:16]!=b"SQLite format 3\x00":raise ProtocolError("not sqlite")
|
|
return SnapshotResult(payload,hashlib.sha256(payload).hexdigest(),size,dev,ino,mtime)
|