Skip to content

feat: subscribe() returns request id, monotonic message id, protobuf via C API - #47

Merged
Thecave3 merged 2 commits into
mainfrom
feature/subscribe-request-id-protobuf-capi
Jul 28, 2026
Merged

feat: subscribe() returns request id, monotonic message id, protobuf via C API#47
Thecave3 merged 2 commits into
mainfrom
feature/subscribe-request-id-protobuf-capi

Conversation

@Thecave3

@Thecave3 Thecave3 commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Surface the assigned E3-MessageID from subscribe() so a dApp can correlate a SubscriptionResponse (which echoes request_id) back to the originating request by id, instead of relying on FIFO ordering that desyncs on a dropped/reordered response. The id is threaded via an out-param queue_subscription_requestE3Agent::subscribe → the SWIG DAppSession::subscribe, which now returns the positive id on success / negative ErrorCode on failure.
  • Make generate_message_id() a lock-free monotonic counter wrapping into the ASN.1 E3-MessageID (1..1000) range, replacing the random draw that could collide within the in-flight window and defeat that correlation.
  • Let protobuf reach the wire from C-API consumers: widen the e3_config_t.encoding clamp in c_api.cpp from 0..1 to 0..2 (it silently dropped PROTOBUF=2), and document it in c_api.h.
  • build_libe3: add --enable-json/--disable-json and default JSON on — JSON previously had no flag and defaulted off, so build_libe3 --enable-swig produced a library/binding without the JSON encoder (a JSON peer then failed at start() with INTERNAL_ERROR). ASN.1+JSON now build by default; protobuf stays opt-in.

Type of change

  • New feature / enhancement
  • Test / CI / packaging
  • Other

Linked issue

No standalone tracking issue — this is coordinated with paired changes in the twin repos (see Twin-repo coordination). Happy to file one if preferred.

Mandatory test checklist

  • ./build_libe3 -c -d build -j $(nproc) -r -t passes (Release build + tests)
  • ./build_libe3 -c -d build -j $(nproc) -g -t passes (Debug build + tests)
  • cd build && ctest --output-on-failure is clean (18/18)
  • MPMC queue benchmark shows no regression vs main (this PR does not touch mpmc_queue.hpp)
  • VERSION bumped per SemVer (0.0.9 → 0.0.10; ABI change — subscribe() return contract)
  • Public headers under include/ touched; ./build_libe3 --docs renders with no new Doxygen warnings vs main
  • No new build dependencies added
  • libe3.pc interface unchanged; downstream consumers still link cleanly (verified: the OAI gNB twin builds and runs against this)

CI checklist

  • Unit Tests workflow is green (Debug + Release matrix) — will run on this PR
  • Commit policy workflow is green — commits carry Assisted-by: and no Co-authored-by/Signed-off-by; verified locally with scripts/check_commit_trailers.py
  • MPMC Queue Benchmark — not triggered (mpmc_queue.hpp unchanged)

Twin-repo coordination

This PR changes the public ABI (subscribe() return contract) and the C-API encoding acceptance, so it is released together with paired changes in the twins:

  • dApp-library (dapps): consumes the returned request id for id-based subscription correlation, and adds the protobuf E3SM codec path.
  • dApp-openairinterface5g: the gNB uses encoding = PROTOBUF through the C API and adds a protobuf-c SM payload codec.

Validated end-to-end: the OAI gNB (rfsim, core-less) driving the real spectrum dApp passes for asn1, json, and protobuf.

  • A paired change exists in each affected twin repo (coordinated release).

Paired PR(s): prepared in the dApp-library and dApp-openairinterface5g twins; released alongside this PR.

Workflow confirmation

  • My branch is a linear, fast-forward-able descendant of main, no merge commits.
  • Every commit builds and passes tests on its own (2 atomic commits).
  • I have read and followed CONTRIBUTING.md.

Thecave3 added 2 commits July 28, 2026 09:27
…obuf via C API

Enable request/response correlation and unblock protobuf E3AP for C-API peers:

- subscribe() now returns the assigned E3-MessageID (via a new out-param
  threaded through queue_subscription_request -> E3Agent::subscribe -> the
  SWIG DAppSession::subscribe, which returns the positive id on success and a
  negative ErrorCode on failure). The RAN already echoes request_id in the
  SubscriptionResponse, so a caller can now correlate a response to the
  originating subscribe by id instead of relying on FIFO ordering.
- generate_message_id() is now a lock-free monotonic counter wrapping into the
  ASN.1 E3-MessageID (1..1000) range, replacing the random draw that could
  collide and defeat correlation. Matches the counter scheme already used on
  the aerial/cuBB side.
- c_api encoding clamp widened 0..1 -> 0..2 so config->encoding = PROTOBUF (2)
  reaches libe3 instead of being silently dropped; c_api.h doc updated.
- VERSION 0.0.9 -> 0.0.10 (ABI change).

Assisted-by: Claude:claude-opus-4-8
JSON had no build_libe3 flag and defaulted off, so `build_libe3 --enable-swig`
silently produced a libe3/libe3py without the JSON encoder (encoding=json then
failed at start() with INTERNAL_ERROR). Add --enable-json/--disable-json
mirroring asn1/protobuf and default JSON on, so asn1+json build by default
(protobuf stays opt-in). Release .deb variants are unaffected (the workflow
calls cmake with explicit -DLIBE3_ENABLE_* flags, not build_libe3).

