. "$BST_LIB" # BST-TIMEOUT: 300 # one probe run plus ~90 shell extractions; the 60s default # is for suites that fork far less than this one does. # libICE 1.1.1 -- the Inter-Client Exchange library (poky # recipes-graphics/xorg-lib). See libice_%.bbappend for what the port adds and # why. # # --------------------------------------------------------------------------- # THE PACKAGE INSTALLS NOTHING THAT RUNS, AND UPSTREAM SHIPS NO TEST AT ALL # # What lands is /usr/lib/libICE.a (with libICE.so.6 beside it once the recipe # converts), six headers under /usr/include/X11/ICE and # /usr/lib/pkgconfig/ice.pc. There is no executable. Unlike libXau -- which at # least ships a no-op Autest.c -- libICE has no check_PROGRAMS and no test # directory whatsoever, so there is not even a candidate to reject. On an image # with no compiler and no session manager, a shell suite alone could say only # that an archive of some size arrived. # # That is not enough for the member of this epic that libSM, and through it # every session-managed toolkit, is about to be built on. libICE can be # complete, installed, and still frame a message with its length field one # 8-byte unit out on this arm -- which would surface as a session manager # desynchronising after the first variable-length message, months later, with # nothing here having gone red. # # THE PORT THEREFORE BUILDS THE MISSING CONSUMER. The recipe compiles # libice_probe.c against ${B}/src/.libs/libICE.a -- the archive it is about to # package, not the sysroot's possibly-stale copy -- and ships it as # libice-tests. This suite drives it. # # WHY MOST OF THE PACKAGE IS REACHABLE HERE, WHICH IS UNUSUAL FOR THIS EPIC. # The epic's standing caveat is that nothing on this image can open a display, # so a port is asserted by "it fails honestly". Three quarters of libICE escapes # that, because ICE is FRAMING over a transport and the framing does not involve # the transport: # # * struct _IceConn is a PUBLIC declaration in ICEconn.h, not an opaque # handle, and IceGetHeader and its siblings are macros touching only # outbuf/outbufptr/outbufmax and send_sequence. A zeroed struct with a # buffer of the probe's own drives the entire message-write path. # * the protocol registration table is a process-local array of 255 slots with # a documented, fully deterministic opcode allocation. # * the .ICEauthority reader, writer and lock are pure file arithmetic. # # Only section 11 is the "fails honestly" kind, and it is small on purpose. # # WHERE THE EXPECTATIONS COME FROM, WHICH IS NOT ONE PLACE. # # * Sections 8 and 9 (the .ICEauthority bytes and the name rule) are pinned to # a NATIVE x86-64 run of this same source against libICE 1.1.1, made on # 2026-08-27 and recorded in this ticket's design doc. That file's entry is # five fields, each a two-byte BIG-ENDIAN length followed by its bytes -- # the same shape libXau's .Xauthority uses and fixed across every # implementation -- so the two architectures must agree byte for byte. Round # -tripping alone would not say that: a writer and a reader that share a # mistake agree with each other perfectly. # * Sections 5 and 7 (the struct ABI and the opcode allocation) state their # answers OUTRIGHT, because they have absolute ones. The sizes are what # ICEproto.h's sz_ constants declare and what every length field is computed # from with SIZEOF(); the opcode allocation is read off register.c. Both are # right or wrong on any arm, which is the sharper assertion of the two, and # neither rots when the build host moves. # * Section 6 (the header hex) is the x86-64 run again, WITH A CAVEAT THAT # MATTERS: ICE writes multi-byte fields in the SENDER'S byte order and # negotiates with a ByteOrder message, so these bytes are not # endianness-neutral the way section 8's are. Both arms are little-endian, # which is what makes the comparison valid; it is stated here rather than # left for a reader to assume a fixed wire format that is not one. # # --------------------------------------------------------------------------- # READ A RED IN SECTION ORDER; THE FIRST SECTION TO GO RED LOCALISES THE DEFECT # # 1 the files arrived packaging only # 2 the .pc resolves consumers find the library at all # 3 the probe runs to its end the compiled-in code executes here # 4 the protocol constants the headers a consumer compiles to # 5 the message struct ABI sizeof/offsetof against sz_ # 6 the header encode path the bytes a peer would receive # 7 the protocol registration table opcode allocation, no peer # 8 the .ICEauthority name rule $ICEAUTHORITY/$XDG/$HOME # 9 the .ICEauthority bytes the cross-arm oracle # 10 the authority lock link(2) on this filesystem # 11 connections that cannot be made the one arm that needs a peer # # A build where 3 passes and 5 does not has a struct-packing fault in the # toolchain; one where 5 passes and 6 does not has one in the header macros. 8 # failing alone is environment handling, 9 failing alone is the counted-string # path, and 9 failing while 8 passes is very likely a byte-order slip. 10 is the # only section whose red can be a property of the filesystem rather than of # libICE, and it says which when it is. # --------------------------------------------------------------------------- # A root prefix, empty on the target and set only when this suite is validated # NATIVELY under dash before it ships. The runner never sets it, so on the image # every path below is exactly the absolute one. R="${BST_LIBICE_ROOT:-}" INC=$R/usr/include/X11/ICE LIB=$R/usr/lib IT=$R/usr/libexec/libice-tests work=${TMPDIR:-/tmp}/bst-libice.$$ rm -rf "$work"; mkdir -p "$work" hasfile() { if [ -s "$2" ]; then bst_ok "$1"; else bst_notok "$1" "missing or empty: $2"; fi } # ic_line # # The prefix is matched with grep -F rather than the whole line, so a red reports # WHAT THE PROBE ACTUALLY SAID for that case instead of merely "absent". Every # prefix below is unique by construction: each ends at a separator -- a space # before `sizeof=', a ` = ', or a `len=' -- so `IC HDR Error ' cannot match # inside `IC HDR ErrorSomething' and `IC LOOKUP hit = ' cannot match inside # `IC LOOKUP hit2 = '. ic_line() { _l=$(grep -m 1 -F -- "$2" "$work/out" 2>/dev/null) check_eq "$1" "$2$3" "$_l" } # --------------------------------------------------------------------------- # 0. THE CANARY, which is not an assertion about libICE at all. # # The guest console drops a suite's FIRST BST result line before the host parser # sees it -- brintos/linux#96. The harness computes its pass count as # total-minus-failures, so THE DROPPED LINE IS COUNTED AS A PASS, and a suite # whose first case is a genuine `not ok' loses the one line that mattered. So # the first line out of this file asserts something already true. # Remove this case when #96 is fixed. bst_ok libice/suite-canary # --------------------------------------------------------------------------- # 1. THE PAYLOAD -- the archive, all six headers by name, and the .pc. # # By name and not by count: /usr/include/X11 is shared with xorgproto and, once # the rest of this epic lands, with every other member. Each of these is # #included by name from some consumer's translation unit -- ICElib.h and # ICEconn.h by libSM directly -- so a missing one fails somebody else's # do_compile rather than anything on this image. check_rc libice/dir-ICE 0 test -d "$INC" hasfile libice/hdr-ICE-h "$INC/ICE.h" hasfile libice/hdr-ICEconn-h "$INC/ICEconn.h" hasfile libice/hdr-ICElib-h "$INC/ICElib.h" hasfile libice/hdr-ICEmsg-h "$INC/ICEmsg.h" hasfile libice/hdr-ICEproto-h "$INC/ICEproto.h" hasfile libice/hdr-ICEutil-h "$INC/ICEutil.h" hasfile libice/archive "$LIB/libICE.a" hasfile libice/pc "$LIB/pkgconfig/ice.pc" hasfile libice/probe-shipped "$IT/libice-probe" # Held static, the recipe ships no .so. BRINTOS-2848 converted it: libice now # carries libICE.so.6 beside the archive, which autotools keeps because # brintos-package-shared.bbclass appends --enable-static. So the old `no .so may # exist' assertion is the wrong question on either arm now; what survives # conversion is that whatever shipped is usable, which is bst_lib_shape. The id # is kept so the history reads continuously. bst_lib_shape libice/no-shared-object "$LIB/libICE.so*" # ICEconn.h is the header this suite's whole no-peer strategy rests on: it must # carry the FULL struct declaration and not a forward declaration, or a consumer # -- this probe included -- cannot size an IceConn at all. Asserted on contents # rather than size, because a truncated install would still be non-empty. check libice/iceconn-declares-struct grep -q 'struct _IceConn {' "$INC/ICEconn.h" # The header macros are the write path. ICEmsg.h having lost IceGetHeader would # leave every consumer to hand-assemble headers, silently. check libice/icemsg-has-getheader grep -q 'define IceGetHeader' "$INC/ICEmsg.h" # --------------------------------------------------------------------------- # 2. THE .pc -- how a consumer finds any of the above. # # NOT `require pkg-config'. require ENDS THE FILE when the tool is absent, and # everything this suite exists for -- sections 3 to 11 -- comes after this one. # Gating the probe on a query tool would mean an image without pkg-config # reported a clean skip for the framing layer it never exercised. if command -v pkg-config >/dev/null 2>&1; then bst_ok libice/pkg-config-present # `pkg-config --exists ice' also resolves ice.pc's `Requires: xproto', so this # case is simultaneously an assertion that xorgproto-dev is in this image. A # red here with section 1 green means the .pc chain broke, not libICE. check libice/pc-exists pkg-config --exists ice check libice/pc-validate pkg-config --validate ice check_eq libice/pc-modversion 1.1.1 "$(pkg-config --modversion ice 2>/dev/null)" # -lICE is the whole point of the .pc: a consumer that got an empty Libs line # would link nothing and fail at the first ICE symbol. check_out libice/pc-libs -lICE pkg-config --libs ice else bst_skip libice/pkg-config-present "pkg-config not installed on this image" fi # --------------------------------------------------------------------------- # 3. THE PROBE RUNS, AND RUNS TO ITS END. # # do_install ships it 0755, but the rootfs is read-only at browser runtime and # the exec bit is not reliably readable there either, so it is copied to /tmp -- # a mode=1777 tmpfs with no noexec -- and given the bit where the copy lands. probe=$work/libice-probe if cp "$IT/libice-probe" "$probe" 2>/dev/null && chmod +x "$probe" 2>/dev/null; then # HOME and TMPDIR are set for the run rather than inherited: section 8 asserts # what IceAuthFileName does with each of them, and the probe sets and unsets # them itself per case -- but a HOME already exported by the runner would # otherwise leak into the `nothing' case through the environment the probe # inherits. TMPDIR is where the authority fixtures are written, and it must be # the tmpfs: a fixture on the virtiofs root is a different measurement. ( HOME=/home/root TMPDIR=${TMPDIR:-/tmp} "$probe" >"$work/out" 2>"$work/err" ) rc=$? # The probe exits 0 whatever it measures -- every expectation lives in this # file, not in the binary -- so a non-zero status here means it did not RUN: a # trap, a missing import, an exec refusal. On this arm that is a real and # common failure and is what this case is for. if [ "$rc" -eq 0 ]; then bst_ok libice/probe-runs else bst_notok libice/probe-runs "rc=$rc $(_bst_1line "$(cat "$work/err")")"; fi total=$(sed -n 's/^IC-TOTAL //p' "$work/out") if [ -n "$total" ]; then bst_ok libice/probe-complete else bst_notok libice/probe-complete \ "no IC-TOTAL line: $(_bst_1line "$(cat "$work/out")")"; fi # The measurement count, bounded from below. Adding a fixture raises it; a # probe that got THINNER while still terminating cleanly is what this catches, # and nothing else here would. if [ -n "$total" ] && [ "$total" -ge 95 ]; then bst_ok libice/probe-coverage else bst_notok libice/probe-coverage \ "reported ${total:-0} measurements, expected not less than 95"; fi # ------------------------------------------------------------------------- # 4. THE PROTOCOL CONSTANTS. # # Constants, but constants every consumer compiles against and the only place # the minor opcode space is written down. A header that renumbered them would # put this client on a different protocol from the peer's while compiling # perfectly. ic_line libice/proto-constants "IC PROTO " \ "major=1 minor=0 error=0 byteorder=1 setup=2 reply=6 ping=9 noclose=12" # ------------------------------------------------------------------------- # 5. THE MESSAGE STRUCT ABI. # # sizeof against sz_, per type. These are the numbers every length field is # computed from with SIZEOF(), so a padding decision that made the two # disagree would put the length of every message of that type out by a # multiple of 8 -- and would fail no build anywhere. # # The values are stated outright rather than diffed against x86-64. They ARE # the same on both arms, because these structures are CARD8/CARD16/CARD32 only # and carry no pointer and no long -- but that is a fact worth asserting, not # a comparison worth making: on an ILP32 arm, agreement with an LP64 host is # the sort of thing that is true until it isn't. ic_line libice/sz-iceMsg "IC SZ iceMsg " "sizeof=8 sz=8" ic_line libice/sz-iceErrorMsg "IC SZ iceErrorMsg " "sizeof=16 sz=16" ic_line libice/sz-iceByteOrderMsg "IC SZ iceByteOrderMsg " "sizeof=8 sz=8" ic_line libice/sz-iceConnectionSetup "IC SZ iceConnectionSetupMsg " "sizeof=16 sz=16" ic_line libice/sz-iceAuthRequiredMsg "IC SZ iceAuthRequiredMsg " "sizeof=16 sz=16" ic_line libice/sz-iceAuthReplyMsg "IC SZ iceAuthReplyMsg " "sizeof=16 sz=16" ic_line libice/sz-iceAuthNextPhaseMsg "IC SZ iceAuthNextPhaseMsg " "sizeof=16 sz=16" ic_line libice/sz-iceConnectionReply "IC SZ iceConnectionReplyMsg " "sizeof=8 sz=8" ic_line libice/sz-iceProtocolSetupMsg "IC SZ iceProtocolSetupMsg " "sizeof=16 sz=16" ic_line libice/sz-iceProtocolReplyMsg "IC SZ iceProtocolReplyMsg " "sizeof=8 sz=8" # The interiors. A struct whose sizeof is right and whose fields moved is the # failure the sizes above cannot see -- and iceErrorMsg is where it would # matter most, since severity and errorClass are what a peer acts on. ic_line libice/off-iceMsg "IC OFF iceMsg " \ "major=0 minor=1 data=2 length=4" ic_line libice/off-iceErrorMsg "IC OFF iceErrorMsg " \ "class=2 length=4 offmin=8 sev=9 offseq=12" ic_line libice/off-iceConnectionSetup "IC OFF iceConnectionSetupMsg " \ "vercnt=2 authcnt=3 length=4 mustauth=8" ic_line libice/off-iceProtocolSetup "IC OFF iceProtocolSetupMsg " \ "proto=2 mustauth=3 length=4 vercnt=8 authcnt=9" # ------------------------------------------------------------------------- # 6. THE HEADER ENCODE PATH -- one case per message type, then the bytes. # # `length' is a COUNT OF 8-BYTE UNITS BEYOND THE 8-BYTE BASE HEADER, which is # why a 16-byte header carries 1 and an 8-byte one carries 0. `used' is how # far the output pointer advanced, and it must equal the header size exactly: # a pointer that advanced further would leave a gap the peer reads as the next # message's opcode. `seq' is the per-message sequence counter, which the # peer's error replies are matched against. # # THE HEX IS LITTLE-ENDIAN AND THAT IS NOT A WIRE-FORMAT CLAIM. ICE writes # multi-byte fields in the sender's byte order and negotiates with a ByteOrder # message. Both this arm and the x86-64 reference are little-endian, which is # what makes the pinned bytes comparable; on a big-endian port these would # legitimately differ and the sizes and offsets above would be the assertion # that survived. ic_line libice/hdr-error "IC HDR Error " "major=0 minor=0 length=1 used=16 seq=1" ic_line libice/hdr-byteorder "IC HDR ByteOrder " "major=0 minor=1 length=0 used=8 seq=1" ic_line libice/hdr-connsetup "IC HDR ConnectionSetup " "major=0 minor=2 length=1 used=16 seq=1" ic_line libice/hdr-authrequired "IC HDR AuthRequired " "major=0 minor=3 length=1 used=16 seq=1" ic_line libice/hdr-authreply "IC HDR AuthReply " "major=0 minor=4 length=1 used=16 seq=1" ic_line libice/hdr-authnextphase "IC HDR AuthNextPhase " "major=0 minor=5 length=1 used=16 seq=1" ic_line libice/hdr-connreply "IC HDR ConnectionReply " "major=0 minor=6 length=0 used=8 seq=1" ic_line libice/hdr-protosetup "IC HDR ProtocolSetup " "major=0 minor=7 length=1 used=16 seq=1" ic_line libice/hdr-protoreply "IC HDR ProtocolReply " "major=0 minor=8 length=0 used=8 seq=1" ic_line libice/hdr-ping "IC HDR Ping " "major=0 minor=9 length=0 used=8 seq=1" ic_line libice/hdr-pingreply "IC HDR PingReply " "major=0 minor=10 length=0 used=8 seq=1" ic_line libice/hdr-wanttoclose "IC HDR WantToClose " "major=0 minor=11 length=0 used=8 seq=1" ic_line libice/hdr-noclose "IC HDR NoClose " "major=0 minor=12 length=0 used=8 seq=1" # A non-zero major opcode -- what every message of a protocol RIDING on ICE # carries. Each core case above is major 0 by construction, so a build that # dropped the majorOpcode assignment would pass all thirteen of them. ic_line libice/hdr-nonzero-major "IC HDR Proto42 " "major=42 minor=7 length=0 used=8 seq=1" # The bytes. Two representative headers of each size, plus the non-zero-major # one, rather than all fifteen: the per-type fields are already asserted above, # and what the hex adds is that they were laid down at the right OFFSETS -- # which one 8-byte and one 16-byte case establishes. ic_line libice/wire-ping "IC WIRE Ping len=8 " "0009000000000000" ic_line libice/wire-error "IC WIRE Error len=16 " "00000000010000000000000000000000" ic_line libice/wire-connsetup "IC WIRE ConnectionSetup len=16 " "00020000010000000000000000000000" ic_line libice/wire-proto42 "IC WIRE Proto42 len=8 " "2a07000000000000" # IceSimpleMessage -- the wrapper the ping and close paths use. It must # produce a header identical to the Ping case above and advance the pointer by # exactly one base header. ic_line libice/simple-message "IC SIMPLE " "used=8 seq=1" ic_line libice/wire-simple "IC WIRE Simple len=8 " "0009000000000000" # IceGetHeaderExtra -- the variable-length path every ConnectionSetup and # ProtocolSetup takes, since both carry strings after the fixed header. # `extra' is in 8-byte units too, so length is the header's own count PLUS # extra (1+3), the pointer advances by 16+24, and the data pointer must land # exactly at the end of the fixed header. A dataoff that was not 16 would have # the caller write its strings over the header it just built. ic_line libice/header-extra "IC EXTRA " "length=4 used=40 dataoff=16 seq=1" # IceErrorHeader -- the only place severity, errorClass and the offending # sequence number are laid down, and the one macro that writes more than the # base header. dataLength adds to the length field on top of the header's own # count (1+2), exactly as in the Extra case. ic_line libice/error-header "IC ERR " \ "major=42 minor=0 class=0x8003 length=3 offmin=7 sev=1 offseq=0x11223344" ic_line libice/wire-error-full "IC WIRE Error-full len=16 " \ "2a000380030000000701000044332211" # The sequence counter is per-header and monotonic. An increment lost here # surfaces as a mismatched error reply months later and never as a build # failure, so it is asserted over five messages rather than one. ic_line libice/sequence-counter "IC SEQ " "after5=5 used=40" # The two public accessors that read the buffer geometry back. They are the # only supported way a consumer sizes a write, so they must agree with the # buffer this connection was actually given -- 1024, which is also libICE's # own ICE_OUTBUFSIZE. ic_line libice/buffer-geometry "IC BUFSIZE " "out=1024 in=1024" # ------------------------------------------------------------------------- # 7. THE PROTOCOL REGISTRATION TABLE. # # IceRegisterForProtocolSetup allocates the MAJOR OPCODE a protocol uses on # every connection this process opens. Opcodes are handed out from 1 upward; a # name already registered for the same role returns its existing opcode rather # than a second one; and the originating and accepting halves of one protocol # SHARE a slot. Getting the last of those wrong gives two protocols the same # opcode, which is a wire-level collision no build catches and no single- # protocol client ever notices. # # The order of the cases is the whole of the fixture: each answer follows from # the ones before it, so a red here also says WHICH rule moved. ic_line libice/reg-first "IC REG po-first = " "1" ic_line libice/reg-second "IC REG po-second = " "2" ic_line libice/reg-repeat "IC REG po-repeat = " "1" ic_line libice/reg-role-shares-slot "IC REG pa-share = " "1" ic_line libice/reg-new-acceptor "IC REG pa-new = " "3" # The two documented refusals, both checked only on the path that would # allocate a NEW slot. ic_line libice/reg-no-versions "IC REG po-noversions = " "-1" ic_line libice/reg-empty-name "IC REG po-emptyname = " "-1" # And a refusal must not consume a slot. A build that incremented before # validating would answer 5 here and would leak one opcode per bad # registration out of a space of 255. ic_line libice/reg-refusal-consumes-nothing "IC REG po-after-refusals = " "4" # ------------------------------------------------------------------------- # 8. THE .ICEauthority NAME RULE. # # $ICEAUTHORITY outright, else $XDG_RUNTIME_DIR with the dot DROPPED from the # basename, else $HOME with the dot kept, else NULL. This is what every ICE # client runs before it opens anything, and a client that computed a different # name from every other client on the machine would find no cookie and fail to # authenticate -- with no error that names the cause. ic_line libice/name-iceauthority-wins "IC NAME iceauthority-wins = " "/probe/explicit.ice" ic_line libice/name-xdg-drops-dot "IC NAME xdg-nodot = " "/probe/xdg/ICEauthority" ic_line libice/name-home-keeps-dot "IC NAME home-dot = " "/probe/home/.ICEauthority" # THE UPSTREAM ODDITY, ASSERTED AS-IS. The dot is dropped BEFORE the empty # check, so an XDG_RUNTIME_DIR set to "" yields "$HOME/ICEauthority" with no # dot. That is upstream's behaviour in a public repository this project files # nothing against (docs/conventions/no-public-upstream-reports.md), and it is # pinned rather than corrected: a port that "fixed" it would disagree with # every other ICE client on the same machine, which is strictly worse than # agreeing on something odd. ic_line libice/name-xdg-empty-loses-dot "IC NAME xdg-empty-home-nodot = " "/probe/home/ICEauthority" # HOME="/" takes the special case that skips a character, so the answer is # "/.ICEauthority" and not "//.ICEauthority". ic_line libice/name-home-root "IC NAME home-root = " "/.ICEauthority" # Nothing to build a name from, by two different branches -- unset, and set to # the empty string. Both must be NULL: a name synthesised from a missing $HOME # would have a client write a cookie somewhere nothing looks. ic_line libice/name-nothing "IC NAME nothing = " "(null)" ic_line libice/name-home-empty "IC NAME home-empty = " "(null)" # ------------------------------------------------------------------------- # 9. THE .ICEauthority BYTES -- the cross-arm oracle. # # Five fields per entry, each a two-byte BIG-ENDIAN length followed by that # many bytes: protocol name, protocol data, network id, auth name, auth data. # Big-endian regardless of the host, exactly as in .Xauthority -- which is why # 57-libxau.test and this one are a genuine cross-check on each other, two # independent implementations of the same counted-string convention in one # image. # # THE HEX IS THE ASSERTION A TARGET-ONLY RUN CANNOT MAKE. Writing an entry and # reading it back proves the writer and the reader are CONSISTENT; it does not # prove they are RIGHT. A port emitting the counts in host byte order would # round-trip through its own mistake and report every field correct. These # bytes are what libICE 1.1.1 wrote from the same fixtures on x86-64 on # 2026-08-27. ic_line libice/auth-write-a "IC AUTH write-a = " "1" ic_line libice/auth-write-b "IC AUTH write-b = " "1" # 56 bytes for the first entry and 30 for the second, and the total is the # arithmetic those two field lists imply. A file one byte short or long means # a length or a pad moved. ic_line libice/auth-filesize "IC AUTH filesize = " "86" ic_line libice/auth-bytes "IC WIRE authfile len=86 " \ "000349434500040001feff000f6c6f63616c2f6272696e746f733a3000124d49542d4d414749432d434f4f4b49452d310006deadbeef007f000458534d500000" # The reader's parse of what the writer just laid down. Both entries, because # the second exercises the ZERO-LENGTH arms of both counted-string fields -- # the branches read_counted_string returns a NULL pointer for, and which a # naive reader turns into a one-byte allocation it then reads past. ic_line libice/auth-entry0 "IC AUTH entry0 " \ "proto=ICE plen=4 nid=local/brintos:0 auth=MIT-MAGIC-COOKIE-1 alen=6" ic_line libice/auth-entry0-data "IC WIRE entry0-authdata len=6 " "deadbeef007f" ic_line libice/auth-entry1 "IC AUTH entry1 " \ "proto=XSMP plen=0 nid=tcp/brintos:7100 auth= alen=0" ic_line libice/auth-entry1-data "IC AUTH entry1-authdata = " "null" # A read past the end must fail rather than invent an entry from whatever the # buffer held. ic_line libice/auth-eof "IC AUTH entry-eof = " "null" # EVERY PREFIX SHORTER THAN A WHOLE ENTRY IS REFUSED, AND THE BOUNDARY IS A # NUMBER. 85 proper prefixes of the 86-byte file are tried; the first accepted # must be at exactly 56 bytes -- the length of the first entry -- and the 30 # prefixes at or past that boundary are all accepted, which is correct rather # than a leak: a complete first entry followed by a truncated second is what a # file interrupted mid-append looks like, and the reader is called once per # entry. # # Pinning the boundary, not just "some are refused", is what catches a reader # that stopped one field early: it would still refuse the short prefixes, still # return an entry, and move firstok. ic_line libice/auth-truncation "IC TRUNC " "prefixes=85 firstok=56 accepted=30" # IceGetAuthFileEntry -- the lookup every ICE client performs before it # authenticates. ALL THREE of protocol name, network id and auth name must # match; a build matching on fewer would hand out a cookie belonging to # another endpoint, which is a security property and not a convenience. ic_line libice/lookup-hit "IC LOOKUP hit = " "local/brintos:0 alen=6" ic_line libice/lookup-hit2 "IC LOOKUP hit2 = " "tcp/brintos:7100 plen=0" ic_line libice/lookup-miss-auth "IC LOOKUP miss-auth = " "null" ic_line libice/lookup-miss-nid "IC LOOKUP miss-nid = " "null" ic_line libice/lookup-absent-file "IC LOOKUP absent-file = " "null" # ------------------------------------------------------------------------- # 10. THE AUTHORITY LOCK -- the one section whose answer is the filesystem's. # # IceLockAuthFile is creat("-c") then link(2) to "-l", with NO # rename fallback -- unlike libXau's XauLockAuth, which has one. So this is a # direct measurement of whether hard links work under $TMPDIR, and the two # outcomes are BOTH correct ports: # # IceAuthLockSuccess (0) link(2) works. The second lock must then fail, and # fail as IceAuthLockTimeout (2) after one attempt. # IceAuthLockError (1) link(2) refused. Documented, errno set, and the # right answer on a filesystem without hard links. # # The suite branches on what the probe reported rather than demanding one, so # a correct port on either filesystem is green and a lock that succeeded twice # -- which is the real defect, two clients writing one authority file -- is red # on either. lock1=$(sed -n 's/^IC LOCK first = \([0-9-]*\) .*/\1/p' "$work/out") lock2=$(sed -n 's/^IC LOCK second = \([0-9-]*\) .*/\1/p' "$work/out") case "$lock1" in 0) bst_ok libice/lock-mechanism-link ic_line libice/lock-files-created "IC LOCK files " "creat=1 link=1" check_eq libice/lock-excludes-second 2 "$lock2" ic_line libice/lock-unlock-removes "IC LOCK unlocked " "creat=0 link=0" ;; 1) # Reported, not scored as a defect. Named so a reader of the capture knows # which world this image is in -- and so that a LATER image growing hard # links flips this to the branch above rather than silently changing what # the suite asserts. bst_skip libice/lock-mechanism-link \ "link(2) unavailable under \$TMPDIR on this image; IceLockAuthFile returned IceAuthLockError as documented" check_eq libice/lock-excludes-second 1 "$lock2" ;; *) bst_notok libice/lock-mechanism-link \ "IceLockAuthFile returned '${lock1:-}', which is neither IceAuthLockSuccess (0) nor IceAuthLockError (1)" bst_notok libice/lock-excludes-second "first lock did not resolve, so the second is unjudgeable" ;; esac # retries=0 never enters the loop, so the documented answer is a timeout # without a single attempt. It is the one lock case whose answer does not # depend on the filesystem at all -- which is why it is outside the branch. ic_line libice/lock-no-retries "IC LOCK noretries = " "2" # The length guard: the two derived names are built into fixed 1025-byte # buffers, so a file name over 1022 bytes is refused BEFORE any filesystem # call. A build that lost this check would smash two stack buffers. ic_line libice/lock-name-too-long "IC LOCK toolong = " "1" # MIT-MAGIC-COOKIE-1 generation. The content is random by design; what is # assertable is the shape -- a non-NULL buffer with a NUL one past the # requested length, which every caller relies on when it passes the cookie # around as a string. ic_line libice/cookie-shape "IC COOKIE c1=" "nonnull c2=nonnull nul1=1 nul2=1" # WHETHER TWO COOKIES DIFFER IS REPORTED AND NOT ASSERTED. Where configure # found neither arc4random_buf nor getentropy, libICE falls back to # srand(gettimeofday) -- so two calls inside one microsecond legitimately # produce identical bytes, and a case demanding difference would go red on a # correct build of a weaker platform. What is worth having is WHICH world this # image is in, so the line is extracted and named rather than scored. distinct=$(sed -n 's/^IC COOKIE distinct = //p' "$work/out") case "$distinct" in yes) bst_ok libice/cookie-entropy ;; no) bst_skip libice/cookie-entropy \ "two cookies matched: this build took libICE's srand(gettimeofday) fallback, not arc4random_buf/getentropy" ;; *) bst_notok libice/cookie-entropy "IceGenerateMagicCookie returned NULL or the line is missing (got '${distinct:-}')" ;; esac # ------------------------------------------------------------------------- # 11. CONNECTIONS THAT CANNOT BE MADE -- the one arm that needs a peer. # # Every case is a DOCUMENTED failure path, and the error string is the # assertion: an identical NULL would also be returned by a function that gave # up before reaching the branch it is supposed to. Two of these return before # any socket(2) at all, which is why they are safe in a guest with no network. ic_line libice/open-null-ids "IC OPEN null-ids " "conn=null err='networkIdsList argument is NULL'" ic_line libice/open-empty-ids "IC OPEN empty-ids " "conn=null err='networkIdsList argument is NULL'" # The same message into an 8-byte buffer. strncpy fills to the limit and the # last byte is forced to NUL, so the answer is the first errorLength-1 # characters -- a contract a caller sizing a stack buffer depends on, and one # a port could break without failing anything else on this image. ic_line libice/open-error-truncates "IC OPEN null-ids-trunc8 " "conn=null err='network'" # A syntactically valid network id whose transport is not in the table this # build compiled, and an address the parser itself refuses. Both stop inside # xtrans -- the same two refusals 57-xtrans.test drives one layer down -- and # both surface here as libICE's own single message. ic_line libice/open-unknown-transport "IC OPEN no-such-transport " \ "conn=null err='Could not open network socket'" ic_line libice/open-unparseable "IC OPEN garbage-ids " \ "conn=null err='Could not open network socket'" # THE ONE CASE THAT ACTUALLY TRIES TO CONNECT, and it is a local socket to a # path nothing has created -- so it fails at connect(2) immediately rather # than waiting on anything. `local' and not `tcp' deliberately: a tcp id would # resolve a name, and this image may have neither resolver nor network. ic_line libice/open-nothing-listening "IC OPEN local-nothing-listening " \ "conn=null err='Could not open network socket'" # The two refusals IceComposeNetworkIdList documents, which need no listener # and are therefore answerable on any image. ic_line libice/compose-zero "IC COMPOSE zero = " "null" ic_line libice/compose-negative "IC COMPOSE negative = " "null" # WHETHER THIS IMAGE CAN LISTEN AT ALL IS THE MEASUREMENT, NOT AN EXPECTATION. # IceListenForConnections is the only supported way to obtain a listen object, # so IceComposeNetworkIdList can only be exercised over real transports. On a # guest with no working socket layer the documented answer is a zero Status # with a message -- a correct port on a limited platform. The suite therefore # branches on what the probe reported, exactly as section 10 does for link(2), # and asserts the SHAPE of the composition wherever there was one to compose: # one comma fewer than there are ids, which is the property a session # manager's SESSION_MANAGER variable depends on. lst=$(sed -n 's/^IC LISTEN status=\([0-9-]*\) count=\([0-9-]*\) .*/\1 \2/p' "$work/out") lstatus=${lst%% *}; lcount=${lst##* } if [ "${lstatus:-0}" != "0" ] && [ "${lcount:-0}" -gt 0 ]; then bst_ok libice/listen-available ic_line libice/compose-shape "IC LISTEN list = nonnull " \ "commas=$((lcount - 1)) ids=$lcount" # Every listen object must carry a network id of the documented # proto/host:port shape and a usable descriptor; a composition over ids that # were not that shape would produce a SESSION_MANAGER nothing can parse. i=0; bad=0 while [ "$i" -lt "$lcount" ]; do l=$(grep -m 1 -F -- "IC LISTEN obj$i " "$work/out" 2>/dev/null) case "$l" in *"shape=proto/host:port") ;; *) bad=$((bad + 1)) ;; esac i=$((i + 1)) done if [ "$bad" -eq 0 ]; then bst_ok libice/listen-id-shape else bst_notok libice/listen-id-shape "$bad of $lcount listen objects reported an id that is not proto/host:port"; fi elif [ "${lstatus:-0}" = "0" ]; then bst_skip libice/listen-available \ "IceListenForConnections created no listener on this image: $(sed -n 's/^IC LISTEN status=[0-9-]* count=[0-9-]* //p' "$work/out" | head -1)" bst_skip libice/compose-shape "no listener, so there is no id list to compose" bst_skip libice/listen-id-shape "no listener, so there are no ids to shape-check" else bst_notok libice/listen-available \ "IceListenForConnections reported success with count=${lcount:-} -- a non-zero Status must come with at least one listen object" bst_notok libice/compose-shape "listener count is inconsistent, so the composition is unjudgeable" bst_notok libice/listen-id-shape "listener count is inconsistent, so the ids are unjudgeable" fi else bst_notok libice/probe-stage "cannot stage $IT/libice-probe" fi rm -rf "$work"