ahancock516/oai-n77-reciprocity-51prb-paired-dataset
5G NR Channel Reciprocity — 219,694 Paired UL/DL Channel Estimates Simultaneously captured uplink and downlink channel estimates from a live OpenAirInterface 5G SA network, paired by time and frequency for reciprocity-based physical-layer key generation. Both directions observe the same 612 subcarriers (51 PRB × 12 — the full carrier). This is the point of the dataset: uplink is measured at the gNB, downlink at the UE, on the same resource blocks within milliseconds of each… See the full description on the dataset page: https://huggingface.co/datasets/ahancock516/oai-n77-reciprocity-51prb-paired-dataset.
5G NR Channel Reciprocity — 219,694 Paired UL/DL Channel Estimates
Simultaneously captured uplink and downlink channel estimates from a live OpenAirInterface 5G SA network, paired by time and frequency for reciprocity-based physical-layer key generation.
Both directions observe the same 612 subcarriers (51 PRB × 12 — the full carrier). This is the point of the dataset: uplink is measured at the gNB, downlink at the UE, on the same resource blocks within milliseconds of each other, so the two estimates are of the same physical channel observed from opposite ends.
Why this is hard to obtain
Downlink channel estimates require instrumentation inside the UE's PHY. This capture hooks nr_ue_pdsch_procedures() in OpenAirInterface's UE stack, after channel estimation and before equalisation. Commercial basebands — phones, embedded modems — expose no equivalent access point, so this dataset is necessarily SDR-only. Both devices are USRP B210s running the full OAI UE stack.
That is a genuine limitation (see Limitations), but it is also why paired UL/DL estimates from a real network are rare.
Devices
The two units are the same model from the same manufacturing batch (adjacent serials). They required different transmit gains to reach matched received SNR because unit 2 is ~3 dB louder at identical settings — see Transmit power calibration.
Files
The raw captures are included so the pairing can be redone with different criteria — window width, comparison domain, alignment — without re-running the network. build_paired_dataset.py and its readers are included too.
Records are deliberately not trimmed to a round count: unlike a classification dataset, pairing wants maximum UL/DL overlap rather than balanced classes.
HDF5 structure
One group per pair, pair_00000 … :
nsym is 3, 12 or 13 depending on the grant. Root attributes record the pairing statistics (paired, no_overlap, total_ul, total_dl, max_slot_distance, max_wall_clock_delta_s, slots_per_frame, site).
Both the delay-domain overlap vectors and the full per-capture arrays are stored, so alignment and averaging can be redefined downstream without re-pairing.
import h5py, numpy as np
f = h5py.File("paired_b210_unit1_51prb.h5", "r")
print(dict(f.attrs)) # pairing statistics
g = f["pair_00100"]
ul = g["ul_H_overlap"][:] # (1024,) complex128
dl = g["dl_H_overlap"][:] # (1024,) complex128
# reciprocity check: correlation between the two directions
rho = np.abs(np.vdot(ul, dl)) / (np.linalg.norm(ul) * np.linalg.norm(dl))How pairs were formed
A pair is one UL capture + the nearest DL capture from the same UE, matched on wall-clock timestamp with a slot-distance guard, then restricted to the overlapping absolute-subcarrier range.
Exact (frame, slot) equality is impossible by construction — this is TDD, and UL and DL never share a slot (DDSUU: DL slots {0,1}, UL slots {3,4}).
Why wall-clock time, and not radio timing
Neither radio has a GPSDO. The X410 (gNB) and both B210s run on their internal references — the UE is launched with --clock-source 0 --time-source 0. There is no common 10 MHz reference and no 1 PPS distributed between them.
With GPSDO-locked radios, both ends would derive time from GPS and their frame/slot counters would be absolutely aligned, so pairs could be matched on (frame, slot) directly with sub-microsecond confidence. Without it, the two radios' frame counters are unrelated and drift against each other, so radio timing cannot be used to establish that a UL and a DL capture describe the same instant.
The fallback is host wall-clock time, which is why both plugins record timestamp_ns from CLOCK_REALTIME (not CLOCK_MONOTONIC — a monotonic clock has no meaning across two machines) and why the two hosts must be NTP/chrony synchronized to each other.
What that costs. Pairing precision is bounded by NTP accuracy — roughly a millisecond on a LAN — rather than the nanosecond-scale alignment a GPSDO would give. The parameters actually used reflect that:
A 50 ms window is far coarser than the 2.5 ms TDD period. It is the honest bound given the hardware, not a design preference.
Why the pairs are still valid here. This is a static indoor bench: no mobility, fixed antennas. Coherence time is set by environmental motion rather than Doppler, and indoors at 4 GHz that is on the order of hundreds of milliseconds to seconds — comfortably longer than the 50 ms window. Each pair therefore describes the same channel state.
Where this would break. Add mobility and it stops holding. At walking pace (~1 m/s) at 4009 MHz the Doppler shift is ~13 Hz, giving a coherence time around 30 ms — shorter than the pairing window, so pairs would straddle decorrelated channel states and measured reciprocity would degrade for reasons that have nothing to do with the channel. Mobile reciprocity work with this method requires GPSDO-locked radios so pairing can move to radio frame timing.
Frequency alignment is valid because the deployment uses the same Point A and carrier bandwidth for UL and DL, making (bwp_start + rb_start) × 12 a common absolute-subcarrier index in both directions.
python3 build_paired_dataset.py \
--pusch pusch/pusch_b210_unit1_imsi001_51prb_110000.bin \
--pdsch pdsch/pdsch_b210_unit1_imsi001_51prb.bin \
--out paired_b210_unit1_51prb.h5 \
--pairing-method timestamp_primary \
--comparison-domain delay \
--site CCAIPairing statistics, both units:
no_overlap: 0 means every pair had some comparable band; the 99.9–100.0% figures quoted above are the share with the full 612 subcarriers.
Radio configuration — identical for both units
Both allocations are pinned by compiled-in scheduler constants (FIXED_PUSCH_RB_SIZE, FIXED_PDSCH_RB_SIZE). The downlink constant is scoped to slots actually carrying user-plane data — a hard cap on every DL transmission starves PDU Session Setup and RRC Reconfiguration, whose control-plane messages need more PRBs than the fixed size at a realistic MCS. Consequently ~0.1% of records are unconstrained control-plane grants at other widths; the pairing reports these as partial-overlap pairs.
Traffic: sustained bidirectional iperf3 (UDP, 5 Mbit/s each way) to drive real DTCH grants in both directions.
Transmit power calibration
Uncalibrated (OAI's default --ue-txgain 0, i.e. full 89.75 dB) a B210 measured 41 dB UL SNR. That reading reflected gNB receiver compression — the noise estimate was dominated by distortion products scaling with the signal, so the captured IQ contained the receiver's own non-linearity. A high SNR reading in this setup is not good news.
--ue-txgain is attenuation from maximum in OAI (set_tx_gain(gain_range.stop() - tx_gain)), so higher means quieter.
Final values, measured not assumed:
The units needed different settings because unit 2 is ~3 dB louder at identical gain (different bench position and antenna coupling). An earlier attempt with both at 26 produced 19.0 vs 22.5 dB and was discarded. Note this means the two units' power amplifiers sit at slightly different backoff points — a trade accepted to match received SNR, which is what appears in the estimates.
The calibration is position-specific, not a property of the unit. Unit 1 required 36 at one bench position and 26 at another. Reproducing this needs re-measuring to the ~19 dB target, not reusing these numbers.
Per-unit link statistics
Low SNR minima (unit 2's 7.5 dB) are single samples from released RRC contexts printing final statistics, not real channel events. Medians are the comparable figures.
Limitations
SDR-only, and both ends run the same codebase. Both devices are B210s running OpenAirInterface's UE stack — the same code family as the gNB. For reciprocity this is arguably an advantage (identical, inspectable processing at both ends, no vendor black box) but results may not transfer to commercial basebands, which cannot be instrumented this way at all.
Unit 2's traffic was asymmetric — DRB ratio 1.22:1 versus unit 1's 1.00:1, because downlink accumulated while the device sat attached before iperf started. This does not bias reciprocity (pairing is by timestamp, and each pair compares UL against DL within one device) but the two sessions are not identical in offered load.
Unit 2 shows 2,150 UL CCE failures against unit 1's 55, and 1,257 pucch0_DTX against 133, over a longer session (381,891 vs 236,390 UL rounds).
MCS table 0 on both units — the OAI UE stack negotiates a different modulation table than commercial basebands.
Static bench, single environment. No mobility, one indoor location, one antenna arrangement. Channel decorrelation over time is limited; treat temporal splits accordingly.
No GPSDO — pairing precision is bounded by NTP, not radio timing. Neither the X410 nor the B210s have a GPS-disciplined oscillator, so their frame counters are independent and pairs are matched on host wall-clock time within a 50 ms window instead of on (frame, slot). Valid for this static bench, where coherence time is hundreds of milliseconds or more, but not transferable to mobile scenarios — at walking pace coherence time drops to ~30 ms, below the pairing window. See Why wall-clock time, and not radio timing.
PA backoff differs ~3 dB between the two units, a consequence of matching received SNR rather than gain setting.
Intended use
Reciprocity-based physical-layer key generation and secret-key-capacity studies: quantifying UL/DL channel correlation, reciprocity error, and the key-agreement rate achievable from real 5G NR channel estimates rather than simulation.
Also usable for channel-estimation and equalisation research, since raw IQ and the gNB/UE channel estimates are both stored.
Not a fingerprinting dataset — for device identification see the companion uplink-only collections, which cover six devices across three hardware classes:
Suggested protocol: hold out by time, not random sampling — adjacent pairs share channel state and random splits leak. Report reciprocity correlation and derived key-agreement metrics per unit separately before pooling, since the two sessions differ in length and offered load.
Provenance
Captured 2026-08-06 on a self-hosted OpenAirInterface 5G SA network (NVIDIA DGX Spark + USRP X410) using nr_pusch_capture from the NVIDIA Sionna Research Kit for the uplink and nr_pdsch_capture for the downlink. Both units used one gNB configuration and one gNB binary. Each session ran to 110,000 accepted uplink slots with the downlink capture running concurrently.
