. "$BST_LIB" # BST-TIMEOUT: 300 # # 67 cases, one exec apiece and no corpus to stage; the two libusb arms are # individually bounded at 60 s by bst_probe, so the suite's own bound only has # to cover the sum. 300 is comfortably above that and well under the default's # usefulness as a hang detector. # # dfu-util 0.11 on wasm32-hwjs glibc -- the USB Device Firmware Upgrade host # utility, plus the two file tools that ship with it: dfu-suffix and dfu-prefix # (meta-openembedded/meta-oe, recipes-support/dfu-util; one meta-brintos # bbappend, one LDFLAGS line). # # --------------------------------------------------------------------------- # THIS MACHINE HAS NO USB BUS, AND THE PORT SPLITS CLEANLY ALONG THAT LINE. # # The kernel HAS a USB core -- usb, hub, usbfs and ftdi_sio are registered and # /sys/bus lists usb-serial beside them -- but there is no host controller, so # /sys/bus/usb/devices is empty and /dev/bus does not exist at all (measured # under BRINTOS-1338, and re-asserted below as this suite's positive control). # # So the three programs divide in two: # # dfu-suffix, dfu-prefix pure file tools. suffix.c / prefix.c over # dfu_file.c: read a firmware image, add / check / # delete a 16-byte DFU suffix or an 8-byte vendor # prefix, CRC32 the payload. No libusb call anywhere. # EVERYTHING these do is exercised here, for real, # over a fixture this suite builds. # # dfu-util needs a bus for anything past argument parsing. # Its argument handling and its version banner run # unconditionally and are asserted; its two device # paths are asserted to REFUSE, bounded, with the # documented diagnostic and exit status. # # --------------------------------------------------------------------------- # WHY dfu-util REFUSES RATHER THAN ENUMERATING AN EMPTY BUS, WHICH IS NOT THE # SAME ANSWER AND IS WORTH STATING PRECISELY. # # main() calls libusb_init() before it probes anything, and errx()s on failure: # # ret = libusb_init(&ctx); # if (ret) # errx(EX_IOERR, "unable to initialize libusb: %s", libusb_error_name(ret)); # # On this image libusb1 is built with the udev PACKAGECONFIG cleared -- eudev's # do_compile is refused outright by this toolchain, and nothing here provides # udev in any case (BRINTOS-1576, libusb1_%.bbappend). That choice reaches all # the way into libusb's own answer: find_usbfs_path() looks for /dev/bus/usb, # then for /dev/usbdev*.*, and ends # # #if defined(HAVE_LIBUDEV) || defined(__ANDROID__) # return USB_DEVTMPFS_PATH; /* assume /dev/bus/usb */ # #else # return NULL; # #endif # # so WITHOUT udev op_init() logs "could not find usbfs" and libusb_init returns # LIBUSB_ERROR_OTHER, where WITH it libusb_init would have reported success and # handed dfu-util an empty device list. Both arms were measured natively under # BRINTOS-1576 against exactly this guest's /dev and /sys state. # # The consequence for this port is that `dfu-util -l` does not print an empty # device list; it exits 74 (EX_IOERR) naming libusb. That is the honest answer # for a machine with no usbfs and it is asserted as such -- with the bus probes # below as its control, so that the day this platform gains a host controller # the refusal cases and the probe cases go red TOGETHER and say why. # # --------------------------------------------------------------------------- # THE FIXTURE IS BUILT HERE, NOT SHIPPED, and its content is chosen. # # It is plain ASCII so that every content assertion is a string compare in the # shell rather than a binary diff, and its first bytes are 'b','r' -- neither # 0x01,0x00 (TI Stellaris) nor 0x1a,0x3f masked (NXP LPC), the two patterns # probe_prefix() recognises. A fixture that accidentally looked like a prefixed # image would make dfu-suffix rewrite a prefix it invented, and the size # assertions would then be measuring the wrong thing. bst_ok dfu-util/suite-canary # first line: absorbs brintos/linux#96, which drops it require dfu-util dfu-util/present require dfu-suffix dfu-suffix/present require dfu-prefix dfu-prefix/present W=${TMPDIR:-/tmp}/bst-dfu-util.$$ rm -rf "$W" if mkdir -p "$W" 2>/dev/null; then bst_ok dfu-util/workdir else bst_notok dfu-util/workdir "cannot create working directory $W" exit 0 fi export W nl=' ' # has : ok iff contains the literal . has() { case "$3" in *"$2"*) bst_ok "$1" ;; *) bst_notok "$1" "want:'$(_bst_1line "$2")' got:'$(_bst_1line "$3")'" ;; esac; } # hasnt : the negative, for a split that must hold. hasnt() { case "$3" in *"$2"*) bst_notok "$1" "present and must not be: $(_bst_1line "$2")" ;; *) bst_ok "$1" ;; esac; } # run : run a command, keeping its stdout, stderr and status as # $W/.out, $W/.err and $W/.rc. Always exits 0 itself, so # bst_probe can bound it while the command's own status stays readable -- the # two device paths below are expected to FAIL, and bst_probe scores a non-zero # child as a red. cat > "$W/run" <<'EOS' #!/bin/sh tag=$1; shift "$@" >"$W/$tag.out" 2>"$W/$tag.err" printf '%s\n' $? >"$W/$tag.rc" exit 0 EOS chmod +x "$W/run" # --- 1. the packaging --------------------------------------------------------- # Asserted against dpkg's own file list as well as by stat: a binary that reached # the image by some other route satisfies the stat and does not satisfy the # claim. All three programs are in ONE package (bin_PROGRAMS in src/Makefile.am, # no PACKAGE_BEFORE_PN split), which is what the three list cases pin. check dfu-util/install-path test -x /usr/bin/dfu-util check dfu-suffix/install-path test -x /usr/bin/dfu-suffix check dfu-prefix/install-path test -x /usr/bin/dfu-prefix check_eq dfu-util/which "/usr/bin/dfu-util" "$(command -v dfu-util)" check_eq dfu-suffix/which "/usr/bin/dfu-suffix" "$(command -v dfu-suffix)" check_eq dfu-prefix/which "/usr/bin/dfu-prefix" "$(command -v dfu-prefix)" pkglist=$(cat /var/lib/dpkg/info/dfu-util.list 2>/dev/null) if [ -n "$pkglist" ]; then bst_ok dfu-util/pkg-list hasline() { case "$nl$3$nl" in *"$nl$2$nl"*) bst_ok "$1" ;; *) bst_notok "$1" "not listed: $2" ;; esac; } hasline dfu-util/pkg-util /usr/bin/dfu-util "$pkglist" hasline dfu-util/pkg-suffix /usr/bin/dfu-suffix "$pkglist" hasline dfu-util/pkg-prefix /usr/bin/dfu-prefix "$pkglist" # The three man pages go to dfu-util-doc by the default FILES split, and that # package is NOT installed here. Their absence from the runtime list is the # packaging working rather than a missing file. hasnt dfu-util/pkg-man-split "/usr/share/man/man1/dfu-util.1" "$pkglist" else bst_notok dfu-util/pkg-list "no /var/lib/dpkg/info/dfu-util.list -- the packaging cases below cannot run" fi # --- 2. the version banners --------------------------------------------------- # All three print a banner unconditionally, before any option is acted on, and # it carries PACKAGE_STRING / PACKAGE_VERSION from AC_INIT. A version skew # between the recipe and the binary shows up here and nowhere else on this image. v_util=$(dfu-util -V 2>&1); check_rc dfu-util/version-rc 0 dfu-util -V v_sfx=$(dfu-suffix -V 2>&1); check_rc dfu-suffix/version-rc 0 dfu-suffix -V v_pfx=$(dfu-prefix -V 2>&1); check_rc dfu-prefix/version-rc 0 dfu-prefix -V has dfu-util/version "dfu-util 0.11" "$v_util" has dfu-suffix/version "dfu-suffix (dfu-util) 0.11" "$v_sfx" has dfu-prefix/version "dfu-prefix (dfu-util) 0.11" "$v_pfx" # --- 3. argument handling, which needs no bus --------------------------------- # These run entirely before libusb_init() and are the part of dfu-util that is # fully exercisable here. EX_USAGE is 64. check_rc dfu-util/help-rc 0 dfu-util -h has dfu-util/help-usage "Usage: dfu-util" "$(dfu-util -h 2>&1)" check_rc dfu-util/no-mode-rc 64 dfu-util has dfu-util/no-mode-msg "You need to specify one of -D or -U" "$(dfu-util 2>&1)" check_rc dfu-util/bad-arg-rc 64 dfu-util bogus-positional has dfu-util/bad-arg-msg "Error: Unexpected argument: bogus-positional" "$(dfu-util bogus-positional 2>&1)" check_rc dfu-suffix/no-file-rc 64 dfu-suffix has dfu-suffix/no-file-msg "You need to specify a filename" "$(dfu-suffix 2>&1)" check_rc dfu-prefix/no-file-rc 64 dfu-prefix # -a without a type is dfu-prefix's own precondition, not a file error. has dfu-prefix/needs-type "Prefix type must be specified" "$(dfu-prefix -a "$W/nonexistent" 2>&1)" # --- 4. the fixture ----------------------------------------------------------- i=0 : > "$W/fw.orig" while [ $i -lt 32 ]; do printf 'brintos-dfu-fixture-%03d\n' $i >> "$W/fw.orig" i=$((i + 1)) done fw_size=$(wc -c < "$W/fw.orig") # BST-REDIRECT-OK: $W is under $TMPDIR, never the rootfs check_eq dfu-util/fixture-size "768" "$fw_size" orig=$(cat "$W/fw.orig") cp "$W/fw.orig" "$W/fw.bin" # --- 5. dfu-suffix: add, check, delete ---------------------------------------- # The suffix is a fixed 16 bytes (DFU_SUFFIX_LENGTH in dfu_file.c) appended after # the payload, carrying bcdDevice/idProduct/idVendor/bcdDFU, the length byte, the # 'DFU' signature and a CRC32 over everything before it. Growing by exactly 16 # and leaving the payload byte-identical is the whole contract of `-a`. add_out=$(dfu-suffix -a "$W/fw.bin" -v 1234 -p 5678 2>&1); add_rc=$? check_eq dfu-suffix/add-rc "0" "$add_rc" has dfu-suffix/add-msg "Suffix successfully added to file" "$add_out" check_eq dfu-suffix/add-grew-16 "$((fw_size + 16))" "$(wc -c < "$W/fw.bin")" # BST-REDIRECT-OK: $TMPDIR check_eq dfu-suffix/add-payload-intact "$orig" "$(head -c "$fw_size" "$W/fw.bin")" cp "$W/fw.bin" "$W/fw.dfu" # kept suffixed for the download case in section 7 chk_out=$(dfu-suffix -c "$W/fw.bin" 2>&1); chk_rc=$? check_eq dfu-suffix/check-rc "0" "$chk_rc" has dfu-suffix/check-announces "contains a DFU suffix with the following properties:" "$chk_out" # The fields are printed with hard tabs, so each needle is built rather than # typed -- a needle with a space where the tool prints a tab matches nothing and # reads as a decode failure. has dfu-suffix/check-vid "$(printf 'Vendor ID:\t0x1234')" "$chk_out" has dfu-suffix/check-pid "$(printf 'Product ID:\t0x5678')" "$chk_out" has dfu-suffix/check-bcddev "$(printf 'BCD device:\t0xFFFF')" "$chk_out" has dfu-suffix/check-bcddfu "$(printf 'BCD DFU:\t0x0100')" "$chk_out" has dfu-suffix/check-length "$(printf 'Length:\t\t16')" "$chk_out" has dfu-suffix/check-crc "CRC:" "$chk_out" del_out=$(dfu-suffix -D "$W/fw.bin" 2>&1); del_rc=$? check_eq dfu-suffix/delete-rc "0" "$del_rc" has dfu-suffix/delete-msg "Suffix successfully removed from file" "$del_out" check_eq dfu-suffix/roundtrip "$orig" "$(cat "$W/fw.bin")" # A CORRUPTED SUFFIX MUST BE REJECTED, and the corruption is built rather than # poked: a second fixture of the same length gets its own suffix, and that # suffix is spliced onto the FIRST fixture's payload. The 'DFU' signature is # therefore valid and only the CRC disagrees, which is the one path that # distinguishes "no suffix here" from "a suffix that does not describe this # file". Poking a byte in place would have needed dd(1) and would have risked # landing in the signature instead. i=0 : > "$W/fw2.bin" while [ $i -lt 32 ]; do printf 'brintos-dfu-corrupt-%03d\n' $i >> "$W/fw2.bin" i=$((i + 1)) done dfu-suffix -a "$W/fw2.bin" -v 1234 -p 5678 >/dev/null 2>&1 head -c "$fw_size" "$W/fw.orig" > "$W/fw.bad" tail -c 16 "$W/fw2.bin" >> "$W/fw.bad" check_eq dfu-suffix/corrupt-staged "$((fw_size + 16))" "$(wc -c < "$W/fw.bad")" # BST-REDIRECT-OK: $TMPDIR bad_out=$(dfu-suffix -c "$W/fw.bad" 2>&1); bad_rc=$? check_eq dfu-suffix/corrupt-rc "65" "$bad_rc" # EX_DATAERR has dfu-suffix/corrupt-names-crc "DFU suffix CRC does not match" "$bad_out" has dfu-suffix/corrupt-refuses "Valid DFU suffix needed" "$bad_out" # And the negative control: -c on a file that never had a suffix reports the # SIGNATURE as missing, not the CRC. Two different diagnoses, one code path. none_out=$(dfu-suffix -c "$W/fw.orig" 2>&1); none_rc=$? check_eq dfu-suffix/nosuffix-rc "65" "$none_rc" has dfu-suffix/nosuffix-names-signature "Invalid DFU suffix signature" "$none_out" # --- 6. dfu-prefix: the 8-byte TI Stellaris prefix ---------------------------- # Same shape at the other end of the file: LMDFU_PREFIX_LENGTH is 8, the prefix # records the payload length so probe_prefix() can recognise it again, and the # round trip must return the original bytes. cp "$W/fw.orig" "$W/fwp.bin" pa_out=$(dfu-prefix -a "$W/fwp.bin" -T 2>&1); pa_rc=$? check_eq dfu-prefix/add-rc "0" "$pa_rc" has dfu-prefix/add-msg "Adding prefix to file" "$pa_out" check_eq dfu-prefix/add-grew-8 "$((fw_size + 8))" "$(wc -c < "$W/fwp.bin")" # BST-REDIRECT-OK: $TMPDIR pc_out=$(dfu-prefix -c "$W/fwp.bin" -T 2>&1); pc_rc=$? check_eq dfu-prefix/check-rc "0" "$pc_rc" has dfu-prefix/check-announces "contains a TI Stellaris DFU prefix" "$pc_out" has dfu-prefix/check-address "$(printf 'Address:\t0x00000000')" "$pc_out" pd_out=$(dfu-prefix -D "$W/fwp.bin" -T 2>&1); pd_rc=$? check_eq dfu-prefix/delete-rc "0" "$pd_rc" check_eq dfu-prefix/roundtrip "$orig" "$(cat "$W/fwp.bin")" # A prefix of the wrong type is refused rather than silently accepted. check_rc dfu-prefix/wrong-type 65 dfu-prefix -c "$W/fw.orig" -L # --- 7. the bus, and what dfu-util does without one --------------------------- # The three probes are this section's control. They are stated as assertions, not # skips: if any of them flips, the two refusal cases below are no longer the # right answer and must go red beside it. check dfu-util/bus-core-present test -d /sys/bus/usb check dfu-util/bus-no-devices sh -c '[ -z "$(ls -A /sys/bus/usb/devices 2>/dev/null)" ]' check dfu-util/bus-no-usbfs sh -c '[ ! -e /dev/bus ]' # BOUNDED, because a tool that blocks on a bus would otherwise take the whole # suite down with it and the runner would report every case above as a pass. # bst_probe scores the WRAPPER, which always exits 0; dfu-util's own status is # read from the .rc file afterwards. bst_probe dfu-util/list-bounded "$W/list.probe" 60 "$W/run" list dfu-util -l list_rc=$(cat "$W/list.rc" 2>/dev/null) list_out=$(cat "$W/list.out" 2>/dev/null) list_err=$(cat "$W/list.err" 2>/dev/null) check_eq dfu-util/list-status "74" "$list_rc" # EX_IOERR has dfu-util/list-banner "dfu-util 0.11" "$list_out" has dfu-util/list-names-libusb "unable to initialize libusb: LIBUSB_ERROR_OTHER" "$list_err" # A download requested against an absent device: the FILE is read and its suffix # parsed before libusb is touched (dfu_load_file precedes libusb_init in main), # so this case asserts both halves -- the vendor/product ID this suite wrote in # section 5 comes back out of the file, and the device path then refuses with the # same diagnostic and the same status as -l rather than crashing or hanging. bst_probe dfu-util/download-bounded "$W/dl.probe" 60 "$W/run" dl dfu-util -D "$W/fw.dfu" dl_rc=$(cat "$W/dl.rc" 2>/dev/null) dl_out=$(cat "$W/dl.out" 2>/dev/null) dl_err=$(cat "$W/dl.err" 2>/dev/null) has dfu-util/download-read-vid "Match vendor ID from file: 1234" "$dl_out" has dfu-util/download-read-pid "Match product ID from file: 5678" "$dl_out" check_eq dfu-util/download-status "74" "$dl_rc" has dfu-util/download-same-diagnostic "unable to initialize libusb: LIBUSB_ERROR_OTHER" "$dl_err" check_eq dfu-util/refusal-is-uniform "$list_err" "$dl_err" rm -rf "$W"