. "$BST_LIB" # BST-TIMEOUT: 300 # measured 27 s for 57 cases on an idle local guest # # (==BST-SUITE jasper elapsed=27s cases=57==, 2026-08-16). # # The 60 s default would hold there and would NOT hold on a # # loaded shared host: ~40 execs of a ~9 MB binary, each a # # fresh wasm module instantiation, is the kind of work that # # goes several times slower under contention. 300 is ~11x # # the measurement, which is the headroom the packager guide # # asks for rather than a round number. # jasper 4.1.2 (meta-oe recipes-graphics/jasper) -- the JPEG-2000 REFERENCE # CODEC. `jasper` transcodes, `imginfo` reports what a file contains, `imgcmp` # measures the distance between two images. Everything here is file-in/file-out: # no display, no network, no device. # # THE ORACLE IS THE CODEC'S OWN ARITHMETIC, AND IT SPLITS IN TWO. # JPEG-2000 has two wavelets and they demand different gates: # # `-O mode=int` the REVERSIBLE 5/3 wavelet. Integer lifting, integer # quantization, integer coding -- there is no rounding anywhere # in the path, so a round trip is REQUIRED TO RETURN THE SOURCE # BYTE FOR BYTE. Not "close enough": exact. This is the # strongest assertion available about any codec, and it is the # one that would catch a wrong wavelet, a wrong coefficient # order, a wrong MQ-coder state or a sign error, because every # one of those moves at least one byte. # # default the IRREVERSIBLE 9/7 wavelet, floating point. Exactness is not # available by construction, so this half is gated on BOUNDS -- # peak absolute error and RMSE per component, from imgcmp. # # NEITHER GATE SUBSUMES THE OTHER, AND THE LOSSY ONE IS NOT DECORATION. A codec # that ignored its input and emitted a fixed picture would fail the bounds while # passing nothing else; a codec whose 5/3 path had drifted by one LSB would fail # the exact gate while sitting comfortably inside every bound. # # WHY imgcmp RATHER THAN A BYTE DIFF FOR THE LOSSY HALF. imgcmp is jasper's own # comparison tool and reports in the codec's own metrics, per component. A byte # count cannot tell "slightly lossy" from "wrong picture"; a peak absolute error # of 33 on a 0..255 component can, and so can the requirement that the higher # rate be strictly closer to the source than the lower one. # # THE FIXTURE IS SYNTHETIC AND EXACTLY REGENERABLE. data/jasper-fixture.ppm is # 64x64 P6, integer arithmetic only, so anyone can reproduce it: per pixel (x,y), # r = x*255/63, g = y*255/63, b = (x+y)*255/126 (integer division throughout); # then every pixel with (x-44)^2 + (y-20)^2 <= 144 is overwritten (255,64,32); # then for 48 <= y < 64 and x < 16 all three channels become 255 when (x+y) is # odd and 0 when it is even. The three ramps give the wavelet the smooth # low-frequency content it is good at, the disc gives ONE HARD CURVED EDGE that # no wavelet represents compactly, and the checkerboard is the highest frequency # the sampling grid can carry -- the content a lossy transform destroys first. # 64x64 is a whole number of code blocks at every default decomposition level. # # THE DIGEST PINS ARE COMPILER-INDEPENDENT GROUND TRUTH, AND THAT IS MEASURED # RATHER THAN ASSUMED. The same question was asked of 57-mpg123.test and came out # the other way (-ffast-math makes gcc and clang disagree on identical source), # so it was asked again here before this suite was written. Measured 2026-08-16: # gcc 16.1.1 and clang 22.1.8 builds of this exact tarball produce # BYTE-IDENTICAL output for EVERY artifact this suite creates -- the lossless # jp2, the lossless jpc, the lossy jp2, and all three decodes. So the pins are # not asserting which compiler built the codec. # # AND THE THIRD DATA POINT IS THIS ARM ITSELF. The first target run reproduced # all three digests and all four deviation numbers EXACTLY (r0.1 pae=33 # rmse=4.200, r0.5 pae=1 rmse=0.090). That includes the LOSSY path, which is the # floating-point 9/7 wavelet -- the one place a wasm32 target could legitimately # have diverged from x86-64. It did not, because wasm f64 arithmetic is strictly # IEEE-754 with no excess precision and no contraction, the same guarantee SSE2 # gives on the control host. # # READING A RED. An EXACT case red (roundtrip-jp2-exact, roundtrip-jpc-exact, # pae-zero) is a genuine codec fault and must never be re-pinned. A DIGEST red # with every exactness and bound case still green is toolchain or version drift: # confirm against the exact cases and re-derive. The one red worth expecting on a # new arm is `encode-lossy-digest`, because that path alone is floating point -- # if it goes red while the lossy BOUNDS stay green, the finding is float # divergence on this arm, and the right response is to DROP that pin (the bounds # already carry the requirement), not to re-pin it to whatever this arm printed, # which would assert the arm instead of the codec. # # THE ==JASPER-OBSERVED== LINE. Outside the BST protocol, like ==BST-SUITE==, so # the report parsers ignore it. It carries the digests and deviations this run # actually produced, which is how the pins above get RE-DERIVED FROM A # MEASUREMENT rather than re-guessed -- one run gives both the failure and the # value to replace it with. require jasper jasper/present tmp=${TMPDIR:-/tmp}/bst-jasper.$$ mkdir -p "$tmp" 2>/dev/null fix=$BST_DATA/jasper-fixture.ppm if [ ! -f "$fix" ]; then bst_notok jasper/payload "missing $fix -- remaining cases not run" rm -rf "$tmp" exit 0 fi md5of() { md5sum "$1" 2>/dev/null | cut -d' ' -f1; } size() { wc -c < "$1" 2>/dev/null | tr -d ' '; } have_md5=0; command -v md5sum >/dev/null 2>&1 && have_md5=1 have_cmp=0; command -v imgcmp >/dev/null 2>&1 && have_cmp=1 # imgcmp prints one value per component, one per line. Reduce to the worst # component: an integer for pae, a milli-integer for rmse so shell can compare # it. Empty output (imgcmp refused the pair) yields an empty string, which every # caller below reports as a failure rather than silently treating as zero. pae_max() { imgcmp -f "$1" -F "$2" -m pae 2>/dev/null \ | awk 'BEGIN{m=-1}{if($1+0>m)m=$1+0}END{if(m>=0)print m}' } rmse_max_milli() { imgcmp -f "$1" -F "$2" -m rmse 2>/dev/null \ | awk 'BEGIN{m=-1}{if($1+0>m)m=$1+0}END{if(m>=0)printf "%d\n", m*1000+0.5}' } ncomps() { imgcmp -f "$1" -F "$2" -m pae 2>/dev/null | wc -l | tr -d ' '; } # ----------------------------------------------------------------- payload --- # Pinned BEFORE anything reads it. Without this a later mismatch has two # indistinguishable causes -- the codec changed, or the payload did -- and the # wrong one gets reported. if [ "$have_md5" -eq 1 ]; then check_eq jasper/fixture-digest c979e1967a3268a42c9c7903d102b53a "$(md5of "$fix")" else bst_skip jasper/fixture-digest "no md5sum on this image" fi check_eq jasper/fixture-size 12301 "$(size "$fix")" check_out jasper/version "4.1.2" jasper --version # ------------------------------------------------------ what imginfo reports --- # The ticket's "reported geometry and component depth", read from jasper's own # reporter rather than inferred from a file size. The six fields are: # format, number of components, width, height, precision in bits, total samples. check_eq jasper/imginfo-pnm "pnm 3 64 64 8 12288" "$(imginfo -f "$fix" 2>/dev/null)" # ------------------------------------------- the reversible path: EXACTNESS --- lj=$tmp/lossless.jp2 check jasper/encode-jp2-lossless jasper --input "$fix" --output "$lj" \ --output-format jp2 -O mode=int check_eq jasper/encode-jp2-lossless-size 2578 "$(size "$lj")" if [ "$have_md5" -eq 1 ]; then check_eq jasper/encode-jp2-lossless-digest 9a6e6cf735ca63000cab5eb117a44474 "$(md5of "$lj")" else bst_skip jasper/encode-jp2-lossless-digest "no md5sum on this image" fi # The container reports the same geometry the raster did -- a jp2 that lost the # component count or the bit depth would still decode to something. check_eq jasper/imginfo-jp2 "jp2 3 64 64 8 12288" "$(imginfo -f "$lj" 2>/dev/null)" ld=$tmp/lossless.ppm check jasper/decode-jp2-lossless jasper --input "$lj" --output "$ld" --output-format pnm # THE STRONGEST ASSERTION IN THIS SUITE. check jasper/roundtrip-jp2-exact cmp -s "$fix" "$ld" # The raw codestream is a second, independent container over the same coder. lc=$tmp/lossless.jpc check jasper/encode-jpc-lossless jasper --input "$fix" --output "$lc" \ --output-format jpc -O mode=int check_eq jasper/encode-jpc-lossless-size 2493 "$(size "$lc")" if [ "$have_md5" -eq 1 ]; then check_eq jasper/encode-jpc-lossless-digest b850f69ec0fbca068a9b3eb1bff9fc79 "$(md5of "$lc")" else bst_skip jasper/encode-jpc-lossless-digest "no md5sum on this image" fi check_eq jasper/imginfo-jpc "jpc 3 64 64 8 12288" "$(imginfo -f "$lc" 2>/dev/null)" cd=$tmp/lossless-jpc.ppm check jasper/decode-jpc-lossless jasper --input "$lc" --output "$cd" --output-format pnm check jasper/roundtrip-jpc-exact cmp -s "$fix" "$cd" # A codec that is deterministic is what makes a stored reference an oracle at # all, so it is asserted rather than assumed. jasper --input "$fix" --output "$tmp/lossless2.jp2" --output-format jp2 -O mode=int 2>/dev/null check jasper/encode-determinism cmp -s "$lj" "$tmp/lossless2.jp2" # --------------------------------------------- the irreversible path: BOUNDS --- # rate=0.1 is ~10% of the raw sample budget: enough loss that the bounds are # measuring something. rate=0.5 is the near-lossless end. Measured 2026-08-16 on # the reference build against this fixture, worst component of three: # rate=0.1 pae = 33 rmse = 4.200 # rate=0.5 pae = 1 rmse = 0.090 # The bounds below are ~2x the measured values. A real fault -- a broken inverse # wavelet, a wrong dequantizer, a component-order swap -- moves these by # hundreds, not by twos. lo=$tmp/lossy.jp2; lod=$tmp/lossy.ppm hi=$tmp/near.jp2; hid=$tmp/near.ppm check jasper/encode-lossy jasper --input "$fix" --output "$lo" \ --output-format jp2 -O rate=0.1 check_eq jasper/encode-lossy-size 1228 "$(size "$lo")" if [ "$have_md5" -eq 1 ]; then # The one pin on a floating-point path. See READING A RED above. check_eq jasper/encode-lossy-digest fe8312aeec6d8c206c582fa8f7ab0b9e "$(md5of "$lo")" else bst_skip jasper/encode-lossy-digest "no md5sum on this image" fi check jasper/decode-lossy jasper --input "$lo" --output "$lod" --output-format pnm jasper --input "$fix" --output "$hi" --output-format jp2 -O rate=0.5 2>/dev/null jasper --input "$hi" --output "$hid" --output-format pnm 2>/dev/null # A lossy codec that returned its input would pass every bound below. Asserting # that it did NOT is what makes those bounds mean anything. if cmp -s "$fix" "$lod"; then bst_notok jasper/lossy-is-lossy "rate=0.1 decode is byte-identical to the source" else bst_ok jasper/lossy-is-lossy fi if [ "$have_cmp" -eq 1 ]; then # The exact gate, restated in the codec's own metric. cmp says "the bytes # differ"; pae=0 on every component says "no SAMPLE moved", which is the # claim the reversible wavelet actually owes. lpae=$(pae_max "$fix" "$ld") check_eq jasper/roundtrip-jp2-pae-zero 0 "${lpae:-unreadable}" check_eq jasper/imgcmp-components 3 "$(ncomps "$fix" "$ld")" qpae=$(pae_max "$fix" "$lod"); qrmse=$(rmse_max_milli "$fix" "$lod") hpae=$(pae_max "$fix" "$hid"); hrmse=$(rmse_max_milli "$fix" "$hid") echo "==JASPER-OBSERVED r0.1pae=${qpae:-?} r0.1rmse_milli=${qrmse:-?} r0.5pae=${hpae:-?} r0.5rmse_milli=${hrmse:-?} lossless=$(md5of "$lj") jpc=$(md5of "$lc") lossy=$(md5of "$lo")==" if [ -n "$qpae" ] && [ "$qpae" -le 64 ]; then bst_ok jasper/lossy-pae-bound else bst_notok jasper/lossy-pae-bound "peak absolute error ${qpae:-unreadable} > 64 at rate=0.1" fi if [ -n "$qrmse" ] && [ "$qrmse" -le 8000 ]; then bst_ok jasper/lossy-rmse-bound else bst_notok jasper/lossy-rmse-bound "rmse ${qrmse:-unreadable}/1000 > 8000/1000 at rate=0.1" fi if [ -n "$hpae" ] && [ "$hpae" -le 4 ]; then bst_ok jasper/near-lossless-pae-bound else bst_notok jasper/near-lossless-pae-bound "peak absolute error ${hpae:-unreadable} > 4 at rate=0.5" fi # The one relation between the two rates that no single pinned number can # express: more bits must buy a closer picture. if [ -n "$hrmse" ] && [ -n "$qrmse" ] && [ "$hrmse" -lt "$qrmse" ]; then bst_ok jasper/quality-ordering else bst_notok jasper/quality-ordering "rate=0.5 rmse ${hrmse:-?} not below rate=0.1 rmse ${qrmse:-?}" fi else for c in roundtrip-jp2-pae-zero imgcmp-components lossy-pae-bound \ lossy-rmse-bound near-lossless-pae-bound quality-ordering; do bst_skip jasper/$c "imgcmp not on this image" done fi # ------------------------------------------------- malformed input: REFUSALS --- # A codec that accepts garbage is a security question, not a cosmetic one, and # JPEG-2000 decoders have a long CVE history, so the refusals are pinned as # tightly as the successes. THE THREE CASES ARE DELIBERATELY DIFFERENT DEPTHS OF # DAMAGE and produce three different diagnostics -- a suite that asserted one # message for all three would pass against a decoder that had collapsed them # into a single generic error and lost the ability to say where it stopped. head -c 1200 "$lj" > "$tmp/trunc-entropy.jp2" 2>/dev/null head -c 100 "$lj" > "$tmp/trunc-header.jp2" 2>/dev/null printf 'not a jp2 at all\n' > "$tmp/junk.jp2" # Cut inside the entropy-coded data: the markers parse, the packets do not. check_rc jasper/truncated-entropy-rc 1 \ jasper --input "$tmp/trunc-entropy.jp2" --output "$tmp/t1.ppm" --output-format pnm check_out jasper/truncated-entropy-diagnostic "cannot decode code stream" \ sh -c "jasper --input '$tmp/trunc-entropy.jp2' --output '$tmp/t1.ppm' --output-format pnm 2>&1; exit 0" # Cut before the codestream markers: there is nothing to recover from. check_rc jasper/truncated-header-rc 1 \ jasper --input "$tmp/trunc-header.jp2" --output "$tmp/t2.ppm" --output-format pnm check_out jasper/truncated-header-diagnostic "cannot get marker segment" \ sh -c "jasper --input '$tmp/trunc-header.jp2' --output '$tmp/t2.ppm' --output-format pnm 2>&1; exit 0" # Not an image at all: refused at format detection, before any codec runs. check_rc jasper/junk-rc 1 \ jasper --input "$tmp/junk.jp2" --output "$tmp/t3.ppm" --output-format pnm check_out jasper/junk-diagnostic "unknown format" \ sh -c "jasper --input '$tmp/junk.jp2' --output '$tmp/t3.ppm' --output-format pnm 2>&1; exit 0" # "A DIAGNOSTIC, NOT A CRASH" IS TWO CLAIMS AND check_rc ONLY MAKES ONE OF THEM. # A shell reports a killed child as 128+signal, so a decoder that segfaulted on # the truncated file would exit 139 -- which is non-zero, and would satisfy any # gate that only asked for failure. Assert the range explicitly. for pair in "entropy:$tmp/trunc-entropy.jp2" "header:$tmp/trunc-header.jp2" "junk:$tmp/junk.jp2"; do nm=${pair%%:*}; fl=${pair#*:} jasper --input "$fl" --output /dev/null --output-format pnm >/dev/null 2>&1 rc=$? if [ "$rc" -gt 0 ] && [ "$rc" -lt 128 ]; then bst_ok jasper/no-crash-$nm else bst_notok jasper/no-crash-$nm "exit $rc -- 128+ means killed by a signal, not refused" fi done # imginfo is a separate program over the same reader and must refuse too: it is # the tool anything scripted would reach for first, on an untrusted file. check_rc jasper/imginfo-rejects-truncated 1 imginfo -f "$tmp/trunc-entropy.jp2" check_rc jasper/imginfo-rejects-junk 1 imginfo -f "$tmp/junk.jp2" # ------------------------------------------------------- the other codecs ------ # BMP and RAS are compiled in by the recipe's defaults, so they cost nothing to # have and are worth asserting rather than assuming. PGX is single-component by # definition, so refusing a colour image is CORRECT behaviour and is pinned as # such -- a build that accepted it would be the defect. check jasper/encode-bmp jasper --input "$fix" --output "$tmp/out.bmp" --output-format bmp check_eq jasper/encode-bmp-size 12342 "$(size "$tmp/out.bmp")" check_eq jasper/imginfo-bmp "bmp 3 64 64 8 12288" "$(imginfo -f "$tmp/out.bmp" 2>/dev/null)" check_rc jasper/pgx-rejects-color 1 \ jasper --input "$fix" --output "$tmp/out.pgx" --output-format pgx check_out jasper/pgx-rejects-color-diagnostic "does not support color space" \ sh -c "jasper --input '$fix' --output '$tmp/out.pgx' --output-format pgx 2>&1; exit 0" # The format table jasper itself advertises. jpg is here because the recipe's # PACKAGECONFIG asks for libjpeg and this is where that DEPENDS becomes visible # as a capability rather than as a link line. h=$(jasper --help 2>&1) for fmt in pnm bmp ras pgx jpc jp2 jpg; do case "$h" in *"[enabled] $fmt "*) bst_ok jasper/format-$fmt ;; *) bst_notok jasper/format-$fmt "$fmt not reported as enabled by jasper --help" ;; esac done # Shape, not absence: this arm converts to shared libraries per recipe # (BRINTOS-1740), so a .so here is a conversion, not a policy breach. bst_lib_shape jasper/static-only "/usr/lib/libjasper.so*" if [ -f /usr/lib/libjasper.a ]; then bst_ok jasper/archive else bst_notok jasper/archive "/usr/lib/libjasper.a is missing"; fi if [ -f /usr/include/jasper/jasper.h ] && [ -f /usr/include/jasper/jas_config.h ]; then bst_ok jasper/headers else bst_notok jasper/headers "jasper.h or jas_config.h missing from /usr/include/jasper" fi for p in jasper imginfo imgcmp; do if command -v $p >/dev/null 2>&1; then bst_ok jasper/program-$p else bst_notok jasper/program-$p "$p is not on PATH"; fi done rm -rf "$tmp"