Assisted-by: Claude:claude-opus-4-8
@github-actions

Copy link
Copy Markdown
Contributor

⏱️ Full-loop Latency Benchmark (commit 35a3141)

Full-loop latency benchmark (N=1044 after 50 warmup)

All values in microseconds (us). Link: zmq, transport: ipc, encoding: ASN.1 APER.

Phase mean p50 p99 max
1. Collect indication data 0.16 0.14 0.30 7.27
2. Create & encode indication 0.74 0.67 1.70 8.90
3. Deliver indication (RAN -> dApp) 92.87 87.92 162.21 183.45
4. Decode indication 0.60 0.56 1.20 8.23
5. Process data 0.04 0.03 0.04 9.16
6. Create & encode control 0.30 0.27 0.66 8.72
7. Deliver control (dApp -> RAN) 108.81 109.47 178.82 552.91
8. Decode & handle control 0.53 0.47 1.11 14.18
Total round-trip 204.06 195.52 330.84 594.45

Benchmarked on ubuntu-latest, Release build, ZMQ + IPC, ASN.1 APER.

@github-actions

Copy link
Copy Markdown
Contributor

🔄 E2E dApp Integration Results (commit 35a3141)

posix/ipc

  • dApp exit: 0
  • Indications received: 7

posix/tcp

  • dApp exit: 0
  • Indications received: 7

zmq/ipc

  • dApp exit: 0
  • Indications received: 7

zmq/tcp

  • dApp exit: 0
  • Indications received: 7

example_simple_agent + example_simple_dapp on ubuntu-latest, Release build

@github-actions

Copy link
Copy Markdown
Contributor

🔀 E2E Topologies — multi-dApp / multi-RAN (commit 35a3141)

zmq/ipc

  • 1 RAN - 1 dApp: indications=5
    • dapp peer=t11 ran=ran-solo sub=1 indications=5 seq=[0..4] dropped=0 (0%) age_ms(avg=0 max=0 @seq=0) hist[<=1:5 2-5:0 6-10:0 >10:0]
  • 1 RAN - 2 dApps: dApp#1 ind=5 sub=1, dApp#2 ind=6 sub=2, RAN saw 2 dApps
    • dapp1 peer=t12 ran=ran-shared sub=1 indications=5 seq=[0..4] dropped=0 (0%) age_ms(avg=1 max=1 @seq=0) hist[<=1:5 2-5:0 6-10:0 >10:0]
    • dapp2 peer=t12 ran=ran-shared sub=2 indications=6 seq=[0..5] dropped=0 (0%) age_ms(avg=1 max=1 @seq=0) hist[<=1:6 2-5:0 6-10:0 >10:0]
  • 2 RANs - 1 dApp: from ran-a ind=5, from ran-b ind=5
    • dapp peer=t2a ran=ran-a sub=1 indications=5 seq=[0..4] dropped=0 (0%) age_ms(avg=0.2 max=1 @seq=0) hist[<=1:5 2-5:0 6-10:0 >10:0]
    • dapp peer=t2b ran=ran-b sub=1 indications=5 seq=[0..4] dropped=0 (0%) age_ms(avg=0 max=0 @seq=0) hist[<=1:5 2-5:0 6-10:0 >10:0]

zmq/tcp

  • 1 RAN - 1 dApp: indications=5
    • dapp peer=default ran=ran-solo sub=1 indications=5 seq=[0..4] dropped=0 (0%) age_ms(avg=0 max=0 @seq=0) hist[<=1:5 2-5:0 6-10:0 >10:0]
  • 1 RAN - 2 dApps: dApp#1 ind=5 sub=1, dApp#2 ind=6 sub=2, RAN saw 2 dApps
    • dapp1 peer=default ran=ran-shared sub=1 indications=5 seq=[0..4] dropped=0 (0%) age_ms(avg=0 max=0 @seq=0) hist[<=1:5 2-5:0 6-10:0 >10:0]
    • dapp2 peer=default ran=ran-shared sub=2 indications=6 seq=[0..5] dropped=0 (0%) age_ms(avg=0 max=0 @seq=0) hist[<=1:6 2-5:0 6-10:0 >10:0]
  • 2 RANs - 1 dApp: from ran-a ind=5, from ran-b ind=5
    • dapp peer=default ran=ran-a sub=1 indications=5 seq=[0..4] dropped=0 (0%) age_ms(avg=0 max=0 @seq=0) hist[<=1:5 2-5:0 6-10:0 >10:0]
    • dapp peer=off100 ran=ran-b sub=1 indications=5 seq=[0..4] dropped=0 (0%) age_ms(avg=0.2 max=1 @seq=0) hist[<=1:5 2-5:0 6-10:0 >10:0]

example_simple_agent + example_simple_dapp on ubuntu-latest, Release. Indication age is report-only.

@Thecave3
Thecave3 merged commit 35a3141 into main Jul 28, 2026
20 checks passed
@Thecave3
Thecave3 deleted the feature/subscribe-request-id-protobuf-capi branch July 28, 2026 14:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant