; tftpc.asm -- minimal TFTP read client built on the firmware's socket API. ; ; The firmware's own tftp_open() calls a resolver that does not return when ; called from the rdebug process (dwt/notes/TCPIP.md), so this payload speaks ; RFC 783 directly: RRQ, DATA/ACK, 512-byte blocks, one file. ; ; socket = 3F5C9A00h socket(2,2,0) -> fd ; sendto = 3F5D3D70h sendto(fd, buf, len, flags, to, tolen) -> n ; recvfrom = 3F5D7D50h recvfrom(fd, buf, len, flags, from, fromlen) -> n ; close = 3F5E8930h close(fd) ; malloc = 3F701230h malloc(bytes) -> bit pointer ; ; The transfer is 600 bytes, so DATA 1 is 512 (full) and DATA 2 is 88 ; (short); the short block ends the transfer. ACKs go to the sockaddr that ; recvfrom reports, not to a hard-coded port. ; ; Evidence cells (32-bit, bit addresses): ; 0187C000 stage (1 malloc, 2 socket, 3 receiving, 4 closed, 0FFh failed) ; 0187C020 fd ; 0187C040 destination buffer (malloc) ; 0187C060 total bytes fetched ; 0187C080 last recvfrom length ; 0187C0A0 last DATA block word as read from the wire ; 0187C0C0 marker 05A5A5A5h when the fetch completed ; 0187C200 entry SP ; 0187C220 entry A14 ; ; Scratch (fixed bit addresses, above this image): ; 01875000 argument area ; 01876000 receive buffer (2048 bytes) ; 0187A000 recvfrom source sockaddr ; 0187A080 recvfrom sockaddr length ; 0187A100 ACK packet (4 bytes) .text _start: setf 16, 0, 0 setf 32, 0, 1 mmtm sp, a0,a1,a2,a3,a4,a5,a6,a7 move sp, @0187C200h, 1 move a14, @0187C220h, 1 movi 1, a0, L move a0, @0187C000h, 1 ; ---- buf = malloc(010000h) ------------------------------------ move a14, -*sp, 1 movi 01875000h, a14, L movi 010000h, a0, L move a0, *a14+, 1 calla 3F701230h move @0187C200h, sp, 1 move @0187C220h, a14, 1 setf 16, 0, 0 setf 32, 0, 1 move a8, @0187C040h, 1 move a8, a11 ; dest cursor (callee-saved) move a8, a8 jreq fail movi 2, a0, L move a0, @0187C000h, 1 ; ---- fd = socket(2,2,0) -------------------------------------- movi 01875000h, a14, L movi 0, a0, L move a0, *a14+, 1 movi 2, a0, L move a0, *a14+, 1 movi 2, a0, L move a0, *a14+, 1 calla 3F5C9A00h move @0187C200h, sp, 1 move @0187C220h, a14, 1 setf 16, 0, 0 setf 32, 0, 1 move a8, @0187C020h, 1 move a8, a9 ; fd (callee-saved) move a8, a8 jrlt fail ; ---- sendto(fd, RRQ, 28, 0, SADDR, 16) ------------------------ movi 01875000h, a14, L movi 16, a0, L move a0, *a14+, 1 movi SADDR, a0, L move a0, *a14+, 1 movi 0, a0, L move a0, *a14+, 1 movi 28, a0, L move a0, *a14+, 1 movi RRQ, a0, L move a0, *a14+, 1 move a9, *a14+, 1 calla 3F5D3D70h move @0187C200h, sp, 1 move @0187C220h, a14, 1 setf 16, 0, 0 setf 32, 0, 1 move a8, a8 jrlt fail movi 3, a0, L move a0, @0187C000h, 1 sub a0, a0 move a0, @0187C060h, 1 ; total = 0 recv_loop: ; ---- recvfrom(fd, BUF, 2048, 0, &FROM, &FROMLEN) ------------- ; arg5 is a POINTER to the socklen (as tftp_read passes it); the ; value is initialised to 16 first. movi 16, a0, L move a0, @0187A080h, 1 ; fromlen = 16 movi 01875000h, a14, L movi 0187A080h, a0, L move a0, *a14+, 1 ; arg5 = &fromlen movi 0187A000h, a0, L move a0, *a14+, 1 ; arg4 = &from movi 0, a0, L move a0, *a14+, 1 ; arg3 = flags movi 00800h, a0, L move a0, *a14+, 1 ; arg2 = 2048 movi 01876000h, a0, L move a0, *a14+, 1 ; arg1 = receive buffer move a9, *a14+, 1 ; arg0 = fd calla 3F5D7D50h move @0187C200h, sp, 1 move @0187C220h, a14, 1 setf 16, 0, 0 setf 32, 0, 1 move a8, @0187C080h, 1 ; last length move a8, a8 jrle fail ; opcode: wire 00 03 -> little-endian word 0300h move @01876000h, a0, 0 cmpi 0300h, a0 jrne fail ; only DATA is accepted move @01876010h, a4, 0 ; block number, wire order move a4, @0187C0A0h, 1 move a8, a3 subk 4, a3 ; payload length ; ---- total += payload, dest += payload ----------------------- move @0187C060h, a1, 1 add a3, a1 move a1, @0187C060h, 1 ; total += payload (bytes) move a11, a6 ; copy destination move a3, a2 sll 3, a2 ; bytes -> bits: pointers are bit addresses add a2, a11 ; dest cursor advances now (do NOT add bytes) ; ---- copy payload from BUF+4 to a6, one byte at a time ------ ; Interrupts are disabled for the copy so a 512-byte copy is atomic ; with respect to the firmware's ISRs. dint movi 01876020h, a5, L move a3, a2 move a3, @0187C0E0h, 1 ; payload length move a2, @0187C140h, 1 ; copy count move a6, @0187C120h, 1 ; write start move a11, @0187C100h, 1 ; cursor after advance move a5, @0187C160h, 1 ; source jreq copy_done copy_bytes: movb *a5, a0 movb a0, *a6 addk 8, a5 ; one byte addk 8, a6 dsjs a2, copy_bytes copy_done: eint ; ---- ACK: 00 04 -------------------------- movi 00400h, a0, L move a0, @0187A100h, 0 move a4, @0187A110h, 0 movi 01875000h, a14, L movi 16, a0, L move a0, *a14+, 1 movi 0187A000h, a0, L move a0, *a14+, 1 movi 0, a0, L move a0, *a14+, 1 movi 4, a0, L move a0, *a14+, 1 movi 0187A100h, a0, L move a0, *a14+, 1 move a9, *a14+, 1 calla 3F5D3D70h move @0187C200h, sp, 1 move @0187C220h, a14, 1 setf 16, 0, 0 setf 32, 0, 1 move a8, a8 jrlt fail ; short block ends the transfer; full block means read again move @0187C080h, a2, 1 subk 4, a2 cmpi 512, a2 jrlt close_it jruc recv_loop close_it: movi 4, a0, L move a0, @0187C000h, 1 movi 01875000h, a14, L move a9, *a14+, 1 calla 3F5E8930h ; close(fd) move @0187C200h, sp, 1 move @0187C220h, a14, 1 setf 16, 0, 0 setf 32, 0, 1 movi 05A5A5A5h, a0, L move a0, @0187C0C0h, 1 sub a8, a8 jruc epilogue fail: movi 0FFh, a0, L move a0, @0187C000h, 1 movi 0FFFFFFFFh, a8, L epilogue: mmfm sp, a0,a1,a2,a3,a4,a5,a6,a7 rets RRQ: .word 00100h ; opcode 1 (00 01) .word 07764h ; "dw" .word 02F74h ; "t/" .word 06374h ; "tc" .word 06970h ; "pi" .word 02F70h ; "p/" .word 07270h ; "pr" .word 0626Fh ; "ob" .word 02E65h ; "e." .word 06962h ; "bi" .word 0006Eh ; "n\0" .word 0636Fh ; "oc" .word 06574h ; "te" .word 00074h ; "t\0" SADDR: .word 00002h ; sin_family = AF_INET .word 04500h ; sin_port = htons(69) .word 0A8C0h ; 192.168 .word 00163h ; .99.1 .word 0 .word 0 .word 0 .word 0 .end