. "$BST_LIB" # BST-TIMEOUT: 300 # # rabbitmq-c 0.13.0 (meta-oe recipes-connectivity/rabbitmq-c) -- the C client # library for AMQP 0-9-1, the protocol RabbitMQ speaks. A codec, a connection # state machine and a socket layer, plus five command-line clients. # # THE TICKET'S CRITERION IS OFFLINE, AND THAT IS A CHOICE ABOUT WHAT CAN BE # ASSERTED HERE RATHER THAN A GAP. Three halves, and none of them needs a broker: # # 1. amqp_table field tables encoded and decoded over memory buffers, with the # round trip asserted lossless; # 2. AMQP frames built with the library's own encoders and parsed back through # amqp_handle_input(), which is the real inbound path -- amqp_simple_wait_frame # is a read(2) wrapper around it; # 3. amqp_socket_open against a CLOSED loopback port returning the documented # error rather than hanging. # # No AMQP broker is on this image and none is planned; when one lands this suite # can be extended with a live publish/consume round trip on loopback. It does NOT # depend on one to pass, and nothing below should be rewritten to. # # AND NONE OF IT NEEDS A THREAD, which is why this package is in this batch at # all. librabbitmq is single-threaded by design and starts no thread anywhere -- # its unix/threads.h is a mutex shim guarding OpenSSL's locking callbacks -- so on # a platform that refuses a thread-mode clone (brintos/linux-in-the-browser#85) # it is exercisable where the brokers it talks to are not. # # THE CODEC IS DRIVEN THROUGH ONE PROGRAM, rabbitmq-c-probe, THOUGH THE PACKAGE # SHIPS FIVE OF ITS OWN. All five need a live broker in their first two seconds: # amqp-publish, amqp-get, amqp-consume, amqp-declare-queue and amqp-delete-queue # each open a connection before doing anything, so they can say that the package # built and linked and nothing more. They are asserted as present and as parsing # their own options below; the criterion is asserted by the probe. Unlike the # sibling ports, everything the probe calls is public -- amqp_encode_table, # amqp_decode_table, amqp_encode_method, amqp_encode_properties and # amqp_handle_input are all declared AMQP_EXPORT in the installed headers -- so a # red here is a statement about what the package installs. The probe is built # in-recipe against this build's own archive (rabbitmq-c_%.bbappend) and carries # the reasoning for each assertion in its own header comment. # # EVERY EXPECTATION IS PINNED FROM AN x86-64 NATIVE CONTROL, not guessed: the # same tree at the same SRCREV (974d71ad), configured with the same static-only # switches this port sets, built natively and driven through all three probe # modes on 2026-08-19. Every case below was green there, `table', `frame' and # `noconnect' each returning 0. So a red here is a statement about this platform. # (The native control ran with ENABLE_SSL_SUPPORT=OFF, because the Fedora 44 host # no longer ships the deprecated openssl/engine.h that amqp_openssl.c includes. # The target build keeps SSL on -- Yocto's openssl does ship that header -- and # no probe assertion touches TLS, so the control covers every case below.) # # THE PROBE IS EXEC'D IN PLACE, never copied into /tmp first. It is a # whole-program static link of librabbitmq; staging one through the tmpfs heap # fragments it past binfmt_wasm's WASM32_IMG_MAX_SEGS=1024 cap and the exec is # refused -ENOMEM (brintos/linux#44). 88-nopoll measured that both ways on target. require /usr/libexec/rabbitmq-c-tests/rabbitmq-c-probe rabbitmq-c/present PROBE=/usr/libexec/rabbitmq-c-tests/rabbitmq-c-probe # =========================================================================== # A. THE DELIVERABLE ITSELF # # The archive, the public headers and the five tools. Asserted before anything is # run against them, so "the port did not install" stays distinguishable from "the # port does not work". check rabbitmq-c/archive test -f /usr/lib/librabbitmq.a check rabbitmq-c/header-amqp test -f /usr/include/rabbitmq-c/amqp.h check rabbitmq-c/header-framing test -f /usr/include/rabbitmq-c/framing.h check rabbitmq-c/header-tcp test -f /usr/include/rabbitmq-c/tcp_socket.h check rabbitmq-c/header-ssl test -f /usr/include/rabbitmq-c/ssl_socket.h # export.h is GENERATED at configure time from whichever target was built, so its # presence is not merely another header: it is what says the static target became # the export target when the shared one was denied. check rabbitmq-c/header-export test -f /usr/include/rabbitmq-c/export.h # THE PORT'S CENTRAL BUILD DECISION, ASSERTED ON THE IMAGE. This distro links no # shared objects, so BUILD_SHARED_LIBS is denied and BUILD_STATIC_LIBS asked for # in its place. A red means a switch stopped applying and the package grew a # shared object that could not be loaded here anyway. check_eq rabbitmq-c/no-shared-library "" "$(ls /usr/lib/librabbitmq.so* 2>/dev/null)" # AMQP_STATIC IS THE HALF OF THAT DECISION THAT REACHES A CONSUMER. Under the # static target generate_export_header emits a STATIC_DEFINE arm, so a downstream # program compiled with -DAMQP_STATIC gets an empty AMQP_EXPORT; without that arm # in the header every entry point would carry a visibility attribute written for # a shared object. grep(1) rather than a compile, because the image ships no # compiler. check_eq rabbitmq-c/export-header-static "yes" \ "$(grep -q AMQP_STATIC /usr/include/rabbitmq-c/export.h && echo yes)" # THE TLS ARM IS COMPILED IN. The stock recipe leaves ENABLE_SSL_SUPPORT at its # ON default and DEPENDS on openssl, so amqp_openssl.o is a member of the archive # -- but the archive would exist either way, one object lighter. The symbol is # what says it. grep(1) over the archive rather than ar(1) or nm(1): the image # ships neither binutils nor a wasm32-aware ar, and elfutils' eu-ar is written # against ELF members while these are wasm objects. The `ar' container format is # architecture-neutral and the symbol name is a plain string in it, so a binary # grep answers the question with a tool the image is certain to have. check_eq rabbitmq-c/ssl-arm-built "yes" \ "$(grep -qa amqp_ssl_socket_new /usr/lib/librabbitmq.a && echo yes)" # THE FIVE TOOLS. They exist only because tools/CMakeLists.txt was patched to # link the static target -- upstream links them against an alias that exists only # under BUILD_SHARED_LIBS, so a static-only configure fails at generate time. # These five cases are what says the patch is still doing its job. for t in amqp-publish amqp-get amqp-consume amqp-declare-queue amqp-delete-queue; do check "rabbitmq-c/tool-$t" test -x "/usr/bin/$t" done # AND ONE OF THEM PARSES ITS OPTIONS, which is as far as a tool can be driven # without a broker: --help is popt's own dispatch, so a green here also says the # popt this port links against works on target. It exits 0 and names itself. if [ -x /usr/bin/amqp-declare-queue ]; then H=$(/usr/bin/amqp-declare-queue --help 2>&1); H_RC=$? if bst_execfail "$H"; then bst_unrun rabbitmq-c/tool-help "$H" else check_eq rabbitmq-c/tool-help-rc 0 "$H_RC" case "$H" in *amqp-declare-queue*) bst_ok rabbitmq-c/tool-help-usage ;; *) bst_notok rabbitmq-c/tool-help-usage "$(_bst_1line "$H")" ;; esac fi fi check rabbitmq-c/probe-installed test -x "$PROBE" # score : ok iff the probe printed " ok". Defined once and # given the captured output by the caller, because all three modes below score # the same way and a per-mode copy is three chances to diverge. score() { _out=$1; _id=$2 _line=$(printf '%s\n' "$_out" | grep "^$_id " | head -1) case "$_line" in "$_id ok"*) bst_ok "rabbitmq-c/$_id" ;; "$_id FAIL"*) bst_notok "rabbitmq-c/$_id" "$(_bst_1line "$_line")" ;; *) bst_notok "rabbitmq-c/$_id" "the probe printed no line for this id -- $(_bst_1line "$_out")" ;; esac } # =========================================================================== # B. THE TICKET'S FIRST CRITERION -- FIELD TABLES OVER MEMORY BUFFERS # # The probe's `table' mode encodes one field table carrying every value kind the # codec defines, then decodes it back. Each assertion is re-scored INDIVIDUALLY # below rather than collapsed into the exit status, because "the 64-bit integers # came back byte-swapped" and "the nested table decoded as opaque bytes" are # different findings and a single green would hide which of them held. TBL=$("$PROBE" table 2>&1); TBL_RC=$? if bst_execfail "$TBL"; then bst_unrun rabbitmq-c/table "$TBL" else # The encoding itself. table/wire is the byte-for-byte comparison against the # encoding AMQP 0-9-1 section 4.2.5.5 fixes, and it is the case this half # exists for: every multi-byte integer in a field table is BIG-endian, so a # little-endian target that skipped a swap would still round-trip through its # own decoder and be wrong on every wire. The round-trip cases below would all # stay green in that state; this one would not. for id in table/encode-rc table/encoded-length table/wire; do score "$TBL" "$id" done # The decode side: the decoder is handed the buffer and nothing else, so what # it recovers is a function of the bytes alone. for id in table/decode-rc table/decode-consumed-all table/decode-num-entries \ table/decode-keys-in-order table/decode-kinds; do score "$TBL" "$id" done # One case per value kind. A kind that stopped surviving the trip on this # target cannot hide behind the kinds that still do -- which is the whole # reason the table carries all eighteen rather than a representative few. for id in table/value-boolean table/value-i8 table/value-u8 table/value-i16 \ table/value-u16 table/value-i32 table/value-u32 table/value-i64 \ table/value-u64 table/value-f32 table/value-f64 \ table/value-decimal table/value-utf8 table/value-bytes \ table/value-timestamp table/value-void table/value-array \ table/value-nested-table; do score "$TBL" "$id" done # AND THE CASE THAT MAKES "LOSSLESS" A CLAIM ABOUT THE WHOLE TRIP rather than # about the fields the probe happens to name: re-encoding the decoded table # must reproduce the original bytes exactly. Anything the decoder dropped, # widened, reordered or silently defaulted shows up here even if no case above # looks at it. for id in table/reencode-rc table/reencode-identical; do score "$TBL" "$id" done # The two refusals. A truncated table must be rejected rather than read past # the end -- on this platform an overrun is a silent read of whatever the wasm # linear memory holds next, not a fault, so the bounds check being compiled in # is worth its own case. A buffer too small to hold the table must likewise be # refused rather than overrun. for id in table/truncated-refused table/too-big-refused; do score "$TBL" "$id" done check_eq rabbitmq-c/table-exit-status 0 "$TBL_RC" fi # =========================================================================== # C. THE TICKET'S SECOND CRITERION -- FRAMES OVER MEMORY BUFFERS # # amqp_handle_input() is the library's real parser and takes an amqp_bytes_t, so # the probe drives it directly with frames it built using the library's own # encoders. Both halves of each round trip are library code; the only thing the # probe writes is the eight bytes of framing the specification fixes. FRM=$("$PROBE" frame 2>&1); FRM_RC=$? if bst_execfail "$FRM"; then bst_unrun rabbitmq-c/frame "$FRM" else # The protocol header pseudo-frame, which a fresh connection matches before # anything else and which therefore has to come first. for id in frame/protocol-header-consumed frame/protocol-header-type \ frame/protocol-header-version; do score "$FRM" "$id" done # A method frame carrying connection.start -- a field table inside a method # inside a frame, which is the whole codec stack in one packet. for id in frame/method-encode-rc frame/method-consumed frame/method-frame-type \ frame/method-channel frame/method-id frame/method-version \ frame/method-mechanisms frame/method-locales \ frame/method-server-properties; do score "$FRM" "$id" done # A content header frame. frame/header-flags is the case that matters most: the # property flags word is a bitmap the encoder and decoder must agree on, and a # target that got the bit order wrong would decode a body as a content type # while every field below it still looked plausible. for id in frame/properties-encode-rc frame/header-consumed \ frame/header-frame-type frame/header-channel frame/header-class-id \ frame/header-body-size frame/header-flags frame/header-content-type \ frame/header-delivery-mode frame/header-timestamp \ frame/header-headers-table; do score "$FRM" "$id" done # A body frame -- no codec, opaque bytes, so the only thing that can go wrong # is the framing arithmetic -- and a heartbeat, the empty-payload degenerate # case a size-0 bug hides in. for id in frame/body-consumed frame/body-frame-type frame/body-fragment \ frame/heartbeat-length frame/heartbeat-consumed \ frame/heartbeat-frame-type; do score "$FRM" "$id" done # THE SAME FRAME DELIVERED ONE BYTE AT A TIME. amqp_handle_input is a resumable # parser and this is what it is for: a parser that only worked on whole frames # would pass every case above and fail every real socket read. for id in frame/incremental-partial-silent frame/incremental-partial-consumed \ frame/incremental-last-byte frame/incremental-frame; do score "$FRM" "$id" done # And the refusal: 0xce is the only legal last byte of a frame. Without the # check a desynchronised stream is read as garbage frames forever instead of # failing at the first one. score "$FRM" frame/bad-end-marker-refused score "$FRM" frame/destroy check_eq rabbitmq-c/frame-exit-status 0 "$FRM_RC" fi # =========================================================================== # D. THE TICKET'S THIRD CRITERION -- CONNECT TO A CLOSED PORT # # amqp_socket_open against a port nothing is listening on must return # AMQP_STATUS_SOCKET_ERROR promptly rather than blocking. On a platform whose # socket layer and poll(2) are themselves what is under test, "it returned at # all" is the substance of this case and not a formality. # # THE PORT IS CHOSEN, THEN CHECKED. A hardcoded number that something on the # image happened to be listening on would invert the whole case -- the connect # would succeed against a non-broker and the probe would report the wrong rc for # the right reason. So the suite picks the first port in a private-range window # that no listener holds, and if it cannot establish that (no ss and no netstat), # it says so and scores skip rather than guessing. PORT="" LST="" if command -v ss >/dev/null 2>&1; then LST=$(ss -ltn 2>/dev/null) elif command -v netstat >/dev/null 2>&1; then LST=$(netstat -ltn 2>/dev/null); fi if [ -z "$LST" ]; then bst_skip rabbitmq-c/noconnect \ "neither ss nor netstat on this image, so no port can be shown closed first" else p=47841 while [ $p -lt 47861 ]; do case "$LST" in *":$p"*) ;; *) PORT=$p; break ;; esac p=$((p+1)) done fi if [ -n "$PORT" ]; then NC=$("$PROBE" noconnect "$PORT" 2>&1); NC_RC=$? if bst_execfail "$NC"; then bst_unrun rabbitmq-c/noconnect "$NC" else # socket-new: the amqp_socket_t itself, so this case stays separable from the # connect below. # rc: the documented AMQP_STATUS_SOCKET_ERROR, asserted exactly rather than # as "nonzero" -- AMQP_STATUS_HOSTNAME_RESOLUTION_FAILED and # AMQP_STATUS_TIMEOUT are nonzero too and would each mean it failed for a # reason that is not the one under test. # prompt: it returned, inside the bound. This is the "rather than hanging" # half of the criterion. # error-string: amqp_error_string2 names the failure. The five tools print # it, and a port on which it came back blank would report every failure # identically. # noblock-*: the same assertions through amqp_socket_open_noblock, which is a # DIFFERENT code path -- non-blocking connect plus poll(2), where the # blocking arm is connect(2) alone. On a platform where poll(2) is what is # being brought up the two can disagree, so both are driven. for id in noconnect/socket-new noconnect/rc noconnect/prompt \ noconnect/error-string noconnect/destroy \ noconnect/noblock-rc noconnect/noblock-prompt \ noconnect/noblock-destroy; do score "$NC" "$id" done check_eq rabbitmq-c/noconnect-exit-status 0 "$NC_RC" fi fi