mirror of
https://git.k-space.ee/arti/vt1200.git
synced 2026-11-01 12:14:56 +02:00
feat(badapple): UDP network loader and sender
This commit is contained in:
1 parent
116356a975
commit
8a1cd1bfb1
8 files changed
+407
-18
No files matched your search
@@ -14,6 +14,12 @@ DOSBOX_CONF="$ROOT/harness/sdk-scratch/dosbox-bench.conf"
|
||||
|
||||
TEXT=018C0000
|
||||
BSS=01C00000
|
||||
NET=${NET:-0}
|
||||
if [ "$NET" = 1 ]; then
|
||||
NAME=player-net
|
||||
else
|
||||
NAME=player
|
||||
fi
|
||||
|
||||
mkdir -p "$WORK" "$OUT"
|
||||
|
||||
@@ -25,6 +31,8 @@ fi
|
||||
cp "$DIR/main.c" "$WORK/MAIN.C"
|
||||
cp "$DIR/movie.c" "$WORK/MOVIE.C"
|
||||
cp "$DIR/movie.h" "$WORK/MOVIE.H"
|
||||
cp "$DIR/romapi.h" "$WORK/ROMAPI.H"
|
||||
printf '#define NET_DEFAULT %s\n' "$NET" > "$WORK/MODECFG.H"
|
||||
cp "$DIR/fill.asm" "$WORK/FILL.ASM"
|
||||
cp "$DIR/entry.asm" "$WORK/ENTRY.ASM"
|
||||
|
||||
@@ -105,7 +113,8 @@ sed 's/\r$//' "$WORK/MOVIE.LST" > "$OUT/movie.lst" 2>/dev/null || true
|
||||
sed 's/\r$//' "$WORK/FILL.LST" > "$OUT/fill.lst" 2>/dev/null || true
|
||||
|
||||
python3 "$ROOT/harness/hello-world/cofextract.py" \
|
||||
"$WORK/PLAYER.OUT" "$OUT/player.bin" > "$OUT/cofextract.txt"
|
||||
"$WORK/PLAYER.OUT" "$OUT/$NAME.bin" > "$OUT/cofextract.txt"
|
||||
cp "$WORK/PLAYER.OUT" "$OUT/$NAME.coff"
|
||||
|
||||
ENTRY_PTR=$(sed 's/\r$//' "$WORK/PLAYER.MAP" \
|
||||
| awk '/ENTRY POINT SYMBOL/ { print $NF; exit }')
|
||||
@@ -113,8 +122,8 @@ if [ -z "$ENTRY_PTR" ]; then
|
||||
echo "build.sh: no entry point in PLAYER.map" >&2
|
||||
exit 1
|
||||
fi
|
||||
printf 'ENTRY_PTR=0x%s\n' "$ENTRY_PTR" > "$OUT/player.env"
|
||||
printf 'ENTRY_PTR=0x%s\n' "$ENTRY_PTR" > "$OUT/$NAME.env"
|
||||
|
||||
echo "build.sh: $OUT/player.bin ($(wc -c < "$OUT/player.bin") bytes)"
|
||||
echo "build.sh: $OUT/$NAME.bin ($(wc -c < "$OUT/$NAME.bin") bytes)"
|
||||
grep -E "ENTRY POINT|^\.(text|cinit|bss)" "$OUT/player.map" | head -8
|
||||
echo "build.sh: entry bit 0x$ENTRY_PTR"
|
||||
@@ -1,7 +1,12 @@
|
||||
; entry.asm -- payload entry for the Bad Apple player.
|
||||
; entry.asm -- payload entry and ROM socket/allocator thunks.
|
||||
;
|
||||
; The payload is entered by a debugger/rdebug jump and never returns. Set the
|
||||
; C stack, call main, and spin if main ever returns.
|
||||
;
|
||||
; The firmware's wrappers end in RETS 2 (they pop the return address and two
|
||||
; argument words, and restore A14 from a caller-pushed slot). A plain calla
|
||||
; would leave SP off by one word; these thunks save/restore SP and A14 exactly
|
||||
; as harness/udpstream/entry.asm does.
|
||||
|
||||
.text
|
||||
.globl _start
|
||||
@@ -14,5 +19,61 @@ _start:
|
||||
spin:
|
||||
jruc spin
|
||||
|
||||
.globl _socket_
|
||||
_socket_:
|
||||
move sp, @_sys_sp, 1
|
||||
move a14, @_sys_a14, 1
|
||||
calla 3F5C9A00h
|
||||
move @_sys_sp, sp, 1
|
||||
move @_sys_a14, a14, 1
|
||||
rets 2
|
||||
|
||||
.globl _bind_
|
||||
_bind_:
|
||||
move sp, @_sys_sp, 1
|
||||
move a14, @_sys_a14, 1
|
||||
calla 3F5CAA80h
|
||||
move @_sys_sp, sp, 1
|
||||
move @_sys_a14, a14, 1
|
||||
rets 2
|
||||
|
||||
.globl _recvfrom_
|
||||
_recvfrom_:
|
||||
move sp, @_sys_sp, 1
|
||||
move a14, @_sys_a14, 1
|
||||
calla 3F5D7D50h
|
||||
move @_sys_sp, sp, 1
|
||||
move @_sys_a14, a14, 1
|
||||
rets 2
|
||||
|
||||
.globl _close_
|
||||
_close_:
|
||||
move sp, @_sys_sp, 1
|
||||
move a14, @_sys_a14, 1
|
||||
calla 3F5E8930h
|
||||
move @_sys_sp, sp, 1
|
||||
move @_sys_a14, a14, 1
|
||||
rets 2
|
||||
|
||||
.globl _malloc_
|
||||
_malloc_:
|
||||
move sp, @_sys_sp, 1
|
||||
move a14, @_sys_a14, 1
|
||||
calla 3F701230h
|
||||
move @_sys_sp, sp, 1
|
||||
move @_sys_a14, a14, 1
|
||||
rets 2
|
||||
|
||||
.globl _delay_
|
||||
_delay_:
|
||||
move sp, @_sys_sp, 1
|
||||
move a14, @_sys_a14, 1
|
||||
calla 3F833A20h
|
||||
move @_sys_sp, sp, 1
|
||||
move @_sys_a14, a14, 1
|
||||
rets 2
|
||||
|
||||
.bss _cstack, 32768, 16 ; 32768 bits = 4096 bytes
|
||||
.bss _sys_sp, 32, 16
|
||||
.bss _sys_a14, 32, 16
|
||||
.end
|
||||
+109
-14
@@ -1,5 +1,15 @@
|
||||
/* main.c -- Bad Apple payload, one-frame mode and (later) playback. */
|
||||
/* main.c -- Bad Apple payload: network receive, then tile playback. */
|
||||
#include "movie.h"
|
||||
#include "romapi.h"
|
||||
#include "modecfg.h"
|
||||
|
||||
#ifndef NET_DEFAULT
|
||||
#define NET_DEFAULT 0
|
||||
#endif
|
||||
|
||||
/* The 30 s stream's exact size, from data/manifest.json. The real loader
|
||||
* allocates this before the first datagram; a different clip needs a rebuild. */
|
||||
#define NET_LEN 2401942
|
||||
|
||||
#define MOVIE_BASE 0x00080000
|
||||
#define MOVIE_MAX 0x00308000
|
||||
@@ -13,6 +23,9 @@
|
||||
#define CTRL_MOVIELEN (MARK + 0x30 * 8)
|
||||
#define CTRL_WAIT (MARK + 0x34 * 8)
|
||||
#define CTRL_READY (MARK + 0x38 * 8)
|
||||
#define CTRL_NET (MARK + 0x3C * 8)
|
||||
|
||||
#define NET_PORT 7001
|
||||
|
||||
extern void fill_run_();
|
||||
extern void fill_init_();
|
||||
@@ -50,6 +63,69 @@ void *ctx;
|
||||
fill_rect(x, y, w, h, color);
|
||||
}
|
||||
|
||||
static unsigned char rxbuf[1600];
|
||||
static unsigned char sa[16];
|
||||
static unsigned char from[16];
|
||||
static int fromlen;
|
||||
|
||||
/* Receive movie.bin over the OS's UDP stack into a malloc'd bit buffer.
|
||||
* Packet: u32 offset, u16 length, u16 seq, then bytes; length 0 is EOF. */
|
||||
static unsigned int receive_movie(expected)
|
||||
unsigned int expected;
|
||||
{
|
||||
unsigned int ptr;
|
||||
unsigned int offset;
|
||||
unsigned int length;
|
||||
unsigned int total;
|
||||
unsigned int i;
|
||||
unsigned char *dst;
|
||||
int fd;
|
||||
int n;
|
||||
|
||||
ptr = malloc_(expected);
|
||||
if (ptr == 0) {
|
||||
return 0;
|
||||
}
|
||||
fd = socket_(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if (fd < 0) {
|
||||
return 0;
|
||||
}
|
||||
sa_any(sa, NET_PORT);
|
||||
if (bind_(fd, sa, SOCKADDR_LEN) < 0) {
|
||||
close_(fd);
|
||||
return 0;
|
||||
}
|
||||
total = 0;
|
||||
dst = (unsigned char *)ptr;
|
||||
for (;;) {
|
||||
fromlen = SOCKADDR_LEN;
|
||||
n = recvfrom_(fd, rxbuf, (int)sizeof(rxbuf), 0, from, &fromlen);
|
||||
if (n < 8) {
|
||||
continue;
|
||||
}
|
||||
offset = (unsigned int)rxbuf[0]
|
||||
| ((unsigned int)rxbuf[1] << 8)
|
||||
| ((unsigned int)rxbuf[2] << 16)
|
||||
| ((unsigned int)rxbuf[3] << 24);
|
||||
length = (unsigned int)rxbuf[4] | ((unsigned int)rxbuf[5] << 8);
|
||||
if (length == 0) {
|
||||
break;
|
||||
}
|
||||
if (offset + length > expected) {
|
||||
continue;
|
||||
}
|
||||
for (i = 0; i < length; i++) {
|
||||
dst[offset + i] = rxbuf[8 + i];
|
||||
}
|
||||
total += length;
|
||||
if (total >= expected) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
close_(fd);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
/* Poll the display interrupt pending bit. Interrupts are disabled; this is
|
||||
* a status bit, not an ISR. A missing DPYINT only costs a bounded spin. */
|
||||
static void wait_vsync()
|
||||
@@ -78,18 +154,9 @@ int main()
|
||||
unsigned int pace;
|
||||
unsigned int movie_base;
|
||||
unsigned int movie_len;
|
||||
unsigned int net;
|
||||
int rc;
|
||||
|
||||
vid_dint_();
|
||||
fill_init_();
|
||||
|
||||
/* Display interrupt at line 1: our only timebase. Interrupts stay off;
|
||||
* wait_vsync() polls the pending bit. */
|
||||
*(unsigned short *)0xC00000A0 = 1;
|
||||
|
||||
/* Start from white; the OS left its own screen behind. */
|
||||
fill_rect(0, 0, 1280, 1024, 0);
|
||||
|
||||
movie_base = *(unsigned int *)CTRL_MOVIE;
|
||||
movie_len = *(unsigned int *)CTRL_MOVIELEN;
|
||||
if (movie_base == 0) {
|
||||
@@ -99,12 +166,40 @@ int main()
|
||||
movie_len = MOVIE_MAX;
|
||||
}
|
||||
|
||||
if (*(unsigned int *)CTRL_WAIT != 0) {
|
||||
*(unsigned int *)(MARK + 0x10 * 8) = 0x10; /* takeover, waiting for data */
|
||||
while (*(unsigned int *)CTRL_READY == 0) {
|
||||
net = *(unsigned int *)CTRL_NET;
|
||||
if (net == 0 && NET_DEFAULT) {
|
||||
net = 1;
|
||||
}
|
||||
if (net != 0) {
|
||||
/* Real delivery: the OS stack is still alive for the receive; the
|
||||
* payload owns the machine only after the last byte arrives. */
|
||||
movie_len = NET_LEN;
|
||||
*(unsigned int *)(MARK + 0x10 * 8) = 0x20;
|
||||
movie_base = receive_movie(movie_len);
|
||||
if (movie_base == 0) {
|
||||
*(unsigned int *)MARK = 0xBAD00003;
|
||||
for (;;) {
|
||||
}
|
||||
}
|
||||
vid_dint_();
|
||||
} else {
|
||||
vid_dint_();
|
||||
if (*(unsigned int *)CTRL_WAIT != 0) {
|
||||
*(unsigned int *)(MARK + 0x10 * 8) = 0x10;
|
||||
while (*(unsigned int *)CTRL_READY == 0) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fill_init_();
|
||||
|
||||
/* Display interrupt at line 1: our only timebase. Interrupts stay off;
|
||||
* wait_vsync() polls the pending bit. */
|
||||
*(unsigned short *)0xC00000A0 = 1;
|
||||
|
||||
/* Start from white; the OS left its own screen behind. */
|
||||
fill_rect(0, 0, 1280, 1024, 0);
|
||||
|
||||
rc = movie_open((unsigned char *)movie_base, movie_len, &header);
|
||||
if (rc != 0) {
|
||||
*(unsigned int *)MARK = 0xBAD00001;
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* romapi.h -- the VT1200 firmware entry points a C payload may call.
|
||||
*
|
||||
* Every address here is a TMS34010 BIT address (= Ghidra byte address x 8).
|
||||
* The evidence for each is dwt/notes/TCPIP.md (sockets) and dwt/notes/HEAP.md
|
||||
* (allocator); the ones marked "verified live" have been called from a payload
|
||||
* on real hardware, not just read out of the ROM.
|
||||
*
|
||||
* K&R C only. This compiler rejects ANSI prototypes (`int f(int a)` is a
|
||||
* syntax error), so the declarations below are old-style: the parameter lists
|
||||
* live in the comments, and the compiler checks nothing but the name. That is
|
||||
* the price of using the 1987 toolchain; keep the comments honest.
|
||||
*
|
||||
* Calling convention: the C compiler and the firmware agree -- arguments are
|
||||
* widened to 32 bits and pushed upward on the program stack (A14), first
|
||||
* argument last, and the result comes back in A8. The thunks in entry.asm
|
||||
* forward with `calla`, so the caller's already-pushed arguments pass through
|
||||
* untouched and only A8 has to survive.
|
||||
*/
|
||||
#ifndef ROMAPI_H
|
||||
#define ROMAPI_H
|
||||
|
||||
/* ---- ROM entry points -------------------------------------------------- */
|
||||
#define ROM_SOCKET 0x3F5C9A00 /* Ghidra 07EB9340 verified live */
|
||||
#define ROM_BIND 0x3F5CAA80 /* Ghidra 07EB9550 BOOTP caller */
|
||||
#define ROM_LISTEN 0x3F5CBCD0 /* Ghidra 07EB979A inetd caller */
|
||||
#define ROM_ACCEPT 0x3F5CCB40 /* Ghidra 07EB9968 inetd caller */
|
||||
#define ROM_SENDTO 0x3F5D3D70 /* Ghidra 07EBA7AE verified live */
|
||||
#define ROM_RECVFROM 0x3F5D7D50 /* Ghidra 07EBAFAA verified live */
|
||||
#define ROM_READ 0x3F5D8E70 /* Ghidra 07EBB1CE rdebug / Xlib */
|
||||
#define ROM_CLOSE 0x3F5E8930 /* Ghidra 07EBD126 verified live */
|
||||
#define ROM_SETSOCKOPT 0x3F5DD260 /* Ghidra 07EBBA4C BOOTP / Xlib */
|
||||
#define ROM_DELAY 0x3F833A20 /* Ghidra 07F66744 sleep(ms), telnet caller */
|
||||
#define ROM_SELECT 0x3F871CE0 /* Ghidra 07F0E39C Xlib caller, candidate */
|
||||
#define ROM_MALLOC 0x3F701230 /* Ghidra 07EE0246 verified live */
|
||||
#define ROM_FREE 0x3F702440 /* Ghidra 07EE0488 allocator */
|
||||
#define ROM_CREATE_PROC 0x3F706A40 /* Ghidra 07EE0D48 GUI work */
|
||||
|
||||
/* ---- thunks implemented in entry.asm ----------------------------------- */
|
||||
/*
|
||||
* int socket_(domain, type, protocol) -> fd, or -1
|
||||
* int bind_(fd, sockaddr *, len) -> 0, or -1
|
||||
* int recvfrom_(fd, buf, len, flags, from *, fromlen *)
|
||||
* -> bytes, or -1
|
||||
* int close_(fd) -> 0, or -1
|
||||
* unsigned int malloc_(bytes) -> BIT pointer, or 0
|
||||
* int free_(bit pointer) -> 0
|
||||
*
|
||||
* `from` is a 16-byte sockaddr the stack fills in; `fromlen` points to an
|
||||
* int that must be 16 on entry (the call writes back the real length).
|
||||
*/
|
||||
int socket_();
|
||||
int bind_();
|
||||
int recvfrom_();
|
||||
int close_();
|
||||
int listen_();
|
||||
int accept_();
|
||||
int read_();
|
||||
int select_();
|
||||
int delay_();
|
||||
unsigned int malloc_();
|
||||
int free_();
|
||||
|
||||
/* ---- socket constants -------------------------------------------------- */
|
||||
#define AF_INET 2
|
||||
#define SOCK_DGRAM 2
|
||||
#define SOCK_STREAM 1
|
||||
#define IPPROTO_UDP 0
|
||||
|
||||
#define SOCKADDR_LEN 16
|
||||
/*
|
||||
* struct sockaddr, 16 bytes, NETWORK byte order:
|
||||
* +0 u16 family (AF_INET = 2 -> bytes 00 02)
|
||||
* +2 u16 port (high byte first, e.g. 7000 = 0x1B58 -> 1B 58)
|
||||
* +4 u32 addr (0 = INADDR_ANY)
|
||||
* +8 u8 zero[8]
|
||||
*/
|
||||
|
||||
/* Fill sa (an unsigned char[16]) with AF_INET, `port`, and INADDR_ANY.
|
||||
* `port` is in host order so callers can write 7000 rather than 0x1B58. */
|
||||
static void sa_any(sa, port)
|
||||
unsigned char *sa;
|
||||
unsigned int port;
|
||||
{
|
||||
sa[0] = 0; sa[1] = AF_INET;
|
||||
sa[2] = (port >> 8) & 0xFF;
|
||||
sa[3] = port & 0xFF;
|
||||
sa[4] = 0; sa[5] = 0; sa[6] = 0; sa[7] = 0;
|
||||
sa[8] = 0; sa[9] = 0; sa[10] = 0; sa[11] = 0;
|
||||
sa[12] = 0; sa[13] = 0; sa[14] = 0; sa[15] = 0;
|
||||
}
|
||||
|
||||
#endif /* ROMAPI_H */
|
||||
Executable
+52
@@ -0,0 +1,52 @@
|
||||
#!/bin/sh
|
||||
# Deliver the network-build payload over rdebug, then send movie.bin over UDP.
|
||||
#
|
||||
# tools/load.sh --host 192.168.99.2 --movie data/movie.bin
|
||||
#
|
||||
# The terminal must be booted and answering ping. The payload calls the OS
|
||||
# socket API, receives into a malloc'd buffer, then disables interrupts and
|
||||
# plays. Recovery after that point is a power cycle.
|
||||
set -eu
|
||||
|
||||
HERE=$(cd "$(dirname "$0")" && pwd)
|
||||
ROOT=$(cd "$HERE/../.." && pwd)
|
||||
HOST=192.168.99.2
|
||||
PORT=7001
|
||||
MOVIE=$ROOT/bad-apple/data/movie.bin
|
||||
WAIT=180
|
||||
SETTLE=3
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--host) HOST=$2; shift 2 ;;
|
||||
--port) PORT=$2; shift 2 ;;
|
||||
--movie) MOVIE=$2; shift 2 ;;
|
||||
--wait) WAIT=$2; shift 2 ;;
|
||||
--settle) SETTLE=$2; shift 2 ;;
|
||||
-h|--help) sed -n '2,12p' "$0"; exit 0 ;;
|
||||
*) echo "load.sh: unknown option $1" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ ! -f "$MOVIE" ]; then
|
||||
echo "load.sh: no movie at $MOVIE" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
start=$(date +%s)
|
||||
while ! ping -c1 -W1 "$HOST" >/dev/null 2>&1; do
|
||||
if [ $(( $(date +%s) - start )) -ge "$WAIT" ]; then
|
||||
echo "load.sh: $HOST did not answer within ${WAIT}s" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
echo "load.sh: $HOST answers"
|
||||
|
||||
python3 "$ROOT/harness/badapple/rdebug_load.py" \
|
||||
--host "$HOST" \
|
||||
--image "$ROOT/bad-apple/player/build/player-net.bin" \
|
||||
--env "$ROOT/bad-apple/player/build/player-net.env"
|
||||
|
||||
sleep "$SETTLE"
|
||||
python3 "$HERE/send_movie.py" --host "$HOST" --port "$PORT" --movie "$MOVIE"
|
||||
echo "load.sh: sent; the screen is the evidence. Power-cycle to recover."
|
||||
@@ -132,6 +132,7 @@ local function load_and_enter()
|
||||
prog:write_u32(MARK + 0x30 * 8, #movie)
|
||||
prog:write_u32(MARK + 0x34 * 8, WAIT)
|
||||
prog:write_u32(MARK + 0x38 * 8, 0)
|
||||
prog:write_u32(MARK + 0x3C * 8, 0) -- debugger path, never network
|
||||
dbg:command("bpset " .. hex(ENTRY_PC) .. ",,{ pc = " .. hex(entry) .. " ; g }")
|
||||
if WAIT == 0 then
|
||||
write_bytes(movie, MOVIE)
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Send movie.bin to the payload's UDP receiver (port 7001).
|
||||
|
||||
Packet: u32 offset, u16 length, u16 seq, u16 crc16, then length bytes.
|
||||
A zero-length packet ends the load. This uses the VT1200 OS stack while it
|
||||
is still alive; the payload disables interrupts only after the last byte.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import socket
|
||||
import struct
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def crc16(data: bytes) -> int:
|
||||
return sum(data) & 0xFFFF
|
||||
|
||||
|
||||
def pack_packet(offset: int, seq: int, body: bytes) -> bytes:
|
||||
return struct.pack("<IHHH", offset, len(body), seq, crc16(body)) + body
|
||||
|
||||
|
||||
def parse_ack(data: bytes) -> int:
|
||||
if len(data) < 8 or data[:4] != b"BAK1":
|
||||
raise ValueError("not a BAK1 ack")
|
||||
return struct.unpack_from("<I", data, 4)[0]
|
||||
|
||||
|
||||
def send_movie(host: str, port: int, path: str, chunk: int = 1450,
|
||||
delay: float = 0.0005) -> int:
|
||||
data = Path(path).read_bytes()
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
try:
|
||||
offset = 0
|
||||
seq = 0
|
||||
while offset < len(data):
|
||||
part = data[offset:offset + chunk]
|
||||
sock.sendto(pack_packet(offset, seq, part), (host, port))
|
||||
offset += len(part)
|
||||
seq += 1
|
||||
if delay:
|
||||
time.sleep(delay)
|
||||
sock.sendto(pack_packet(len(data), seq, b""), (host, port))
|
||||
finally:
|
||||
sock.close()
|
||||
print("sent %d bytes in %d packets" % (len(data), seq + 1))
|
||||
return 0
|
||||
|
||||
|
||||
def main(argv=None) -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--host", default="192.168.99.2")
|
||||
parser.add_argument("--port", type=int, default=7001)
|
||||
parser.add_argument("--movie", required=True)
|
||||
parser.add_argument("--chunk", type=int, default=1450)
|
||||
parser.add_argument("--delay", type=float, default=0.0005)
|
||||
args = parser.parse_args(argv)
|
||||
return send_movie(args.host, args.port, args.movie, args.chunk, args.delay)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,13 @@
|
||||
from tools.send_movie import pack_packet, parse_ack
|
||||
|
||||
|
||||
def test_pack_packet_header():
|
||||
pkt = pack_packet(0x1234, 1, b"abc")
|
||||
assert pkt[0:4] == (0x1234).to_bytes(4, "little")
|
||||
assert pkt[4:6] == (3).to_bytes(2, "little")
|
||||
assert pkt[6:8] == (1).to_bytes(2, "little")
|
||||
assert pkt[10:] == b"abc"
|
||||
|
||||
|
||||
def test_parse_ack():
|
||||
assert parse_ack(b"BAK1" + (0x1000).to_bytes(4, "little")) == 0x1000
|
||||
Reference in new issue
Block a user