. "$BST_LIB" # libowfat 0.32 -- Felix von Leitner's reimplementation of Dan Bernstein's # libdjb: byte and string primitives, integer formatting and parsing, the # textcode encoders (base64, hex, URL, yEnc, uuencode, quoted-printable, JSON, # LDAP), IP address formatting, dynamic strings and arrays, critbit trees, # overflow-checked multiplication, TAI64 time, and on top of those a buffered # I/O layer, a cdb reader/writer, sockets and an epoll event loop. meta-oe # ships it from meta-networking/recipes-support/ncp. # # --------------------------------------------------------------------------- # TWO KINDS OF CASE, AND THE SPLIT IS THE POINT # # libowfat spans both sides of the syscall boundary, which no other library # ported on this Epic has done, and the two sides cannot honestly be judged the # same way. # # THE COMPUTATIONAL CORE (sections A-L, compared against the control) is pure # arithmetic over caller-supplied memory. It allocates, at most, with malloc; # it opens nothing and calls no syscall. For a given input its answers are # fixed by the code and by the formats it implements -- not by the word size, # the byte order or the libc -- so every one of those values is REQUIRED to # be identical on x86_64 and wasm32. data/owfat-native.txt is the verbatim # output of the SAME probe source built against a native x86_64 build of the # SAME libowfat 0.32 sources, patched identically. There is no tolerance band # in that block: one differing line is a defect. # # THE KERNEL-FACING LAYER (the RT section) is file I/O, mmap, cdb, sockets # and epoll. Those are exactly where this platform's remaining gaps live, so # they are NOT compared against the native control -- a complete kernel would # always disagree with a partial one, and the diff would say nothing. Each RT # case is instead scored against what a WORKING platform must do, so a red # there names a platform gap and is attributed to its issue rather than being # read as a bad port. # # WHAT IS ACTUALLY BEING MEASURED. libowfat installs no executable at all -- # ${PN} exists but is empty (ALLOW_EMPTY), and the deliverable is the archive # plus 37 headers. So without a cross-linked consumer this suite could only # asserts that files exist, which would pass identically if every function in # the archive trapped on first call. data/owfat-probe is that consumer: this # port's own archive, reached through the way a real consumer # reaches it. # # THE ANCHORED CASES EXIST BECAUSE AN ORACLE CAN BE WRONG. The bulk below # compares target output against the native control, which catches any # disagreement between the two machines but would NOT catch a control captured # from an already-broken build. So the values that a published specification # fixes -- the RFC 4648 base64 vectors, the byte order of uint*_pack, the UTF-8 # encoding boundaries, the overflow answers -- are ALSO asserted against # literals. Those hold whatever the control says. # --------------------------------------------------------------------------- # A root prefix, empty on the target and set only when this suite is validated # NATIVELY under dash before it ships. Writing /usr/lib and /usr/include needs # real root, so a native run stages them under a temporary tree and points this # at it. The runner never sets it, so on the image these are absolute paths. R="${BST_OWFAT_ROOT:-}" ARCHIVE=$R/usr/lib/libowfat.a INCDIR=$R/usr/include/libowfat PROBE=$BST_DATA/owfat-probe NATIVE=$BST_DATA/owfat-native.txt if [ ! -f "$ARCHIVE" ]; then bst_skip owfat/present "libowfat not installed on this image" exit 0 fi bst_ok owfat/present # --- the package is whole ---------------------------------------------------- # One header per functional area the probe actually links against, rather than # all 37: a missing header the probe needs would fail the link anyway, and # these are the ones a consumer includes by name. for h in byte str fmt scan uint32 textcode stralloc array critbit safemult \ ip4 ip6 tai taia buffer socket io cdb mmap open; do check "owfat/hdr-$h" test -f "$INCDIR/$h.h" done check owfat/staticlib test -f "$ARCHIVE" # libowfat's GNUmakefile builds NO shared object -- there is no -shared rule in # it at all, which is why this port needed no bbappend on a static-only image # where wasmld refuses side modules. If a .so ever appeared it would mean the # upstream build had grown a shared link that nothing here disables, and the # port would be shipping something that cannot be loaded. if [ -n "$(find "$R/usr/lib" -maxdepth 1 -name 'libowfat.so*' 2>/dev/null)" ]; then bst_notok owfat/no-shared "a libowfat shared object is installed on a static-only image" else bst_ok owfat/no-shared fi # --- the probe and its control ship together --------------------------------- if [ ! -f "$PROBE" ]; then bst_notok owfat/probe-present "$PROBE missing -- the port cannot be functionally verified" exit 0 fi bst_ok owfat/probe-present if [ ! -f "$NATIVE" ]; then bst_notok owfat/native-present "$NATIVE missing -- no oracle to compare against" exit 0 fi bst_ok owfat/native-present # --- run the consumer -------------------------------------------------------- # brintos-selftests' do_install stages data/* at 0644 and the deployed rootfs is # read-only at browser runtime, so the probe cannot be executed where it lies -- # stage an executable copy under TMPDIR, as every probe-driven suite here does. tmp=${TMPDIR:-/tmp}/bst-owfat.$$ _p=$tmp.bin cp "$PROBE" "$_p" 2>/dev/null && chmod 0755 "$_p" 2>/dev/null if [ ! -x "$_p" ]; then bst_notok owfat/probe-exec "could not stage an executable copy of the probe" rm -f "$_p" exit 0 fi bst_ok owfat/probe-exec "$_p" cmp > "$tmp.out" 2> "$tmp.err"; rc=$? check_eq owfat/probe-rc 0 "$rc" # A truncated run must be distinguishable from a run that legitimately produced # these lines, so the probe prints a terminator and it is checked before any # comparison. Without it a crash half way through would silently reduce the # number of cases rather than fail one. if [ "$(tail -n 1 "$tmp.out" 2>/dev/null)" = "PROBE-END" ]; then bst_ok owfat/probe-complete else bst_notok owfat/probe-complete "no PROBE-END terminator: $(_bst_1line "$(tail -n 3 "$tmp.out" 2>/dev/null)")" fi want_n=$(wc -l < "$NATIVE" | tr -d ' ') got_n=$(wc -l < "$tmp.out" | tr -d ' ') check_eq owfat/line-count "$want_n" "$got_n" ow_get() { awk -v k="$1" '$1==k { $1=""; sub(/^ /,""); print; exit }' "$tmp.out"; } # --- anchored: published specifications, not the control --------------------- # RFC 4648 section 10 test vectors, as hex of the ASCII the encoder produced. # These are the canonical base64 vectors; they hold whatever the control says. # Each line is " ", so the leading numbers are asserted # too -- a fmt_* that wrote the right bytes but mis-reported its length would # corrupt the next field a caller appends. check_eq owfat/anchor-b64-empty "0 0" "$(ow_get F01-base64-empty)" check_eq owfat/anchor-b64-f "4 4 5a673d3d" "$(ow_get F02-base64-f)" check_eq owfat/anchor-b64-fo "4 4 5a6d383d" "$(ow_get F03-base64-fo)" check_eq owfat/anchor-b64-foo "4 4 5a6d3976" "$(ow_get F04-base64-foo)" check_eq owfat/anchor-b64-foob "8 8 5a6d397659673d3d" "$(ow_get F05-base64-foob)" check_eq owfat/anchor-b64-fooba "8 8 5a6d3976596d453d" "$(ow_get F06-base64-fooba)" check_eq owfat/anchor-b64-foobar "8 8 5a6d3976596d4679" "$(ow_get F07-base64-foobar)" # The two alphabet positions that differ between base64 and base64url: '+/' # versus '-_' (RFC 4648 sections 4 and 5). check_eq owfat/anchor-b64-62-63 "4 4 2b2f383d" "$(ow_get F08-base64-62-63)" check_eq owfat/anchor-b64url-alpha "2d5f38" "$(ow_get F09-base64url-62-63 | cut -d' ' -f3)" # BYTE ORDER IS FIXED BY THE API, NOT BY THE HOST. uint32_pack is little endian # and uint32_pack_big is big endian on every platform libowfat runs on -- that # is what makes it usable for on-disk and on-wire formats (cdb among them). A # port that let the host's order leak through would silently corrupt every such # format, and would still pass a round-trip test that packed and unpacked with # the same wrong order. Only asserting the BYTES catches it. check_eq owfat/anchor-u16-le "0201" "$(ow_get E01-u16-pack)" check_eq owfat/anchor-u16-be "0102" "$(ow_get E02-u16-pack-big)" check_eq owfat/anchor-u32-le "04030201" "$(ow_get E03-u32-pack)" check_eq owfat/anchor-u32-be "01020304" "$(ow_get E04-u32-pack-big)" check_eq owfat/anchor-u64-le "0807060504030201" "$(ow_get E05-u64-pack)" check_eq owfat/anchor-u64-be "0102030405060708" "$(ow_get E06-u64-pack-big)" # The integer widths a consumer declares its own on-disk structures with. These # are libowfat's typedefs, not the host's, and must be exactly 2/4/8 everywhere. check_eq owfat/anchor-sizeof-u16 "2" "$(ow_get E17-sizeof-uint16)" check_eq owfat/anchor-sizeof-u32 "4" "$(ow_get E18-sizeof-uint32)" check_eq owfat/anchor-sizeof-u64 "8" "$(ow_get E19-sizeof-uint64)" check_eq owfat/anchor-char-bit "8" "$(ow_get E20-char-bit)" # UTF-8 encoding boundaries (RFC 3629): the first code point needing 1, 2, 3 # and 4 bytes. Off-by-one at a boundary is the classic encoder bug and would # not show up on ASCII-only input. check_eq owfat/anchor-utf8-7f "1 1 7f" "$(ow_get C33-utf8-b1)" check_eq owfat/anchor-utf8-80 "2 2 c280" "$(ow_get C34-utf8-b2)" check_eq owfat/anchor-utf8-800 "3 3 e0a080" "$(ow_get C35-utf8-b3)" check_eq owfat/anchor-utf8-10000 "4 4 f0908080" "$(ow_get C36-utf8-b4)" # U+20AC EURO SIGN is e2 82 ac; U+1D11E is f0 9d 84 9e. check_eq owfat/anchor-utf8-euro "3 3 e282ac" "$(ow_get C31-utf8-3)" check_eq owfat/anchor-utf8-clef "4 4 f09d849e" "$(ow_get C32-utf8-4)" # The overflow answers. safemult's whole reason to exist is returning 0 when a # product does not fit; a port on which an intermediate got widened would # return 1 and every bounds check built on it would stop working -- while every # non-overflowing case still passed. check_eq owfat/anchor-umult16-over "0" "$(ow_get K02-umult16-over)" check_eq owfat/anchor-umult32-over "0" "$(ow_get K04-umult32-over)" check_eq owfat/anchor-umult64-over "0" "$(ow_get K06-umult64-over)" check_eq owfat/anchor-imult16-over "0" "$(ow_get K08-imult16-over)" check_eq owfat/anchor-imult64-over "0" "$(ow_get K13-imult64-over)" check_eq owfat/anchor-umult16-ok "1 60000" "$(ow_get K01-umult16-ok)" check_eq owfat/anchor-imult16-neg "1 -30000" "$(ow_get K09-imult16-neg)" # critbit0_allprefixed walks in lexicographic order by construction. An # ordering change would be invisible to a per-key contains() check, so the # joined walk is pinned. check_eq owfat/anchor-critbit-order "apple,apricot,banana," "$(ow_get J10-allprefixed-all)" check_eq owfat/anchor-critbit-prefix "apple,apricot," "$(ow_get J09-allprefixed-ap-keys)" # IPv6 text forms. The :: compression and the v4-mapped rendering are what a # consumer prints addresses with, and both are fixed by RFC 5952. check_eq owfat/anchor-ip6-compress "11 11 323030313a6462383a3a31" "$(ow_get G09-fmt-ip6c)" check_eq owfat/anchor-ip6-unspec "2 2 3a3a" "$(ow_get G13-fmt-ip6c-unspec)" check_eq owfat/anchor-ip4-bytes "c0a80101" "$(ow_get G02-scan-ip4-bytes)" # TAI64 labels are big-endian 64-bit stores; 1000000000 == 0x3b9aca00. check_eq owfat/anchor-tai-be "000000003b9aca00" "$(ow_get L02-pack-1e9)" # TWO UPSTREAM FMT_LEN DEFECTS, PINNED DELIBERATELY. # # Every libowfat fmt_* takes a NULL destination and then returns the length it # WOULD have written, so callers can size a buffer with it. Two encoders get # that length wrong, identically on x86_64 and wasm32 -- this is upstream # libowfat 0.32, not a port defect and not a platform one: # # fmt_uuencoded's NULL path returns (len+2)/3*4, which counts only the data # quartets. The writer also emits a per-line length byte and a trailing # newline, so it writes 2 MORE bytes per line than the caller was told to # allocate. A caller following the documented convention overflows its buffer. # For a 3-byte input: told 4, writes 6. # # fmt_base64url's NULL path returns the PADDED base64 length while the writer # deliberately emits no padding, so it over-reports by up to 2. Harmless # (over-allocation), but wrong. # # These are pinned rather than corrected: the suite's job is to detect change, # and if a future libowfat bump fixes either, that case goes red and the reader # is pointed straight at the fix by this comment. check_eq owfat/upstream-uuencoded-fmtlen "6 4" "$(ow_get F19-uuencoded | cut -d' ' -f1,2)" check_eq owfat/upstream-b64url-fmtlen "3 4" "$(ow_get F09-base64url-62-63 | cut -d' ' -f1,2)" # --- the control comparison: one case per probe line ------------------------- # # Both files carry the same ids in the same order by construction (same probe # source, same sequence of prints), so this is a single lockstep pass with no # subprocess per line -- 300 awk invocations would be slow inside the guest. A # target run that ended early leaves the remaining reads empty, which fails # those cases rather than silently shortening the suite. exec 3< "$NATIVE" exec 4< "$tmp.out" while IFS= read -r wl <&3; do IFS= read -r gl <&4 || gl="" id=${wl%% *} if [ "$wl" = "$gl" ]; then bst_ok "owfat/$id" else bst_notok "owfat/$id" "want='$(_bst_1line "${wl#* }")' got='$(_bst_1line "${gl#* }")'" fi done exec 3<&- 4<&- if [ -s "$tmp.err" ]; then bst_notok owfat/clean-stderr "$(_bst_1line "$(cat "$tmp.err")")" else bst_ok owfat/clean-stderr fi # --- RT: the kernel-facing layer --------------------------------------------- # # Not compared against the control -- see the header. Each case asserts what a # working platform must do. A red here is a platform gap: it is attributed at # the browser-verify gate and recorded in the design doc, and the ticket # accepts it as a legitimate result rather than a reason to withhold the port. rtdir=${TMPDIR:-/tmp} # ONE INVOCATION PER SECTION, because on this platform an RT case can TRAP # rather than return an error, and a trap ends the whole process. Run as one # batch, the first gap would hide every gap after it -- which is the opposite # of what this ticket wants, since surfacing the gaps IS the deliverable. Split, # a section that dies costs only its own cases. # # Each section also declares how many cases it owes. A trapped section prints # fewer lines than that, and without the count the suite would simply get # smaller -- a shrinking total reads as "fine" in a summary line, which is # exactly the failure that must not be silent. rt_section() { _sec=$1; _want=$2 "$_p" rt "$rtdir" "$_sec" > "$tmp.rt" 2> "$tmp.rterr"; _rc=$? _got=$(grep -c '^RT[0-9]' "$tmp.rt" 2>/dev/null || echo 0) # Score every case the section did report. " ok" passes; anything else is # that case's own failure detail. while IFS= read -r line; do case "$line" in RT[0-9]*) ;; *) continue ;; esac rid=${line%% *} rval=${line#* } if [ "$rval" = "ok" ]; then bst_ok "owfat/$rid" else bst_notok "owfat/$rid" "$(_bst_1line "$rval")" fi done < "$tmp.rt" # WAS THIS SECTION TRUNCATED? The verdict is `exited 0 AND reported every case # it owes`, and those two together are conclusive: rt mode's ONLY exit path is # `fflush(stdout); _exit(0)` after the terminator, so a zero status already # proves the probe ran past the end. A section that traps reports rc=132 and a # short count, which is exactly what buffer and cdb do here. # # The RT-END line is kept as a diagnostic rather than as the verdict, because # it is not reliable on this platform: the net section -- the one that leaves # an epoll set behind -- exits 0 with all six cases present and its terminator # line missing from the captured stdout, both before and after the switch to # _exit(0). Gating on it would have parked a permanently red case that means # nothing, which is the fastest way to teach a reader to skim past reds. if [ "$_rc" = 0 ] && [ "$_got" = "$_want" ]; then bst_ok "owfat/rt-$_sec-complete" else bst_notok "owfat/rt-$_sec-complete" \ "rc=$_rc, $_got/$_want cases reported, died after '$(_bst_1line "$(grep '^RT[0-9]' "$tmp.rt" 2>/dev/null | tail -n 1)")': $(_bst_1line "$(cat "$tmp.rterr" 2>/dev/null)")" fi check_eq "owfat/rt-$_sec-count" "$_want" "$_got" if [ -s "$tmp.rterr" ]; then bst_notok "owfat/rt-$_sec-stderr" "$(_bst_1line "$(cat "$tmp.rterr")")" else bst_ok "owfat/rt-$_sec-stderr" fi } rt_section file 6 # RT01-RT06: open_trunc/read/excl, read-back, openreadclose rt_section mmap 3 # RT07-RT09: file-backed mmap, checked by CONTENT rt_section buffer 1 # RT10: the buffered-I/O layer rt_section cdb 3 # RT11-RT13: cdb_make + cdb_find, which mmaps underneath rt_section net 6 # RT14-RT19: sockets, ndelay, the epoll event loop rm -f "$_p" "$tmp.out" "$tmp.err" "$tmp.rt" "$tmp.rterr"