dapps 0.3.0__tar.gz → 0.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. {dapps-0.3.0 → dapps-0.3.1}/PKG-INFO +1 -1
  2. dapps-0.3.1/VERSION +1 -0
  3. {dapps-0.3.0 → dapps-0.3.1}/src/e3interface/e3_interface.py +21 -2
  4. {dapps-0.3.0 → dapps-0.3.1}/src/e3interface/libe3_agent.py +4 -3
  5. {dapps-0.3.0 → dapps-0.3.1}/src/spectrum/policy.py +5 -4
  6. {dapps-0.3.0 → dapps-0.3.1}/src/spectrum/spectrum_dapp.py +7 -7
  7. {dapps-0.3.0 → dapps-0.3.1}/tests/test_latrec_stamps.py +2 -0
  8. {dapps-0.3.0 → dapps-0.3.1}/tests/test_outbound_send_result.py +1 -1
  9. dapps-0.3.1/tests/test_report_sequence_id.py +51 -0
  10. dapps-0.3.0/VERSION +0 -1
  11. {dapps-0.3.0 → dapps-0.3.1}/.github/CODEOWNERS +0 -0
  12. {dapps-0.3.0 → dapps-0.3.1}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  13. {dapps-0.3.0 → dapps-0.3.1}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  14. {dapps-0.3.0 → dapps-0.3.1}/.github/ISSUE_TEMPLATE/documentation.yml +0 -0
  15. {dapps-0.3.0 → dapps-0.3.1}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  16. {dapps-0.3.0 → dapps-0.3.1}/.github/ISSUE_TEMPLATE/new_sm.yml +0 -0
  17. {dapps-0.3.0 → dapps-0.3.1}/.github/ISSUE_TEMPLATE/question.yml +0 -0
  18. {dapps-0.3.0 → dapps-0.3.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  19. {dapps-0.3.0 → dapps-0.3.1}/.github/workflows/mirror.yml +0 -0
  20. {dapps-0.3.0 → dapps-0.3.1}/.github/workflows/tag-and-release.yml +0 -0
  21. {dapps-0.3.0 → dapps-0.3.1}/.github/workflows/test.yml +0 -0
  22. {dapps-0.3.0 → dapps-0.3.1}/.gitignore +0 -0
  23. {dapps-0.3.0 → dapps-0.3.1}/CMakeLists.txt +0 -0
  24. {dapps-0.3.0 → dapps-0.3.1}/CONTRIBUTING.md +0 -0
  25. {dapps-0.3.0 → dapps-0.3.1}/CONTRIBUTORS +0 -0
  26. {dapps-0.3.0 → dapps-0.3.1}/LICENSE +0 -0
  27. {dapps-0.3.0 → dapps-0.3.1}/MANIFEST.in +0 -0
  28. {dapps-0.3.0 → dapps-0.3.1}/README.md +0 -0
  29. {dapps-0.3.0 → dapps-0.3.1}/SECURITY.md +0 -0
  30. {dapps-0.3.0 → dapps-0.3.1}/docs/dapp_uml.txt +0 -0
  31. {dapps-0.3.0 → dapps-0.3.1}/docs/ground_truth_labeling.md +0 -0
  32. {dapps-0.3.0 → dapps-0.3.1}/docs/gui_uml.txt +0 -0
  33. {dapps-0.3.0 → dapps-0.3.1}/examples/__init__.py +0 -0
  34. {dapps-0.3.0 → dapps-0.3.1}/examples/hackathon_policy.py +0 -0
  35. {dapps-0.3.0 → dapps-0.3.1}/examples/simple_dapp.py +0 -0
  36. {dapps-0.3.0 → dapps-0.3.1}/examples/spectrum_dapp.py +0 -0
  37. {dapps-0.3.0 → dapps-0.3.1}/libiqsaver/CMakeLists.txt +0 -0
  38. {dapps-0.3.0 → dapps-0.3.1}/libiqsaver/cmake/libiqsaver.pc.in +0 -0
  39. {dapps-0.3.0 → dapps-0.3.1}/libiqsaver/cmake/libiqsaverConfig.cmake.in +0 -0
  40. {dapps-0.3.0 → dapps-0.3.1}/libiqsaver/include/iqsaver/annotation.hpp +0 -0
  41. {dapps-0.3.0 → dapps-0.3.1}/libiqsaver/include/iqsaver/iq_saver_writer.hpp +0 -0
  42. {dapps-0.3.0 → dapps-0.3.1}/libiqsaver/include/iqsaver/iqsaver.hpp +0 -0
  43. {dapps-0.3.0 → dapps-0.3.1}/libiqsaver/include/iqsaver/types.hpp +0 -0
  44. {dapps-0.3.0 → dapps-0.3.1}/libiqsaver/include/iqsaver/version.hpp.in +0 -0
  45. {dapps-0.3.0 → dapps-0.3.1}/libiqsaver/src/iq_saver_writer.cpp +0 -0
  46. {dapps-0.3.0 → dapps-0.3.1}/libiqsaver/swig/iqsaver.i +0 -0
  47. {dapps-0.3.0 → dapps-0.3.1}/libiqsaver/swig/iqsaver_swig.cpp +0 -0
  48. {dapps-0.3.0 → dapps-0.3.1}/libiqsaver/swig/iqsaver_swig.hpp +0 -0
  49. {dapps-0.3.0 → dapps-0.3.1}/pyproject.toml +0 -0
  50. {dapps-0.3.0 → dapps-0.3.1}/src/dapp/__init__.py +0 -0
  51. {dapps-0.3.0 → dapps-0.3.1}/src/dapp/control_server.py +0 -0
  52. {dapps-0.3.0 → dapps-0.3.1}/src/dapp/dapp.py +0 -0
  53. {dapps-0.3.0 → dapps-0.3.1}/src/e3interface/__init__.py +0 -0
  54. {dapps-0.3.0 → dapps-0.3.1}/src/e3interface/e3_logging.py +0 -0
  55. {dapps-0.3.0 → dapps-0.3.1}/src/e3interface/e3_transport.py +0 -0
  56. {dapps-0.3.0 → dapps-0.3.1}/src/e3interface/latrec.py +0 -0
  57. {dapps-0.3.0 → dapps-0.3.1}/src/e3interface/sm_helpers.py +0 -0
  58. {dapps-0.3.0 → dapps-0.3.1}/src/iq_saver/__init__.py +0 -0
  59. {dapps-0.3.0 → dapps-0.3.1}/src/iq_saver/iq_saver.py +0 -0
  60. {dapps-0.3.0 → dapps-0.3.1}/src/simple/__init__.py +0 -0
  61. {dapps-0.3.0 → dapps-0.3.1}/src/simple/defs/__init__.py +0 -0
  62. {dapps-0.3.0 → dapps-0.3.1}/src/simple/defs/e3sm_simple.proto +0 -0
  63. {dapps-0.3.0 → dapps-0.3.1}/src/simple/defs/e3sm_simple_pb2.py +0 -0
  64. {dapps-0.3.0 → dapps-0.3.1}/src/simple/simple_dapp.py +0 -0
  65. {dapps-0.3.0 → dapps-0.3.1}/src/spectrum/README.md +0 -0
  66. {dapps-0.3.0 → dapps-0.3.1}/src/spectrum/__init__.py +0 -0
  67. {dapps-0.3.0 → dapps-0.3.1}/src/spectrum/adaptive_noise_floor.py +0 -0
  68. {dapps-0.3.0 → dapps-0.3.1}/src/spectrum/control_handlers.py +0 -0
  69. {dapps-0.3.0 → dapps-0.3.1}/src/spectrum/defs/__init__.py +0 -0
  70. {dapps-0.3.0 → dapps-0.3.1}/src/spectrum/defs/e3sm_oai_l1_kpm.asn +0 -0
  71. {dapps-0.3.0 → dapps-0.3.1}/src/spectrum/defs/e3sm_oai_l1_kpm.proto +0 -0
  72. {dapps-0.3.0 → dapps-0.3.1}/src/spectrum/defs/e3sm_oai_l1_kpm_pb2.py +0 -0
  73. {dapps-0.3.0 → dapps-0.3.1}/src/spectrum/defs/e3sm_spectrum.asn +0 -0
  74. {dapps-0.3.0 → dapps-0.3.1}/src/spectrum/defs/e3sm_spectrum.json +0 -0
  75. {dapps-0.3.0 → dapps-0.3.1}/src/spectrum/defs/e3sm_spectrum.proto +0 -0
  76. {dapps-0.3.0 → dapps-0.3.1}/src/spectrum/defs/e3sm_spectrum_pb2.py +0 -0
  77. {dapps-0.3.0 → dapps-0.3.1}/src/spectrum/e3_l2_sensing_reader.py +0 -0
  78. {dapps-0.3.0 → dapps-0.3.1}/src/spectrum/e3_ran_buffers_reader.py +0 -0
  79. {dapps-0.3.0 → dapps-0.3.1}/src/spectrum/embargo_manager.py +0 -0
  80. {dapps-0.3.0 → dapps-0.3.1}/src/spectrum/ru_profiles.py +0 -0
  81. {dapps-0.3.0 → dapps-0.3.1}/src/spectrum/slot_recorder.py +0 -0
  82. {dapps-0.3.0 → dapps-0.3.1}/src/spectrum/threshold_detector.py +0 -0
  83. {dapps-0.3.0 → dapps-0.3.1}/src/visualization/__init__.py +0 -0
  84. {dapps-0.3.0 → dapps-0.3.1}/src/visualization/energy.py +0 -0
  85. {dapps-0.3.0 → dapps-0.3.1}/src/visualization/grafana.py +0 -0
  86. {dapps-0.3.0 → dapps-0.3.1}/src/visualization/influx.py +0 -0
  87. {dapps-0.3.0 → dapps-0.3.1}/src/visualization/iq.py +0 -0
  88. {dapps-0.3.0 → dapps-0.3.1}/src/visualization/subcarrier/insi_white.png +0 -0
  89. {dapps-0.3.0 → dapps-0.3.1}/src/visualization/subcarrier/start_subcarrier_visualizer.sh +0 -0
  90. {dapps-0.3.0 → dapps-0.3.1}/src/visualization/subcarrier/subcarrier_visualizer.py +0 -0
  91. {dapps-0.3.0 → dapps-0.3.1}/src/visualization/subcarrier_pub.py +0 -0
  92. {dapps-0.3.0 → dapps-0.3.1}/start_dapp.sh +0 -0
  93. {dapps-0.3.0 → dapps-0.3.1}/tests/test_control_handlers.py +0 -0
  94. {dapps-0.3.0 → dapps-0.3.1}/tests/test_control_server.py +0 -0
  95. {dapps-0.3.0 → dapps-0.3.1}/tests/test_detector_configure.py +0 -0
  96. {dapps-0.3.0 → dapps-0.3.1}/tests/test_detector_granularity_pipeline.py +0 -0
  97. {dapps-0.3.0 → dapps-0.3.1}/tests/test_e3_concurrency.py +0 -0
  98. {dapps-0.3.0 → dapps-0.3.1}/tests/test_indication_dispatch.py +0 -0
  99. {dapps-0.3.0 → dapps-0.3.1}/tests/test_iq_saver.py +0 -0
  100. {dapps-0.3.0 → dapps-0.3.1}/tests/test_latrec.py +0 -0
  101. {dapps-0.3.0 → dapps-0.3.1}/tests/test_libe3_agent.py +0 -0
  102. {dapps-0.3.0 → dapps-0.3.1}/tests/test_policy.py +0 -0
  103. {dapps-0.3.0 → dapps-0.3.1}/tests/test_protobuf_sm.py +0 -0
  104. {dapps-0.3.0 → dapps-0.3.1}/tests/test_ru_profiles.py +0 -0
  105. {dapps-0.3.0 → dapps-0.3.1}/tests/test_sensing_policy.py +0 -0
  106. {dapps-0.3.0 → dapps-0.3.1}/tests/test_spectrum_sm_timestamps_mask.py +0 -0
  107. {dapps-0.3.0 → dapps-0.3.1}/tests/test_spectrum_telemetry.py +0 -0
  108. {dapps-0.3.0 → dapps-0.3.1}/tests/test_subcarrier_pub_viz.py +0 -0
  109. {dapps-0.3.0 → dapps-0.3.1}/tests/test_visualizer_grid.py +0 -0
  110. {dapps-0.3.0 → dapps-0.3.1}/tools/README.md +0 -0
  111. {dapps-0.3.0 → dapps-0.3.1}/tools/asntojson.py +0 -0
  112. {dapps-0.3.0 → dapps-0.3.1}/tools/generate_sm.sh +0 -0
  113. {dapps-0.3.0 → dapps-0.3.1}/tools/read_recording.py +0 -0
  114. {dapps-0.3.0 → dapps-0.3.1}/tools/regen_proto.sh +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dapps
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: A library for creating and connecting dApps to Open RAN units.
5
5
  Keywords: dApps,O-RAN,5G,cellular,networks
6
6
  Author-Email: Andrea Lacava <thecave003@gmail.com>
dapps-0.3.1/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.3.1
@@ -90,6 +90,9 @@ class E3Interface:
90
90
  # Guards the three callback dicts above: caller-thread add_/remove_*
91
91
  # vs. inbound-thread iteration in the _handle_* dispatchers.
92
92
  self._callback_lock = threading.Lock()
93
+ # Source of the report correlation ids (see schedule_report).
94
+ self._report_seq = 0
95
+ self._report_seq_lock = threading.Lock()
93
96
  self.stop_event = threading.Event()
94
97
  # Subscription-response correlation. The wire is fire-and-forget, so a
95
98
  # queued request is NOT an accepted one — a queue put() succeeding
@@ -336,7 +339,7 @@ class E3Interface:
336
339
  case "report":
337
340
  rc = self.agent.send_report(
338
341
  data["ranFunctionId"], data["reportData"],
339
- data.get("sequenceId", 0))
342
+ data["sequenceId"])
340
343
  case "release":
