Skip to main content

Plugins

The plugin system allows extending or replacing parts of OCUDU with third-party implementations without modifying the core codebase. Plugins live in the outermost layer of the Clean Architecture: they depend on OCUDU interfaces, but OCUDU never depends on them. The core stack does not know - and does not care - whether it is talking to a built-in implementation or a plugin.

Available plugin types​

PluginPurpose
RadioAdd support for a new split-8 radio device
DU HIInterface a 3rd-party PHY; the built-in PHY and higher layers are not instantiated
DU LOInterface a 3rd-party MAC; the built-in MAC and higher layers are not instantiated
PHY TAPTap uplink IQ symbols at the PHY layer for custom processing, monitoring, or telemetry

How the plugin system works​

The application controls the entire lifecycle. The pattern is the same for all plugin types:

  1. At startup, OCUDU calls a factory entry point exported by the plugin, passing configuration parameters.
  2. The plugin's factory creates one or more objects that implement a well-defined C++ interface.
  3. OCUDU stores a pointer to that interface and calls it at runtime - it never touches the plugin's internals.

This directly realises the Dependency Inversion Principle. OCUDU defines the interface; the plugin provides the implementation. The dependency arrow points inward, toward OCUDU, even though the runtime call goes outward.

Plugin lifecycle​

Startup
OCUDU calls create_phy_tap_factory(nof_rb, nof_ports, args)
└─ Plugin builds factory chain from args
└─ Returns phy_tap_factory to OCUDU

OCUDU calls factory->create() for each cell
└─ Factory creates external_ul_processor chain
└─ phy_tap_impl wraps it and returns phy_tap to OCUDU

Runtime - per symbol (allocated slot)
OCUDU upper PHY calls phy_tap::handle_ul_symbol()
└─ phy_tap_impl calls external_ul_processor::process()
└─ Decorator chain executes in order
└─ Custom DSP reads/writes resource grid
└─ (Optional) ZMQ decorator streams measurements async

Runtime - per slot (quiet slot, if enabled)
OCUDU calls phy_tap::handle_quiet_grid()
└─ external_ul_processor::process_quiet()

Runtime - per PRACH window
OCUDU calls phy_tap::handle_prach_window()
└─ external_ul_processor::process_prach()

Dependency rule​

A well-written plugin:

  • includes only the interface headers provided by OCUDU (external_ul_processor.h, external_processor_factories.h),
  • never includes internal OCUDU headers outside of those interfaces,
  • pre-allocates all memory in constructors - never inside processing callbacks (if it's executed in the critical path),
  • defers any I/O (network, disk) to a background thread so the critical path is not blocked.