This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
from __future__ import annotations
|
||||
import base64,hashlib,ipaddress,json,os,socket,time
|
||||
from pathlib import Path
|
||||
from phase10ds_metadata_protocol import MAX_WIRE,parse_stream
|
||||
ARTIFACT_SIZE=109928;ARTIFACT_SHA256="077307b98e44f566fa1db82b08cd5e71bd56bd9792826fc7254965fa768c0dc7";PORT=9021
|
||||
FIELDS={"active","run_id","target","port","artifact_size","artifact_sha256","output_path","receipt_path","not_before","not_after","one_connection","one_transfer","one_execution","result_receive","four_exact_metadata_reads","possible_atime_effect_acknowledged","app_pkg_read","backup_read","persistent_device_write","installation","autoload","retry","reconnect"}
|
||||
class RunnerError(RuntimeError):pass
|
||||
def load(path):
|
||||
v=json.loads(path.read_text(encoding="utf-8"))
|
||||
if not isinstance(v,dict):raise RunnerError("record object")
|
||||
return v
|
||||
def validate(a,b,now):
|
||||
if set(a)!=FIELDS or set(b)!=FIELDS or a!=b:raise RunnerError("record mismatch")
|
||||
if a["active"] is not True or not a["not_before"]<=now<=a["not_after"]:raise RunnerError("inactive")
|
||||
if a["port"]!=PORT or a["artifact_size"]!=ARTIFACT_SIZE or a["artifact_sha256"]!=ARTIFACT_SHA256:raise RunnerError("identity")
|
||||
if any(a[k] is not True for k in ("one_connection","one_transfer","one_execution","result_receive","four_exact_metadata_reads","possible_atime_effect_acknowledged")):raise RunnerError("authority")
|
||||
if any(a[k] is not False for k in ("app_pkg_read","backup_read","persistent_device_write","installation","autoload","retry","reconnect")):raise RunnerError("forbidden")
|
||||
ipaddress.IPv4Address(a["target"])
|
||||
for key in ("output_path","receipt_path"):
|
||||
p=Path(a[key])
|
||||
if not p.is_absolute() or p.exists():raise RunnerError("exclusive path")
|
||||
return a
|
||||
def exclusive(path,value):
|
||||
with path.open("x",encoding="utf-8",newline="\n") as f:json.dump(value,f,sort_keys=True);f.write("\n");f.flush();os.fsync(f.fileno())
|
||||
def run(artifact,activation,approval,socket_factory=socket.socket,clock=time.monotonic,wall=time.time):
|
||||
p=validate(load(activation),load(approval),wall());raw=artifact.read_bytes()
|
||||
if len(raw)!=ARTIFACT_SIZE or hashlib.sha256(raw).hexdigest()!=ARTIFACT_SHA256:raise RunnerError("artifact")
|
||||
exclusive(Path(p["receipt_path"]),{"artifact_sha256":ARTIFACT_SHA256,"consumed_before_socket":True,"run_id":p["run_id"]});wire=bytearray();s=None;deadline=clock()+20
|
||||
try:
|
||||
s=socket_factory(socket.AF_INET,socket.SOCK_STREAM);s.settimeout(3);s.connect((p["target"],PORT));s.sendall(raw);s.shutdown(socket.SHUT_WR)
|
||||
while True:
|
||||
left=deadline-clock()
|
||||
if left<=0:raise RunnerError("deadline")
|
||||
s.settimeout(left);chunk=s.recv(min(4096,MAX_WIRE-len(wire)))
|
||||
if not chunk:break
|
||||
wire.extend(chunk)
|
||||
if len(wire)==MAX_WIRE:break
|
||||
files=parse_stream(bytes(wire));exclusive(Path(p["output_path"]),{"files":{k:{"base64":base64.b64encode(v).decode(),"sha256":hashlib.sha256(v).hexdigest(),"size":len(v)} for k,v in files.items()},"run_id":p["run_id"]});return files
|
||||
finally:
|
||||
for i in range(len(wire)):wire[i]=0
|
||||
if s is not None:s.close()
|
||||
Reference in New Issue
Block a user