341
344
  rc = self.agent.release()
342
345
  case _:
@@ -467,12 +470,28 @@ class E3Interface:
467
470
  'latSeq': latrec.ctx(),
468
471
  }))
469
472
 
470
- def schedule_report(self, dappId: int, ranFunctionId: int, reportData: bytes):
473
+ def _next_report_sequence_id(self) -> int:
474
+ """Next correlation id for a report: 1..4294967295, wrapping to 1 (0 is not a valid id)."""
475
+ with self._report_seq_lock:
476
+ self._report_seq = self._report_seq % 0xFFFFFFFF + 1
477
+ return self._report_seq
478
+
479
+ def schedule_report(self, dappId: int, ranFunctionId: int, reportData: bytes,
480
+ sequenceId: int = 0) -> int:
481
+ """Queue a report and return its correlation id.
482
+
483
+ The id is mandatory on the wire (E3-SequenceID, 1..4294967295) and travels on to the
484
+ xApp. Pass the one you want, or leave it at 0 to get the next one from this interface.
485
+ """
486
+ if not sequenceId:
487
+ sequenceId = self._next_report_sequence_id()
471
488
  self.outbound_queue.put(('report', {
472
489
  'ranFunctionId': ranFunctionId,
473
490
  'reportData': reportData,
491
+ 'sequenceId': sequenceId,
474
492
  'latSeq': latrec.ctx(),
475
493
  }))
