e3ap: surface assigned message ids for dApp sends and relayed xApp control - #72
Merged
Conversation
Contributor
CI report —
|
| Workflow | Result | Time | Run |
|---|---|---|---|
| Commit policy | ✅ success | 2m11s | #91 |
| E2E dApp Integration | ✅ success | 3m02s | #106 |
| E2E Topologies (multi-dApp / multi-RAN) | ✅ success | 2m29s | #104 |
| Full-loop Latency Benchmark | ✅ success | 1m11s | #105 |
| Unit Tests | ✅ success | 4m39s | #132 |
| latrec portability | ⏭️ not triggered (paths filter) | — | — |
| MPMC Queue Benchmark | ⏭️ not triggered (paths filter) | — | — |
E2E Topologies (multi-dApp / multi-RAN)
zmq/ipc
- ✅ 1 RAN - 1 dApp: indications=
5dapppeer=t11 ran=ran-solo sub=1 indications=5 seq=[0..4] dropped=0 (0%) age_ms(avg=0.4 max=1 @seq=0) hist[<=1:5 2-5:0 6-10:0 >10:0]
- ✅ 1 RAN - 2 dApps: dApp#1 ind=
5sub=1, dApp#2 ind=6sub=2, RAN saw2dApps - ✅ 2 RANs - 1 dApp: from ran-a ind=
5, from ran-b ind=5
zmq/tcp
- ✅ 1 RAN - 1 dApp: indications=
5dapppeer=default ran=ran-solo sub=1 indications=5 seq=[0..4] dropped=0 (0%) age_ms(avg=0.8 max=1 @seq=1) hist[<=1:5 2-5:0 6-10:0 >10:0]
- ✅ 1 RAN - 2 dApps: dApp#1 ind=
5sub=1, dApp#2 ind=6sub=2, RAN saw2dApps - ✅ 2 RANs - 1 dApp: from ran-a ind=
5, from ran-b ind=5
E2E dApp Integration
✅ 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
Full-loop Latency Benchmark
Full-loop latency
Full-loop latency benchmark (N=1017 after 50 warmup)
All values in microseconds (μs). Link: zmq, transport: ipc, encoding: ASN.1 APER.
| # | Description | Tags | mean | p50 | p99 | max |
|---|---|---|---|---|---|---|
| 1 | Collect indication data | RECORD_BEGIN to ENCODE_E3SM_BEGIN |
0.11 | 0.10 | 0.26 | 2.37 |
| 2 | Create & encode indication | ENCODE_E3SM_BEGIN to ENCODE_E3SM_DONE |
0.94 | 0.89 | 1.46 | 18.96 |
| 3 | Encode E3AP (indication) | EMIT_ENTER to ENQUEUE, then DEQUEUE to ENCODE_E3AP_DONE |
3.00 | 3.10 | 5.54 | 10.19 |
| 4 | Queuing (indication) | ENQUEUE to DEQUEUE |
7.00 | 7.54 | 17.82 | 57.73 |
| 5 | Delivery (indication) | ENCODE_E3AP_DONE to SEND_DONE |
0.91 | 0.26 | 24.48 | 40.95 |
| 6 | E3 wire (RAN -> dApp) | SEND_DONE to RECV |
108830348517511.97 | 52.20 | 75.06 | 18446744073709548.00 |
| 7 | Decode E3AP (indication) | RECV to DECODE_E3AP_DONE |
1.85 | 1.77 | 3.81 | 16.76 |
| 8 | libe3 dispatch (indication) | DECODE_E3AP_DONE to DELIVER_BEGIN |
0.13 | 0.13 | 0.18 | 0.22 |
| 9 | Decode indication | DELIVER_BEGIN to DECODE_E3SM_DONE |
0.63 | 0.56 | 0.92 | 2.08 |
| 10 | Process data | DECODE_E3SM_DONE to ENCODE_E3SM_BEGIN |
0.05 | 0.05 | 0.06 | 0.09 |
| 11 | Create & encode control | ENCODE_E3SM_BEGIN to ENCODE_E3SM_DONE |
0.32 | 0.32 | 0.47 | 1.76 |
| 12 | Encode E3AP (control) | EMIT_ENTER to ENQUEUE, then DEQUEUE to ENCODE_E3AP_DONE |
3.75 | 3.56 | 6.12 | 6.41 |
| 13 | Queuing (control) | ENQUEUE to DEQUEUE |
16.11 | 15.94 | 27.21 | 56.45 |
| 14 | Delivery (control) | ENCODE_E3AP_DONE to SEND_DONE |
5.71 | 5.27 | 10.61 | 21.70 |
| 15 | E3 wire (dApp -> RAN) | SEND_DONE to RECV |
50.90 | 50.66 | 67.93 | 111.68 |
| 16 | Decode E3AP (control) | RECV to DECODE_E3AP_DONE |
2.97 | 2.87 | 6.67 | 15.73 |
| 17 | libe3 dispatch (control) | DECODE_E3AP_DONE to DECODE_E3SM_BEGIN |
0.32 | 0.31 | 0.53 | 0.64 |
| 18 | Decode & handle control | DECODE_E3SM_BEGIN to DECODE_E3SM_DONE |
0.43 | 0.40 | 0.63 | 0.80 |
| Total | Total round-trip | 147.11 | 146.66 | 195.06 | 892.41 |
ubuntu-latest, Release build, ZMQ + IPC, ASN.1 APER.
These numbers are measured inside a GitHub Actions container and should be treated as an upper bound on E3AP's and the library's own latency, not a representative deployment measurement.
Ready to merge (fast-forward only)
A maintainer can land the reviewed commits with:
git fetch origin
git checkout main && git merge --ff-only 92130a1273ddd670c5bd265b1fa2daf35e35281f && git push origin mainHead: 92130a1273ddd670c5bd265b1fa2daf35e35281f (branch 67-68-message-id-correlation). If --ff-only fails as non-fast-forward, the branch must be rebased on the latest main.
One comment per PR, rewritten in place once every workflow for
92130a1finished.
Thecave3
force-pushed
the
67-68-message-id-correlation
branch
from
August 23, 2026 16:48
3a7cd74 to
22f3918
Compare
…App control Closes #67 and #68: both are correlation gaps around E3-MessageID that already exists internally but never reaches the API surface. - send_control()/send_report() (E3Agent + E3Interface) gain a trailing uint32_t* out_message_id = nullptr, mirroring subscribe()'s existing out_request_id. The generated id was already computed before queueing; this only plumbs it out. Source-compatible: no existing caller needs to change, and the ErrorCode return is unchanged in both the success and failure case. - XAppControlAction gains a decode-time-only message_id field, set by the dApp inbound loop from the wrapping E3-PDU's id (already decoded into Pdu::message_id, never forwarded before). No ASN.1/JSON/Protobuf grammar changes — the id was always on the wire, just not threaded through the C++ handler call. This is ABI-breaking (struct layout changes) but not source-breaking for any of the four existing XAppControlHandler registrations (examples/simple_dapp.cpp, swig/e3_dapp_session.cpp, tests/integration/bench_latrec_load.cpp, plus the header/setter declarations) — none construct or destructure the struct positionally. The SWIG binding follows both fixes through to Python: send_control()/ send_report() now return the assigned message id (positive, 1..1000) on success instead of always 0, matching subscribe()'s existing convention; E3_EVENT_XAPP_CONTROL events now carry the id in the existing request_id field. This is a Python-visible behavior change — updating the dApp library's outbound loop (which currently checks `rc != SUCCESS` for these two calls) is left as a follow-up in that repo. Extends tests/integration/test_role_pair_zmq_ipc.cpp's existing RAN+dApp round trip: asserts send_control's out_message_id matches the id the RAN sees as request_message_id, and that two RAN -> dApp xApp control relays deliver distinct, nonzero message ids to the dApp handler. Assisted-by: Claude:claude-sonnet-5 Signed-off-by: Andrea Lacava <a.lacava@northeastern.edu>
Patch bump for the additive/source-compatible API surfaced in the previous commit (#67, #68) — same class of change as 1ad9ced, which bumped 0.1.0 -> 0.1.1 for the analogous subscribe() out_request_id. Jumps to 0.1.2 rather than 0.1.1 because feat/latency-tracing (open, unmerged) already carries VERSION 0.1.1 on its own branch; this keeps the two in sequence instead of colliding whenever that branch lands. Assisted-by: Claude:claude-sonnet-5 Signed-off-by: Andrea Lacava <a.lacava@northeastern.edu>
Thecave3
force-pushed
the
67-68-message-id-correlation
branch
from
August 27, 2026 16:52
fa6c62b to
92130a1
Compare
17 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #67. Closes #68.
Based on top of
feat/latency-tracing(open, unmerged) at the author's request, since that branch is expected to land first.Both issues are correlation gaps around
E3-MessageIDthat already exists internally but never reaches the API surface. No E3AP wire/ASN.1/JSON/Protobuf grammar changes in either case.#67 —
send_control()/send_report()(E3Agent+E3Interface) gain a trailinguint32_t* out_message_id = nullptr, mirroringsubscribe()'s existingout_request_id. The id was already computed internally before queueing; this just plumbs it out. Fully source-compatible — no existing caller needs to change, and theErrorCodereturn is unchanged in both the success and failure case.#68 —
XAppControlActiongains a decode-time-onlymessage_idfield, set by the dApp inbound loop from the wrappingE3-PDU'sid(already decoded intoPdu::message_id, just never forwarded). This corrects the issue's own follow-up note, which claimed "either [fix] is source-breaking for existing handler registrations" — that's true of the alternative (changingXAppControlHandler's signature), but not of the struct-field approach implemented here: it's ABI-breaking (struct layout changes) but source-compatible for all four existing registrations (examples/simple_dapp.cpp,swig/e3_dapp_session.cpp,tests/integration/bench_latrec_load.cpp, plus the header/setter declarations) — none construct or destructure the struct positionally.SWIG binding follows both fixes through to Python:
send_control()/send_report()now return the assigned message id (positive, 1..1000) on success instead of always0, matchingsubscribe()'s existing convention;E3_EVENT_XAPP_CONTROLevents now carry the id in the existingrequest_idfield. This is a Python-visible behavior change — updating the dApp library's outbound loop (which currently checksrc != SUCCESSfor these two calls) is left as a follow-up in that repo, not done here.Tests: extended
tests/integration/test_role_pair_zmq_ipc.cpp's existing RAN+dApp round trip — assertssend_control'sout_message_idmatches the id the RAN sees asrequest_message_id, and that two RAN → dApp xApp control relays deliver distinct, nonzero message ids to the dApp handler.VERSION:
0.1.1 → 0.1.2(patch, same class of change as1ad9ced).feat/latency-tracingalready carries0.1.1; this bumps past it since that branch is expected to merge first.Local verification (both with
--enable-swigand integration tests on, rebased onfeat/latency-tracingsoLIBE3_ENABLE_LATRECis exercised throughout):./build_libe3 -c -r -t— Release + tests: 28/28 passed./build_libe3 -c -g -t— Debug + tests: 28/28 passedpython3 scripts/check_commit_trailers.py --base origin/feat/latency-tracing --head HEAD— passesfeat/latency-tracing, no merge commitsHappy to split further or adjust scope if either fix should land separately.