Skip to content

Periodicity range 0..60000000 us, and an interop page with a change ledger - #122

Open
Thecave3 wants to merge 3 commits into
fix/dapp-lifecycle-90-91-92from
docs/interop-98-101
Open

Thecave3 wants to merge 3 commits into
fix/dapp-lifecycle-90-91-92from
docs/interop-98-101

Conversation

@Thecave3

@Thecave3 Thecave3 commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #98, closes #101. Stacked on #120 (base is its branch, which is stacked on #118); retarget to main as they land.

What changes for a user

  • A libe3 dApp on ASN.1 can now ask for a delivery interval above 1000 microseconds. Aerial's default of 100000 (100 ms) used to fail to encode, so the subscription never left the dApp ([Interop]: ASN.1 periodicity is 0..1000 but the value is microseconds #98). periodicity is now one thing everywhere: microseconds, 0 means every event, 0 to 60000000 (60 s), on the request and on the granted value in the response. 60000001 still fails to encode, with the existing "APER encoding failed" log line.
  • There is now a page that says what pairs with OAI, Aerial and OCUDU, which JSON libe3 speaks, where peers disagree on a field, and what changed on the wire and when: docs/interop.md ([Interop]: libe3 has no interoperability documentation #101). It has a change ledger, one row per wire or peer-visible change. The README links it, and the version badge (it read 0.0.4) now follows the latest release.

Behavior to know about

  • OCUDU reads the same number as milliseconds. libe3 does not convert. A libe3 dApp that asks an OCUDU RAN for 100000 asks for 100 s and is rejected. The decision on [Interop]: ASN.1 periodicity is 0..1000 but the value is microseconds #98 is that OCUDU adapts. The API docs and the interop page say so. Until OCUDU converts, a caller facing OCUDU passes milliseconds itself.
  • This is an ASN.1 wire change. APER sizes the field from the constraint (10 bits before, 26 now), so a peer built from the old grammar cannot exchange SubscriptionRequest or SubscriptionResponse with this one. Rebuild libe3, OAI's E3 agent and the dApp library together. JSON and Protobuf are unchanged apart from comments.
  • A request with no periodicity encodes the same bytes as before.
  • JSON and Protobuf still accept any uint32, so above 60 s is rejected on ASN.1 only. Aerial's schema has no maximum, so I did not add a check there.

Not in this PR

Deferred Cost to the user Status
libe3 to OCUDU unit conversion A libe3 dApp against OCUDU gets 1000x the interval, or a rejection above 60 s OCUDU's side; its libe3-compatible codec passes the number through and caps it at 1000
Same bytes as OCUDU's own grammar The field is 26 bits here, 16 in OCUDU's grammar and 10 in its libe3-compatible mode Already broken by #89 and #109
OAI and dApp library rebuild See above Unowned. Upstream OAI also pins libe3>=0.0.10, <0.2.0 (duranta develop and duranta#495 use >=0.2.0 <0.3.0)
VERSION bump Nothing announces the wire change by version Goes in the last PR of the stack. The ledger row says "unmerged"
What "granted" means (enforcement) A libe3 RAN still echoes the request #119. This PR only widens the range of that field
Conformance fixtures from Aerial's examples An encoder change can still break a peer without a test failing #102

Type of change

  • Bug fix
  • New feature / enhancement
  • New Service Model
  • Refactor (no behavior change)
  • Documentation
  • Test / CI / packaging
  • Other: ASN.1 grammar range (wire change)

Mandatory test checklist

  • ./build_libe3 -c -d build -j $(nproc) -r -t passes (Release): not run locally, left to CI
  • Debug build with ASN.1, JSON and Protobuf, ctest -LE integration: 24/24 pass on macOS. Integration tests and the SWIG test were not run here
  • MPMC queue benchmark: no queue code touched, not run
  • VERSION bumped: deliberately not here, see above
  • ./build_libe3 --docs: not run; the Doxygen comments touched are on e3_agent.hpp, e3_interface.hpp, e3_dapp_session.hpp
  • No new build dependencies; libe3.pc unchanged

The new round trips (request and response at 0, 100000 and 60000000, and 60000001 rejected) fail on the old 0..1000 grammar and pass on the new one. I checked that by reverting the grammar and rebuilding test_asn1_size.

Twin-repo coordination

  • This PR changes the E3 wire protocol, and a paired change is needed in the dApp library (e3.asn and e3.json, if still present there) and in OAI's E3 agent. Not opened yet.
  • The ledger row in docs/interop.md is added.

Workflow confirmation

@Thecave3
Thecave3 force-pushed the docs/interop-98-101 branch from ac58756 to 49f34c7 Compare October 6, 2026 15:17
@github-actions

github-actions Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

CI report — 947b94b — ✅ all checks passed

Workflow Result Time Run
CI report ⚪ cancelled 1m34s #57
Commit policy ✅ success 3m19s #147
E2E dApp Integration ✅ success 1m57s #149
E2E Topologies (multi-dApp / multi-RAN) ✅ success 2m26s #147
Full-loop Latency Benchmark ✅ success 0m52s #148
Unit Tests ✅ success 4m35s #175
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=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=0 max=0 @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=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=t2a 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=t2b ran=ran-b sub=1 indications=5 seq=[0..4] dropped=0 (0%) age_ms(avg=0.6 max=1 @seq=1) 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.8 max=1 @seq=0) hist[<=1:5 2-5:0 6-10:0 >10:0]
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=1224 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.06 0.05 0.09 0.30
2 Create & encode indication ENCODE_E3SM_BEGIN to ENCODE_E3SM_DONE 0.31 0.28 0.59 8.85
3 Encode E3AP (indication) EMIT_ENTER to ENQUEUE, then DEQUEUE to ENCODE_E3AP_DONE 1.30 1.17 2.78 9.61
4 Queuing (indication) ENQUEUE to DEQUEUE 1.72 1.26 7.94 12.69
5 Delivery (indication) ENCODE_E3AP_DONE to SEND_DONE 0.39 0.09 14.16 21.28
6 E3 wire (RAN -> dApp) SEND_DONE to RECV 30.52 30.69 39.31 66.06
7 Decode E3AP (indication) RECV to DECODE_E3AP_DONE 0.64 0.52 2.18 8.36
8 libe3 dispatch (indication) DECODE_E3AP_DONE to DELIVER_BEGIN 0.04 0.04 0.07 0.34
9 Decode indication DELIVER_BEGIN to DECODE_E3SM_DONE 0.22 0.19 0.37 0.74
10 Process data DECODE_E3SM_DONE to ENCODE_E3SM_BEGIN 0.02 0.02 0.03 0.10
11 Create & encode control ENCODE_E3SM_BEGIN to ENCODE_E3SM_DONE 0.11 0.10 0.18 0.45
12 Encode E3AP (control) EMIT_ENTER to ENQUEUE, then DEQUEUE to ENCODE_E3AP_DONE 1.27 1.27 1.73 11.46
13 Queuing (control) ENQUEUE to DEQUEUE 8.99 9.08 11.15 18.37
14 Delivery (control) ENCODE_E3AP_DONE to SEND_DONE 3.11 3.05 4.66 12.67
15 E3 wire (dApp -> RAN) SEND_DONE to RECV 30.30 29.79 37.76 166.24
16 Decode E3AP (control) RECV to DECODE_E3AP_DONE 0.82 0.70 2.92 10.69
17 libe3 dispatch (control) DECODE_E3AP_DONE to DECODE_E3SM_BEGIN 0.05 0.05 0.11 0.31
18 Decode & handle control DECODE_E3SM_BEGIN to DECODE_E3SM_DONE 0.15 0.13 0.27 0.58
Total Total round-trip 80.22 79.64 93.04 222.72

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 947b94b7937a87f024501b5ae0a4711399b2eb73 && git push origin main

Head: 947b94b7937a87f024501b5ae0a4711399b2eb73 (branch docs/interop-98-101). 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 947b94b finished.

The grammar capped periodicity at 1000 while every user reads it as
microseconds: Aerial's default is 100000 (100 ms), and OCUDU allows up to
60 s. The encoder refused anything above 1000, so an ASN.1 dApp could not ask
for Aerial's default, and types.hpp documented a third range, 0 to 10000.

The request and the granted value in the response now share one definition:
microseconds, 0 means every event, 0 to 60000000. OCUDU reads the same number
as milliseconds and has to convert on its side. JSON and Protobuf carry any
uint32 and are unchanged apart from comments.

This is a wire change for ASN.1: APER sizes the field from the constraint, so
a peer built from the old grammar cannot exchange SubscriptionRequest or
SubscriptionResponse with this one. The new round trips fail on the old
grammar. The equivalence test now carries 100000 through every encoding.

Refs #98

Assisted-by: Claude:claude-sonnet-5-5
periodicity is microseconds between indications, 0 means every event, and 0 to
60000000 over ASN.1. Say so on E3Agent::subscribe, the internal queue helper
and the Python session, and say that an OCUDU RAN reads the number as
milliseconds, so a caller facing OCUDU passes what it expects.

Refs #98

Assisted-by: Claude:claude-sonnet-5-5
docs/ covered latency recording only. Nothing said which JSON libe3 speaks,
which link and encoding pair with which peer, where a field means something
different to OAI, Aerial or OCUDU, or what changed on the wire and when. The
README badge also read 0.0.4 against a 0.2.2 release.

docs/interop.md has the pairing table with how sure each cell is (CI, read
from source, not run), the JSON envelope and keys, a field contracts table that
starts with the periodicity unit, what a dApp sees when the RAN goes away, and
a ledger with one row per change that touches the wire or a peer-visible
behavior, seeded from 0.2.0 and the open stack.

The PR template gets one checkbox so a ledger row is added in the same PR as
the change. The README badge now reads the latest release, and the README
links the page.

Refs #101

Assisted-by: Claude:claude-sonnet-5-5
@Thecave3
Thecave3 force-pushed the docs/interop-98-101 branch from 03887b0 to 947b94b Compare October 16, 2026 17:18
@Thecave3
Thecave3 force-pushed the fix/dapp-lifecycle-90-91-92 branch from 2cf3145 to 1280b3c Compare October 16, 2026 17:18

This branch has not been deployed

No deployments
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