494
+ return sequenceId
476
495
 
477
496
  def add_subscription_callback(self, dapp_id: int, callback):
478
497
  with self._callback_lock:
@@ -247,7 +247,7 @@ class Libe3Agent:
247
247
  ) -> int:
248
248
  """Send a subscribe request.
249
249
 
250
- Returns the assigned request id (a positive int, 1..1000) on success —
250
+ Returns the assigned request id (a positive int) on success —
251
251
  so the caller can correlate the SubscriptionResponse (which echoes
252
252
  ``request_id``) back to this call — or a negative libe3 ErrorCode on
253
253
  failure.
@@ -275,11 +275,12 @@ class Libe3Agent:
275
275
  return self._session.send_control(int(ran_function_id), int(control_id), bytes(action_data),
276
276
  int(sequence_id))
277
277
 
278
- def send_report(self, ran_function_id: int, report_data: bytes, sequence_id: int = 0) -> int:
278
+ def send_report(self, ran_function_id: int, report_data: bytes, sequence_id: int) -> int:
279
279
  """Send a report to the RAN.
280
280
 
281
281
  ``sequence_id`` is the correlation id this dApp assigns to the detection;
282
- it travels to the xApp on the resulting E2 indication. 0 means unset.
282
+ it travels to the xApp on the resulting E2 indication. It is mandatory on the
283
+ wire (1..4294967295); 0 is not valid and is refused with a negative ErrorCode.
283
284
  """
284
285
  return self._session.send_report(int(ran_function_id), bytes(report_data), int(sequence_id))
285
286
 
@@ -3,7 +3,8 @@
3
3
  A policy decides which PRBs the gNB must stop using. The dApp feeds it one
4
4
  :class:`Observation` per detector run and sends the PRB blacklist it returns.
5
5
  Everything else (E3 connection, subscriptions, DC-leakage guard, control
6
- reconciliation and rate limiting) stays in :class:`spectrum.spectrum_dapp.SpectrumSharingDApp`.
6
+ reconciliation, which sends a control only when the set changed) stays in
7
+ :class:`spectrum.spectrum_dapp.SpectrumSharingDApp`.
7
8
 
8
9
  Write a policy by subclassing :class:`Policy`::
9
10
 
@@ -62,9 +63,9 @@ class Observation:
62
63
  class Policy(ABC):
63
64
  """Base class of a PRB-blocking policy.
