Skip to content
Docs / dflux Runner
ContactGet started

Flows

A flow is the unit dflux Runner executes. It encodes one protocol procedure as a finite-state machine in YAML. This page is the conceptual core of the docs — read it first, then States and transitions, then Actions.

The shape of a procedure

5G/4G procedures share the same cadence: wait for an event, check it, send the next message. Registration walks from an initial UE message through authentication and security activation to a registered state, as the diagram below shows. PDU session establishment, deregistration, paging, and handover all follow the same pattern.

A finite-state machine captures that cadence directly. Each state is a "wait for" point. Each transition out of the state is an "if X happens, do Y, go to Z" rule. The whole procedure is a directed graph from initial_state to one of the final_states.

That diagram is the shipped templates/gnb/registration.yaml. Every box, arrow, label is one line of YAML.

What a flow declares

YAML

The full schema reference is at Flow schema reference. This page covers the conceptual pieces — the next two pages dive into states and transitions and actions.

Client vs server flows

Same FSM model, different trigger.

Client-mode flows initiate the procedure. The engine fires a synthetic Start event into the FSM's initial_state, which dispatches the first send. Most NGAP gNB-side flows and Diameter MME-side flows are client-mode.

Server-mode flows wait for a peer to send the first message. No Start event; the FSM auto-spawns when the inbound demux receives a matching message at initial_state. There must be at least one transition at initial_state whose event matches an inbound RX message. The shipped templates/amf/registration_amf.yaml, templates/sbi/nudm_sdm_get_server.yaml, and templates/rest/edge_admin_server.yaml are the canonical examples.

The engine validates this at flow-load: client flows must have a Start-event transition out of initial_state; server flows must not. Mistakes fail fast.

Per-UE state, per-flow context

When a flow starts for one UE, the engine creates a UEContext carrying that UE's subscriber, its FSM state, its message log, and a Params map for ad-hoc state. A hundred concurrent UEs each have their own UEContext — checks, extracts, and sends all read/write the right one without explicit author intervention.

UEs running the same flow share a flow context that holds aggregate metrics and per-flow handover state, but UE-level state is isolated. The UEContext is the surface the flow author thinks in, addressed through ue. paths (ue.AmfUeNgapId, ue.SecCtx, ue.Params.<key>).

What runs at flow time

When you run run-flow -flow registration, the engine:

  1. Looks up registration in the catalog.
  2. Parses the YAML into a state machine and validates it.
  3. Pre-parses each send — its message_body JSON is loaded into the typed message prototype, and every {{...}} template is compiled.
  4. For each UE the workload calls for (-repetitions, -rate, -duration), creates a UEContext and dispatches a Start event.
  5. As inbound events arrive, matches each against the current state's transitions, executes the matching transition's actions, and advances. This repeats until the UE reaches a final state.
  6. Persists a report entry and exits.

Authoring a flow means declaring the data for steps 3 and 4. Everything else is the engine's job.

Why YAML

A hand-coded state machine would be perfectly precise — and unmaintainable for the test engineers who write hundreds of these. YAML makes the structure obvious at a glance: every state visible, every transition visible, every action visible. New flows are typically copy-paste-edit from a sibling. The schema is enforced at load time — bad YAML never reaches the engine.

The cost is that some procedures don't fit the FSM model cleanly (anything with arbitrary loops or unbounded state). For those, drive multiple flows back-to-back via a suite.

Where to go next