64
65
 
65
- The dApp calls :meth:`update` once per observation (roughly every 20 ms with the
66
- default gNB settings). It returns the set of PRB indices to block; PRBs that are
67
- not returned are released. State between calls lives on ``self``;
66
+ The dApp calls :meth:`update` once per observation (every few milliseconds, about
67
+ 6.7 ms nominal, depending on how fast the gNB runs). It returns the set of PRB
68
+ indices to block; PRBs that are not returned are released. State between calls lives on ``self``;
68
69
  ``self.history`` holds the last ``history_len`` observations, the current one last.
69
70
  """
70
71
 
@@ -755,11 +755,11 @@ class SpectrumSharingDApp(DApp):
755
755
  Spectrum-DAppControlData envelope.
756
756
 
757
757
  Drives the gNB's per-MAC prb_block_state_t via set_prb_block_mask():
758
- each PRB in ``blocked_prbs`` gets every UL symbol marked occupied,
759
- so downstream scheduling treats it as unavailable.
758
+ each PRB in ``blocked_prbs`` gets every UL symbol marked occupied and is
759
+ withheld from DL scheduling too, so the gNB uses it in neither direction.
760
760
 
761
761
  Args:
762
- blocked_prbs: List of PRB indices to block on the UL
762
+ blocked_prbs: List of PRB indices to block (UL and DL)
763
763
  update_sampling: Whether to include the updated sampling threshold
764
764
  validity_period: How long this block is valid in seconds (optional)
765
765
 
@@ -1747,10 +1747,10 @@ class SpectrumSharingDApp(DApp):
1747
1747
  # mask-of-slot-N applied to IQ-of-slot-N+k → UE energy bleeding
1748
1748
  # into "sensing" cells.
1749
1749
  #
1750
- # Threshold matches the gNB ring TTL: 64 rows × 2 buffers = 128
1751
- # slots × ~0.5 ms/slot at 30 kHz SCS ≈ 64 ms safety budget. We use
1752
- # 50 ms as a conservative cutoff. If breached, treat the IQ as
1753
- # untrustworthy and skip the slot.
1750
+ # The bound comes from the ring depth in the shm header (see
1751
+ # _shm_staleness_ns): 16 rows × 2 buffers = 32 slots × 0.5 ms/slot at
1752
+ # 30 kHz SCS = 16 ms. Until the header is read a fixed 50 ms is used.
1753
+ # If breached, treat the IQ as untrustworthy and skip the slot.
1754
1754
  if p.timestamp_ns > 0:
1755
1755
  lag_ns = t.recv_ns - p.timestamp_ns
1756
1756
  stale_ns = self._shm_staleness_ns()
@@ -47,6 +47,8 @@ def _iface():
47
47
  iface.subscription_callbacks = {}
48
48
  iface.xapp_control_callbacks = {}
49
49
  iface._callback_lock = threading.Lock()
50
+ iface._report_seq = 0
51
+ iface._report_seq_lock = threading.Lock()
50
52
  iface.stop_event = threading.Event()
51
53
  iface.outbound_queue = queue.Queue()
52
54
  return iface
@@ -42,7 +42,7 @@ def _drain(rc, msg, data, caplog):
42
42
 
43
43
 
44
44
  CONTROL = {"ranFunctionId": 1, "controlId": 1, "actionData": b"x"}
45
- REPORT = {"ranFunctionId": 1, "reportData": b"x"}
45
+ REPORT = {"ranFunctionId": 1, "reportData": b"x", "sequenceId": 1}
46
46
 
47
47
 
48
48
  @pytest.mark.parametrize("msg,data", [("control", CONTROL), ("report", REPORT)])
@@ -0,0 +1,51 @@
1
+ """A report always carries a non-zero correlation id: E3-SequenceID is 1..4294967295 on the wire."""
2
+ import os
3
+ import queue
4
+ import sys
5
+ import threading
6
+
7
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
8
+
9
+ from e3interface.e3_interface import E3Interface
10
+
11
+
12
+ def _iface():
13
+ iface = object.__new__(E3Interface)
14
+ iface.outbound_queue = queue.Queue()
15
+ iface._report_seq = 0
16
+ iface._report_seq_lock = threading.Lock()
17
+ return iface
18
+
19
+
20
+ def test_a_report_gets_the_next_non_zero_id():
21
+ iface = _iface()
22
+ ids = [iface.schedule_report(1, 1, b"x") for _ in range(3)]
23
+ assert ids == [1, 2, 3]
24
+ queued = [iface.outbound_queue.get_nowait()[1]["sequenceId"] for _ in range(3)]
25
+ assert queued == ids
26
+
27
+
28
+ def test_an_id_given_by_the_caller_is_kept():
29
+ iface = _iface()
30
+ assert iface.schedule_report(1, 1, b"x", sequenceId=77) == 77
31
+ assert iface.outbound_queue.get_nowait()[1]["sequenceId"] == 77
32
+
33
+
34
+ def test_the_id_wraps_to_1_and_never_reaches_0():
35
+ iface = _iface()
36
+ iface._report_seq = 0xFFFFFFFE
37
+ assert iface.schedule_report(1, 1, b"x") == 0xFFFFFFFF
38
+ assert iface.schedule_report(1, 1, b"x") == 1
39
+
40
+
41
+ def test_ids_are_unique_across_threads():
42
+ iface = _iface()
43
+ out = []
44
+
45
+ def work():
46
+ out.extend(iface.schedule_report(1, 1, b"x") for _ in range(500))
47
+
48
+ threads = [threading.Thread(target=work) for _ in range(4)]
49
+ [t.start() for t in threads]
50
+ [t.join() for t in threads]
51
+ assert len(set(out)) == 2000
dapps-0.3.0/VERSION DELETED
@@ -1 +0,0 @@
1
- 0.3.0
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes