diff --git a/CMakeLists.txt b/CMakeLists.txt index 5ec2d0d..19b906d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -181,6 +181,11 @@ else(SRSRAN_FOUND AND NOT FORCE_SUBPROJECT_SRSRAN) WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/srsRAN-download" ) execute_process(COMMAND "${CMAKE_COMMAND}" --build . WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/srsRAN-download" ) + execute_process( + COMMAND "${CMAKE_COMMAND}" -DSRSRAN_SRC_DIR=${CMAKE_BINARY_DIR}/srsRAN-src + -P "${CMAKE_SOURCE_DIR}/external/cmake/PatchDownloadedSrsran.cmake" + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" + ) include_directories("${CMAKE_BINARY_DIR}/srsRAN-build/lib/include") include_directories("${CMAKE_BINARY_DIR}/srsRAN-src/lib/include") @@ -318,14 +323,14 @@ if(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_C_COMPILER_ID MATCHES "Clang") endif(HAVE_SSE) endif(NOT ${CMAKE_BUILD_TYPE} STREQUAL "Debug") - if(${CMAKE_SYSTEM_PROCESSOR} MATCHES "arm") - - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mfpu=neon -march=native -DIS_ARM -DHAVE_NEON") + if(${CMAKE_SYSTEM_PROCESSOR} MATCHES "^(arm|aarch64)") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=native -DIS_ARM -DHAVE_NEON") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native -DIS_ARM -DHAVE_NEON") message(STATUS "have ARM") set(HAVE_NEON "True") - else(${CMAKE_SYSTEM_PROCESSOR} MATCHES "arm") + else(${CMAKE_SYSTEM_PROCESSOR} MATCHES "^(arm|aarch64)") set(HAVE_NEON "False") - endif(${CMAKE_SYSTEM_PROCESSOR} MATCHES "arm") + endif(${CMAKE_SYSTEM_PROCESSOR} MATCHES "^(arm|aarch64)") set(CMAKE_REQUIRED_FLAGS ${CMAKE_C_FLAGS}) if(NOT HAVE_SSE AND NOT HAVE_NEON AND NOT DISABLE_SIMD) diff --git a/README.md b/README.md index 5b023b6..8ee1367 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,34 @@ cd build cmake ../ make -j 4 (use 4 threads) ``` + +### Build this PR branch from scratch + +This repository also has a PR branch with raw IQ replay/capture support: + +```bash +git clone --branch LTESniffer-pr-raw-io https://github.com/vk5zsn/LTESniffer.git +cd LTESniffer +git rev-parse --short HEAD +``` + +The tested clean-build commit is: + +```bash +1925243 +``` + +On aarch64 systems such as Raspberry Pi 5, this branch includes the fix for the old `no SIMD instructions found` configure failure. It also applies the required vendored `srsRAN` build patches automatically during configure on a fresh clone. + +Build commands: + +```bash +mkdir build +cd build +cmake .. +cmake --build . --target LTESniffer -j4 +``` + ## Usage LTESniffer has 3 main functions: - Sniffing LTE downlink traffic from the base station @@ -191,6 +219,40 @@ example: sudo ./src/LTESniffer -A 2 -W 4 -f 1840e6 -u 1745e6 -I 379 -p 100 -m 1 ``` The debug mode can be enabled by using option ``-d``. In this case, the debug messages will be printed on the terminal. +### Raw IQ replay and capture + +The `LTESniffer-pr-raw-io` branch adds: + +- `-Q` live raw IQ capture to `cf32` +- `-j` generic raw IQ replay with synchronization +- `-J` raw replay input is interleaved `sc16` +- `-G` minimum DL DCI search SNR for offline replay + +Verified live capture example: + +```bash +.//src/LTESniffer -A 1 -W 4 -f 763e6 -C -m 0 -a "num_recv_frames=512" -n 500 -Q iq_763M_cf32.bin +``` + +Verified raw replay example: + +```bash +.//src/LTESniffer -i iq_763M_cf32.bin -j -c 405 -p 50 -A 1 -W 4 -m 0 -n 500 +``` + +For raw replay, provide: + +- `-c ` +- `-p ` + +Verified Gqrx raw `fc32` replay example at `11.52 Msps`: + +```bash +.//src/LTESniffer -i gqrx_20260413_233101_763000000_11520000_fc.raw -j -c 405 -p 50 -A 1 -W 4 -m 0 -n 500 +``` + +If the raw file is interleaved `sc16`, add `-J`. + ### Output of LTESniffer LTESniffer provides pcap files in the output. The pcap file can be opened by WireShark for further analysis and packet trace. @@ -287,4 +349,4 @@ To sniff the uplink traffic, LTESniffer requires USRP X310 with 2 daughterboards [capture-readme]: https://github.com/SysSec-KAIST/LTESniffer/tree/LTESniffer-record-subframe [cellular77]: https://github.com/cellular777 [Cemaxecuter]: https://www.youtube.com/@cemaxecuter7783 -[Ksk190809]: https://github.com/Ksk190809 \ No newline at end of file +[Ksk190809]: https://github.com/Ksk190809 diff --git a/docs/fresh_branch_verification_2026-04-13.md b/docs/fresh_branch_verification_2026-04-13.md new file mode 100644 index 0000000..004becf --- /dev/null +++ b/docs/fresh_branch_verification_2026-04-13.md @@ -0,0 +1,95 @@ +# Fresh Branch Verification + +Date: `2026-04-13` +Worktree: `/home/vk2tlq/LTESniffer-pr-raw-io` +Binary: `/home/vk2tlq/LTESniffer-pr-raw-io/build/src/LTESniffer` + +This note captures the verification runs performed from the clean PR branch itself. + +## CLI surface + +`-h` shows the new options: + +- `-G minimum DL DCI search SNR in dB for offline replay [Default 6.0]` +- `-j treat -i as generic raw IQ and synchronize it before decoding` +- `-J raw -i/-j input is interleaved sc16 instead of cf32` +- `-Q output live raw IQ capture to cf32 file while decoding` + +## Record-subframe replay + +Command: + +```bash +env LD_LIBRARY_PATH=/opt/install/uhd/lib \ + /home/vk2tlq/LTESniffer-pr-raw-io/build/src/LTESniffer \ + -A 1 -W 1 \ + -i /home/vk2tlq/subframe_iq_sample.bin \ + -P 2 -c 405 -p 50 -m 0 -n 200 +``` + +Observed result: + +- MIB decoded: `PCI 405 / PRB 50 / ports 2 / PHICH Resources 1` +- final stats: + - `nof_decoded_locations=1103` + - `nof_cce=564` + - `nof_missed_cce=1` + - `nof_subframes=20` + - `nof_locations=1040` + +## Direct raw `cf32` replay + +Command: + +```bash +env LD_LIBRARY_PATH=/opt/install/uhd/lib \ + /home/vk2tlq/LTESniffer-pr-raw-io/build/src/LTESniffer \ + -A 1 -W 4 \ + -i /home/vk2tlq/live_gold_763M_11p52M_cf32.bin \ + -j \ + -P 2 -c 405 -p 50 -G 1.0 -m 0 -n 2000 +``` + +Observed result: + +- lock point: `raw_samples=172040` +- MIB decoded: `PCI 405 / PRB 50 / ports 2 / PHICH Resources 1` +- final stats: + - `nof_decoded_locations=72506` + - `nof_cce=56383` + - `nof_missed_cce=1541` + - `nof_subframes=1954` + - `nof_locations=104221` + +## Direct raw `sc16` replay + +Command: + +```bash +env LD_LIBRARY_PATH=/opt/install/uhd/lib \ + /home/vk2tlq/LTESniffer-pr-raw-io/build/src/LTESniffer \ + -A 1 -W 4 \ + -i /dev/shm/iq_763M_11p52M_sc16.bin \ + -j -J \ + -P 2 -c 405 -p 50 -G 1.0 -m 0 -n 1000 +``` + +Observed result: + +- lock point: `raw_samples=81783` +- MIB decoded: `PCI 405 / PRB 50 / ports 2 / PHICH Resources 1` +- final stats: + - `nof_decoded_locations=9613` + - `nof_cce=11984` + - `nof_missed_cce=24` + - `nof_subframes=936` + - `nof_locations=22239` + +## Build notes + +Two local dependency-tree fixes were needed in the generated `srsRAN` build directory on this Pi5/GCC 12 host: + +- add `` to `build/srsRAN-src/lib/include/srsran/srslog/bundled/fmt/core.h` +- initialize `cc_list` in `build/srsRAN-src/srsenb/src/stack/mac/nr/ue_nr.cc` + +These are not branch source changes and are not intended for the pull request diff. diff --git a/docs/gold_standard_manifest_2026-04-13.md b/docs/gold_standard_manifest_2026-04-13.md new file mode 100644 index 0000000..526055e --- /dev/null +++ b/docs/gold_standard_manifest_2026-04-13.md @@ -0,0 +1,42 @@ +# Gold Standard Manifest + +Date: `2026-04-13` +Host: `vk2tlq@piSDR` + +This branch does not commit the raw capture binaries. The files below remain local to the host and are referenced by path and checksum for reproducibility. + +## Local Files + +### Live raw `cf32` gold capture + +- Path: `/home/vk2tlq/live_gold_763M_11p52M_cf32.bin` +- Format: `cf32` +- Sample rate: `11.52 Msps` +- Center frequency: `763 MHz` +- Size: `922148560` bytes +- SHA256: `95cacdeb4c669bbbc7299b4fbb933f15b202100cc63e349780664081958ef70d` + +### Record-subframe branch sample + +- Path: `/home/vk2tlq/subframe_iq_sample.bin` +- Format: synchronized replay file emitted by `LTESniffer-record-subframe` +- Size: `17418240` bytes +- SHA256: `b33801ffee329244c0559f707b8c1864bcc24c55134eb7dcc07ebf3301d638a3` + +### Robust raw `sc16` capture + +- Path: `/dev/shm/iq_763M_11p52M_sc16.bin` +- Format: `sc16` +- Sample rate: `11.52 Msps` +- Center frequency: `763 MHz` +- Size: `458506240` bytes +- SHA256: `76b911f74d2f6499ff92577c6d6fc23e387e4b8a815eb30879303c6cdfb656f7` + +## Why These Files Matter + +- `live_gold_763M_11p52M_cf32.bin` + Gold reference generated from the same live `LTESniffer` receive path via `-Q`. +- `subframe_iq_sample.bin` + Confirms compatibility with the synchronized file format used by the `LTESniffer-record-subframe` branch. +- `iq_763M_11p52M_sc16.bin` + Robust raw capture produced in the cleaned Pi5/B210 setup and replayed through `-j -J`. diff --git a/docs/gold_standard_results_2026-04-13.md b/docs/gold_standard_results_2026-04-13.md new file mode 100644 index 0000000..8c8e1bc --- /dev/null +++ b/docs/gold_standard_results_2026-04-13.md @@ -0,0 +1,138 @@ +# Gold Standard Results + +Date: `2026-04-13` +Host: `vk2tlq@piSDR` + +## Live capture with `-Q` + +Command: + +```bash +sudo env LD_LIBRARY_PATH=/opt/install/uhd/lib \ + ./LTESniffer \ + -A 1 -W 4 -f 763e6 -C -m 0 -n 10000 \ + -a "num_recv_frames=512" \ + -Q /home/vk2tlq/live_gold_763M_11p52M_cf32.bin +``` + +Key results: + +- UHD: `4.9.0.0-0-g006d7f76` +- MIB: `PCI 405`, `PRB 50`, `ports 2`, `PHICH Resources 1` +- `nof_decoded_locations=162350` +- `nof_cce=221312` +- `nof_missed_cce=2773` +- `nof_subframes=9991` +- `nof_locations=408723` +- raw sink summary: + - `overflows=0` + - `late_rx=0` + - `rx_errors=0` + - `other_errors=0` + - `timestamp_gap_events=0` + - `timestamp_gap_samples_accum=0` + - `timestamp_gap_samples_max_abs=0` + - `file_write_failed=0` + +## Offline replay of the live gold `cf32` file + +Command: + +```bash +env LD_LIBRARY_PATH=/opt/install/uhd/lib \ + ./LTESniffer \ + -A 1 -W 4 \ + -i /home/vk2tlq/live_gold_763M_11p52M_cf32.bin \ + -j \ + -P 2 -c 405 -p 50 -G 1.0 -m 0 -n 10000 -d +``` + +Key results: + +- lock point: `raw_samples=172040` +- MIB: `PCI 405`, `PRB 50`, `ports 2`, `PHICH Resources 1` +- `nof_decoded_locations=159681` +- `nof_cce=219694` +- `nof_missed_cce=2780` +- `nof_subframes=9938` +- `nof_locations=405726` + +Live vs replay deltas: + +- `nof_decoded_locations`: `-1.64%` +- `nof_cce`: `-0.73%` +- `nof_subframes`: `-0.53%` +- `nof_locations`: `-0.73%` + +Interpretation: + +- direct raw replay is close to live when the file comes from the internal `-Q` sink +- this validates the raw ingest path independent of third-party capture tools + +## Record-subframe compatibility + +Command: + +```bash +env LD_LIBRARY_PATH=/opt/install/uhd/lib \ + ./LTESniffer \ + -A 1 -W 1 \ + -i /home/vk2tlq/subframe_iq_sample.bin \ + -P 2 -c 405 -p 50 -m 0 -n 200 -d +``` + +Key results: + +- valid MIB decode for `PCI 405 / PRB 50 / ports 2 / PHICH Resources 1` +- nonzero offline decode stats +- multiple RNTIs decoded successfully + +Interpretation: + +- the offline PHICH resource preset change keeps replay compatible with files produced by the `LTESniffer-record-subframe` branch + +## Robust raw `sc16` replay + +Capture command: + +```bash +sudo env LD_LIBRARY_PATH=/opt/install/uhd/lib \ + /home/vk2tlq/LTESniffer/build/src/UhdCaptureRobust \ + --output /dev/shm/iq_763M_11p52M_sc16.bin \ + --args "type=b200,num_recv_frames=512,master_clock_rate=23.04e6" \ + --freq 763e6 \ + --rate 11.52e6 \ + --gain 50 \ + --antenna RX2 \ + --duration 10 \ + --format sc16 \ + --wirefmt sc16 \ + --metadata +``` + +Replay command: + +```bash +env LD_LIBRARY_PATH=/opt/install/uhd/lib \ + ./LTESniffer \ + -A 1 -W 4 \ + -i /dev/shm/iq_763M_11p52M_sc16.bin \ + -j -J \ + -P 2 -c 405 -p 50 -G 1.0 -m 0 -n 9950 -d +``` + +Key results: + +- capture: `overflows=0 timeouts=0 late=0 other_errors=0` +- replay lock: `raw_samples=81783` +- replay MIB: `PCI 405 / PRB 50 / ports 2 / PHICH Resources 1` +- `nof_decoded_locations=63229` +- `nof_cce=165327` +- `nof_missed_cce=308` +- `nof_subframes=9865` +- `nof_locations=305978` + +Interpretation: + +- the raw replay path works for both `cf32` and `sc16` +- the cleaned Pi5/B210 setup is stable enough to use as a baseline capture environment diff --git a/docs/raw_iq_pr_notes.md b/docs/raw_iq_pr_notes.md new file mode 100644 index 0000000..7f5adab --- /dev/null +++ b/docs/raw_iq_pr_notes.md @@ -0,0 +1,76 @@ +# Raw IQ Replay And Capture Notes + +## Scope + +This branch is a clean `origin/main` derivative that keeps only the changes needed for: + +- replaying generic raw IQ captures through `LTESniffer` +- writing a live raw IQ sink while decoding +- preserving compatibility with synchronized replay files produced by the `LTESniffer-record-subframe` branch + +Large binary captures are intentionally not committed in this branch. See [gold_standard_manifest_2026-04-13.md](gold_standard_manifest_2026-04-13.md) for local file paths, sizes, and checksums. + +## User-visible Changes + +### New CLI options + +- `-j` + Treat `-i` as a generic raw IQ capture that must be synchronized before decode. +- `-J` + Interpret raw `-i -j` input as interleaved `sc16` instead of `cf32`. +- `-Q /path/to/output.bin` + In live RF mode, write the exact received IQ stream to a raw `cf32` file while decoding. +- `-G ` + Minimum DL DCI search SNR for offline replay. Default remains `6.0 dB` to preserve baseline behavior. + +### Offline replay compatibility + +- File replay now presets offline PHICH resources to `SRSRAN_PHICH_R_1`. +- This is the compatibility fix needed for files produced from the `LTESniffer-record-subframe` branch and for the validated local replay captures in this environment. + +## Implementation Summary + +### Raw file replay + +- Added a file-backed receive callback that lets a raw IQ file behave like a live RF source. +- The raw replay path runs through `ue_sync`, waits for a stable `sf_idx 0`, and only transitions into normal decode after a valid MIB is found. +- Optional `sc16` input is converted to internal `cf_t` buffers on read. +- `-o` frequency correction is applied in the raw replay callback path. + +### Live raw sink + +- The RF receive wrapper can now mirror the live stream into a `cf32` file via `-Q`. +- The wrapper also tracks: + - `overflows` + - `late_rx` + - `rx_errors` + - `other_errors` + - timestamp gap counts and magnitudes +- The summary is printed at shutdown so the capture file and decoder run can be correlated. + +### Offline decode tuning + +- The DL DCI SNR gate is now configurable with `-G`. +- The default stays conservative (`6.0 dB`) for reviewability and to avoid surprising baseline users. + +## Review Focus + +The intended review surface is: + +- [ArgManager.h](../src/include/ArgManager.h) +- [ArgManager.cc](../src/src/ArgManager.cc) +- [DCISearch.h](../src/include/DCISearch.h) +- [DCISearch.cc](../src/src/DCISearch.cc) +- [Phy.h](../src/include/Phy.h) +- [Phy.cc](../src/src/Phy.cc) +- [SubframeWorker.h](../src/include/SubframeWorker.h) +- [SubframeWorker.cc](../src/src/SubframeWorker.cc) +- [LTESniffer_Core.h](../src/include/LTESniffer_Core.h) +- [LTESniffer_Core.cc](../src/src/LTESniffer_Core.cc) + +## Notes For A Pull Request + +- The code changes are self-contained inside `LTESniffer` and do not require the auxiliary local capture helper added in other experimental branches. +- The local verification build required the same vendored `srsRAN` GCC 12 compatibility fix as other worktrees: + add `` to `build/srsRAN-src/lib/include/srsran/srslog/bundled/fmt/core.h`. + That is a generated dependency-tree fix and is not part of the branch source diff. diff --git a/external/cmake/PatchDownloadedSrsran.cmake b/external/cmake/PatchDownloadedSrsran.cmake new file mode 100644 index 0000000..b989073 --- /dev/null +++ b/external/cmake/PatchDownloadedSrsran.cmake @@ -0,0 +1,50 @@ +if(NOT DEFINED SRSRAN_SRC_DIR) + message(FATAL_ERROR "SRSRAN_SRC_DIR is required") +endif() + +function(patch_file_contains path needle replacement description) + file(READ "${path}" content) + string(FIND "${content}" "${replacement}" replacement_pos) + if(NOT replacement_pos EQUAL -1) + message(STATUS "${description}: already applied") + return() + endif() + + string(FIND "${content}" "${needle}" needle_pos) + if(needle_pos EQUAL -1) + message(FATAL_ERROR "${description}: expected snippet not found in ${path}") + endif() + + string(REPLACE "${needle}" "${replacement}" patched "${content}") + file(WRITE "${path}" "${patched}") + message(STATUS "${description}: applied") +endfunction() + +set(fmt_core "${SRSRAN_SRC_DIR}/lib/include/srsran/srslog/bundled/fmt/core.h") +patch_file_contains( + "${fmt_core}" + [=[#include +#include +#include ]=] + [=[#include +#include +#include +#include ]=] + "fmt include fix") + +set(srsran_cmake "${SRSRAN_SRC_DIR}/CMakeLists.txt") +patch_file_contains( + "${srsran_cmake}" + [=[# Add -Werror to C/C++ flags for newer compilers +if(NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror") +endif()]=] + [=[# Add -Werror to C/C++ flags for newer compilers +# Disabled for local GCC 12/aarch64 builds where vendored srsRAN emits warnings +# that are non-fatal in practice but stop the full LTESniffer build. +# if(NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0) +# set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror") +# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror") +# endif()]=] + "srsRAN Werror disable") diff --git a/src/include/ArgManager.h b/src/include/ArgManager.h index 6174b74..7e07f50 100644 --- a/src/include/ArgManager.h +++ b/src/include/ArgManager.h @@ -14,6 +14,8 @@ struct Args { uint32_t time_offset; int force_N_id_2; std::string input_file_name = ""; + bool input_file_raw_sync = false; + bool input_file_raw_sc16 = false; std::string dci_file_name = ""; std::string stats_file_name = ""; int file_offset_time; @@ -23,6 +25,7 @@ struct Args { uint32_t file_nof_ports; uint32_t file_cell_id; bool file_wrap; + std::string raw_iq_output_file = ""; std::string rf_args; uint32_t rf_nof_rx_ant; double rf_freq; @@ -48,6 +51,7 @@ struct Args { char* rf_dev; //char* rf_args; int enable_cfo_ref; + double dl_dci_min_snr_db; std::string estimator_alg; bool cell_search = false; uint16_t target_rnti = 0; diff --git a/src/include/DCISearch.h b/src/include/DCISearch.h index 4253e5a..857eb7f 100644 --- a/src/include/DCISearch.h +++ b/src/include/DCISearch.h @@ -24,7 +24,8 @@ class DCISearch { uint32_t sf_idx, uint32_t sfn, srsran_dl_sf_cfg_t *sf, - srsran_ue_dl_cfg_t *ue_dl_cfg); + srsran_ue_dl_cfg_t *ue_dl_cfg, + double min_snr_db); int search(); DCIBlindSearchStats& getStats(); @@ -57,6 +58,7 @@ class DCISearch { SubframePower& subframePower; uint32_t sf_idx; uint32_t sfn; + double min_snr_db; DCIBlindSearchStats stats; bool enableShortcutDiscovery; std::vector temp_dci0; diff --git a/src/include/LTESniffer_Core.h b/src/include/LTESniffer_Core.h index 00dd81e..16a507d 100644 --- a/src/include/LTESniffer_Core.h +++ b/src/include/LTESniffer_Core.h @@ -34,8 +34,10 @@ #include "Phy.h" #include "PcapWriter.h" #include "HARQ.h" +#include #include #include +#include #include #include #include "srsue/hdr/ue.h" @@ -73,6 +75,37 @@ typedef struct { } UL_Sniffer_ta_buffer_t; +typedef struct { + FILE* file; + bool eof; + bool wrap; + uint64_t samples_read; + bool input_is_sc16; + int16_t* sc16_buffer; + uint32_t sc16_capacity_samples; + bool apply_cfo; + float cfo_freq; + srsran_cfo_t cfo_correct; +} raw_iq_file_source_t; + +typedef struct { + srsran_rf_t* rf; + srsran_filesink_t* raw_sink; + uint32_t nof_channels; + bool write_failed; + double rx_srate; + bool have_last_rx_ts; + uint64_t last_rx_ts_samples; + uint32_t last_rx_nsamples; + std::atomic overflow_count; + std::atomic late_rx_count; + std::atomic rx_error_count; + std::atomic other_error_count; + uint64_t timestamp_gap_events; + int64_t timestamp_gap_samples_accum; + int64_t timestamp_gap_samples_max_abs; +} rf_live_source_t; + static SRSRAN_AGC_CALLBACK(srsran_rf_set_rx_gain_th_wrapper_) { srsran_rf_set_rx_gain_th((srsran_rf_t*)h, gain_db); @@ -82,6 +115,8 @@ int srsran_rf_recv_wrapper( void* h, cf_t* data_[SRSRAN_MAX_PORTS], uint32_t nsamples, srsran_timestamp_t* t); +void srsran_rf_error_handler_wrapper(void* arg, srsran_rf_error_t error); +int raw_iq_file_recv_wrapper(void* h, cf_t* data_[SRSRAN_MAX_PORTS], uint32_t nsamples, srsran_timestamp_t* t); class LTESniffer_Core : public SignalHandler { public: diff --git a/src/include/Phy.h b/src/include/Phy.h index db2baa2..92aee9b 100644 --- a/src/include/Phy.h +++ b/src/include/Phy.h @@ -33,7 +33,8 @@ class Phy { HARQ *harq, int mcs_tracking_mode, int harq_mode, - ULSchedule *ulsche); + ULSchedule *ulsche, + double dl_dci_min_snr_db); ~Phy(); std::shared_ptr getAvail(); std::shared_ptr getAvailImmediate(); diff --git a/src/include/SubframeWorker.h b/src/include/SubframeWorker.h index 4ce969d..c085d19 100644 --- a/src/include/SubframeWorker.h +++ b/src/include/SubframeWorker.h @@ -25,7 +25,8 @@ class SubframeWorker { int mcs_tracking_mode, int harq_mode, ULSchedule *ulsche, - int sniffer_mode); + int sniffer_mode, + double dl_dci_min_snr_db); ~SubframeWorker(); bool setCell(srsran_cell_t cell); @@ -69,6 +70,7 @@ class SubframeWorker { int mcs_tracking_mode; MCSTracking *mcs_tracking; int harq_mode; + double dl_dci_min_snr_db; HARQ *harq; //uplink diff --git a/src/src/ArgManager.cc b/src/src/ArgManager.cc index b952653..16b350b 100644 --- a/src/src/ArgManager.cc +++ b/src/src/ArgManager.cc @@ -20,6 +20,8 @@ void ArgManager::defaultArgs(Args& args) { args.time_offset = 0; args.force_N_id_2 = -1; // Pick the best args.input_file_name = ""; + args.input_file_raw_sync = false; + args.input_file_raw_sc16 = false; args.dci_file_name = ""; args.stats_file_name = ""; args.file_offset_time = 0; @@ -29,6 +31,7 @@ void ArgManager::defaultArgs(Args& args) { args.file_nof_ports = DEFAULT_NOF_PORTS; args.file_cell_id = 0; args.file_wrap = false; + args.raw_iq_output_file = ""; args.rf_args = ""; args.rf_freq = -1.0; args.ul_freq = 0; @@ -54,6 +57,7 @@ void ArgManager::defaultArgs(Args& args) { args.rf_args = ""; args.verbose = 0; args.enable_cfo_ref = 1; + args.dl_dci_min_snr_db = 6.0; args.estimator_alg = "interpolate"; args.cell_search = false; args.cell_id = 0; @@ -64,7 +68,7 @@ void ArgManager::defaultArgs(Args& args) { } void ArgManager::usage(Args& args, const std::string& prog) { - printf("Usage: %s [aAcCDdEfghHilLnpPrRsStTvwWyYqFIuUmOoz] -f rx_frequency (in Hz) | -i input_file\n", prog.c_str()); + printf("Usage: %s [aAcCDdEfGghHijlJLnpPQrRsStTvwWyYqFIuUmOoz] -f rx_frequency (in Hz) | -i input_file\n", prog.c_str()); printf("\t-h show this help message\n"); #ifndef DISABLE_RF printf("\t-a RF args [Default %s]\n", args.rf_args.c_str()); @@ -78,9 +82,13 @@ void ArgManager::usage(Args& args, const std::string& prog) { #endif printf("\t-i input_file [Default use RF board] (default disable)\n"); printf("\t-D output filename for DCI [default stdout]\n"); + printf("\t-G minimum DL DCI search SNR in dB for offline replay [Default %.1f]\n", args.dl_dci_min_snr_db); + printf("\t-j treat -i as generic raw IQ and synchronize it before decoding [Default disabled]\n"); + printf("\t-J raw -i/-j input is interleaved sc16 instead of cf32 [Default disabled]\n"); printf("\t-o offset frequency correction (in Hz) for input file [Default %.1f Hz]\n", args.file_offset_freq); printf("\t-O offset samples for input file [Default %d]\n", args.file_offset_time); printf("\t-P nof_ports for input file [Default %d]\n", args.file_nof_ports); + printf("\t-Q output live raw IQ capture to cf32 file while decoding [Default disabled]\n"); printf("\t-c cell_id for input file [Default %d]\n", args.file_cell_id); printf("\t-C Enable cell search, default disable, \n"); printf("\t-C Disable CFO correction [Default %s]\n", args.disable_cfo ? "Disabled" : "Enabled"); @@ -107,7 +115,7 @@ void ArgManager::usage(Args& args, const std::string& prog) { void ArgManager::parseArgs(Args& args, int argc, char **argv) { int opt; defaultArgs(args); - while ((opt = getopt(argc, argv, "aAcCDdEfghHilLnpPrRsStTvwWyYqFIuUmOoz")) != -1) { + while ((opt = getopt(argc, argv, "aAcCDdEfGghHijlJLnpPQrRsStTvwWyYqFIuUmOoz")) != -1) { switch (opt) { case 'a': args.rf_args = argv[optind]; @@ -118,6 +126,9 @@ void ArgManager::parseArgs(Args& args, int argc, char **argv) { case 'g': args.rf_gain = strtod(argv[optind], nullptr); break; + case 'G': + args.dl_dci_min_snr_db = strtod(argv[optind], nullptr); + break; case 'L': args.enable_shortcut_discovery = false; break; @@ -127,6 +138,12 @@ void ArgManager::parseArgs(Args& args, int argc, char **argv) { case 'i': args.input_file_name = argv[optind]; break; + case 'j': + args.input_file_raw_sync = true; + break; + case 'J': + args.input_file_raw_sc16 = true; + break; case 'I': args.cell_id = static_cast(strtoul(argv[optind], nullptr, 0)); case 'w': @@ -154,6 +171,9 @@ void ArgManager::parseArgs(Args& args, int argc, char **argv) { case 'P': args.file_nof_ports = static_cast(strtoul(argv[optind], nullptr, 0)); break; + case 'Q': + args.raw_iq_output_file = argv[optind]; + break; case 'c': args.file_cell_id = static_cast(strtoul(argv[optind], nullptr, 0)); break; diff --git a/src/src/DCISearch.cc b/src/src/DCISearch.cc index ae7d6de..0e74df7 100644 --- a/src/src/DCISearch.cc +++ b/src/src/DCISearch.cc @@ -534,7 +534,8 @@ DCISearch::DCISearch(falcon_ue_dl_t& falcon_ue_dl, uint32_t sf_idx, uint32_t sfn, srsran_dl_sf_cfg_t *sf, - srsran_ue_dl_cfg_t *ue_dl_cfg) : + srsran_ue_dl_cfg_t *ue_dl_cfg, + double min_snr_db) : falcon_ue_dl(falcon_ue_dl), metaFormats(metaFormats), rntiManager(rntiManager), @@ -542,6 +543,7 @@ DCISearch::DCISearch(falcon_ue_dl_t& falcon_ue_dl, subframePower(subframeInfo.getSubframePower()), sf_idx(sf_idx), sfn(sfn), + min_snr_db(min_snr_db), stats(), enableShortcutDiscovery(true), sf(sf), @@ -566,7 +568,7 @@ int DCISearch::search() { dciCollection.setSubframe(sfn, sf_idx, sf->cfi); } float snr_db = falcon_ue_dl.q->chest_res.snr_db; - if (snr_db > 6.0){ + if (snr_db > min_snr_db){ //PrintLifetime lt(test_string + "DCI Blind Search: "); temp_dci0.clear(); recursive_blind_dci_search(&dci_msg, sf->cfi); diff --git a/src/src/LTESniffer_Core.cc b/src/src/LTESniffer_Core.cc index e091341..37dca8a 100644 --- a/src/src/LTESniffer_Core.cc +++ b/src/src/LTESniffer_Core.cc @@ -83,7 +83,8 @@ LTESniffer_Core::LTESniffer_Core(const Args& args): &harq, args.mcs_tracking_mode, args.harq_mode, - &ulsche); + &ulsche, + args.dl_dci_min_snr_db); phy->getCommon().setShortcutDiscovery(args.enable_shortcut_discovery); std::shared_ptr cons(new DCIConsumerList()); if(args.dci_file_name != "") { @@ -110,6 +111,11 @@ bool LTESniffer_Core::run(){ .nof_valid_pss_frames = SRSRAN_DEFAULT_NOF_VALID_PSS_FRAMES, .init_agc = 0, .force_tdd = false}; + raw_iq_file_source_t raw_input_file = {}; + srsran_filesink_t raw_iq_sink = {}; + rf_live_source_t rf_live_source = {}; + bool use_raw_sync_mode = args.input_file_raw_sync; + std::string file_input_name = args.input_file_name; srsran_cell_t cell; falcon_ue_dl_t falcon_ue_dl; srsran_dl_sf_cfg_t dl_sf; @@ -227,39 +233,134 @@ bool LTESniffer_Core::run(){ ERROR("Could not set sampling rate"); exit(-1); } + rf_live_source.rx_srate = srate_rf; } else { ERROR("Invalid number of PRB %d", cell.nof_prb); exit(-1); } INFO("Stopping RF and flushing buffer...\r"); + + rf_live_source.rf = &rf; + rf_live_source.raw_sink = nullptr; + rf_live_source.nof_channels = args.rf_nof_rx_ant; + rf_live_source.write_failed = false; + rf_live_source.have_last_rx_ts = false; + rf_live_source.last_rx_ts_samples = 0; + rf_live_source.last_rx_nsamples = 0; + rf_live_source.overflow_count = 0; + rf_live_source.late_rx_count = 0; + rf_live_source.rx_error_count = 0; + rf_live_source.other_error_count = 0; + rf_live_source.timestamp_gap_events = 0; + rf_live_source.timestamp_gap_samples_accum = 0; + rf_live_source.timestamp_gap_samples_max_abs = 0; + + if (!args.raw_iq_output_file.empty()) { + if (srsran_filesink_init(&raw_iq_sink, args.raw_iq_output_file.c_str(), SRSRAN_COMPLEX_FLOAT_BIN)) { + ERROR("Could not open raw IQ output file %s", args.raw_iq_output_file.c_str()); + exit(-1); + } + setvbuf(raw_iq_sink.f, nullptr, _IOFBF, 8 * 1024 * 1024); + rf_live_source.raw_sink = &raw_iq_sink; + srsran_rf_register_error_handler(&rf, srsran_rf_error_handler_wrapper, &rf_live_source); + printf("Writing live raw IQ capture to %s\n", args.raw_iq_output_file.c_str()); + } } #endif /* If reading from file, go straight to PDSCH decoding. Otherwise, decode MIB first */ - if (args.input_file_name != "") { + if (file_input_name != "") { /* preset cell configuration */ cell.id = args.file_cell_id; cell.cp = SRSRAN_CP_NORM; cell.phich_length = SRSRAN_PHICH_NORM; - cell.phich_resources = SRSRAN_PHICH_R_1_6; + cell.phich_resources = SRSRAN_PHICH_R_1; cell.nof_ports = args.file_nof_ports; cell.nof_prb = args.nof_prb; - char* tmp_filename = new char[args.input_file_name.length()+1]; - strncpy(tmp_filename, args.input_file_name.c_str(), args.input_file_name.length()); - tmp_filename[args.input_file_name.length()] = 0; - if (srsran_ue_sync_init_file_multi(&ue_sync, - args.nof_prb, - tmp_filename, - args.file_offset_time, - args.file_offset_freq, - args.rf_nof_rx_ant)) { //args.rf_nof_rx_ant - ERROR("Error initiating ue_sync"); - exit(-1); + if (use_raw_sync_mode) { + if (args.rf_nof_rx_ant != 1) { + ERROR("Raw input file sync mode currently supports exactly one RX antenna"); + exit(-1); + } + + raw_input_file.file = fopen(file_input_name.c_str(), "rb"); + if (raw_input_file.file == nullptr) { + perror(file_input_name.c_str()); + exit(-1); + } + raw_input_file.eof = false; + raw_input_file.wrap = args.file_wrap; + raw_input_file.samples_read = 0; + raw_input_file.input_is_sc16 = args.input_file_raw_sc16; + raw_input_file.sc16_buffer = nullptr; + raw_input_file.sc16_capacity_samples = 0; + raw_input_file.apply_cfo = false; + raw_input_file.cfo_freq = 0.0f; + + if (raw_input_file.input_is_sc16) { + cout << "Treating raw input file as interleaved sc16 IQ" << endl; + } else { + cout << "Treating raw input file as interleaved cf32 IQ" << endl; + } + + if (args.file_offset_time != 0) { + off_t bytes_per_sample = raw_input_file.input_is_sc16 ? static_cast(sizeof(int16_t) * 2) + : static_cast(sizeof(cf_t)); + off_t byte_offset = static_cast(args.file_offset_time) * bytes_per_sample; + if (fseeko(raw_input_file.file, byte_offset, SEEK_SET) != 0) { + perror("fseeko"); + fclose(raw_input_file.file); + raw_input_file.file = nullptr; + exit(-1); + } + } + + if (args.file_offset_freq != 0.0) { + uint32_t raw_cfo_block_len = 3 * SRSRAN_SF_LEN_PRB(cell.nof_prb); + if (srsran_cfo_init(&raw_input_file.cfo_correct, raw_cfo_block_len)) { + ERROR("Error initiating raw input CFO corrector"); + fclose(raw_input_file.file); + raw_input_file.file = nullptr; + exit(-1); + } + raw_input_file.apply_cfo = true; + raw_input_file.cfo_freq = + static_cast(args.file_offset_freq / 15000.0 / srsran_symbol_sz(cell.nof_prb)); + cout << "Applying raw input frequency correction of " << args.file_offset_freq << " Hz" << endl; + } + + if (srsran_ue_sync_init_multi(&ue_sync, + cell.nof_prb, + false, + raw_iq_file_recv_wrapper, + 1, + &raw_input_file)) { + ERROR("Error initiating ue_sync for raw input file"); + exit(-1); + } + if (srsran_ue_sync_set_cell(&ue_sync, cell)) { + ERROR("Error setting LTE cell for raw input file"); + exit(-1); + } + } else { + char* tmp_filename = new char[file_input_name.length() + 1]; + strncpy(tmp_filename, file_input_name.c_str(), file_input_name.length()); + tmp_filename[file_input_name.length()] = 0; + if (srsran_ue_sync_init_file_multi(&ue_sync, + args.nof_prb, + tmp_filename, + args.file_offset_time, + args.file_offset_freq, + args.rf_nof_rx_ant)) { + ERROR("Error initiating ue_sync"); + exit(-1); + } + srsran_ue_sync_file_wrap(&ue_sync, args.file_wrap); + delete[] tmp_filename; + tmp_filename = nullptr; } - delete[] tmp_filename; - tmp_filename = nullptr; } else { #ifndef DISABLE_RF @@ -276,7 +377,7 @@ bool LTESniffer_Core::run(){ cell.id == 1000, srsran_rf_recv_wrapper, args.rf_nof_rx_ant, - (void*)&rf, + (void*)&rf_live_source, decimate)) { ERROR("Error initiating ue_sync"); exit(-1); @@ -300,11 +401,15 @@ bool LTESniffer_Core::run(){ /* Config mib */ srsran_ue_mib_t ue_mib; + srsran_cell_t mib_cell = cell; + if (use_raw_sync_mode) { + mib_cell.nof_ports = 0; + } if (srsran_ue_mib_init(&ue_mib, cur_buffer[0], cell.nof_prb)) { ERROR("Error initaiting UE MIB decoder"); exit(-1); } - if (srsran_ue_mib_set_cell(&ue_mib, cell)) { + if (srsran_ue_mib_set_cell(&ue_mib, mib_cell)) { ERROR("Error initaiting UE MIB decoder"); exit(-1); } @@ -314,6 +419,9 @@ bool LTESniffer_Core::run(){ ue_sync.cfo_is_copied = true; ue_sync.cfo_correct_enable_find = true; srsran_sync_set_cfo_cp_enable(&ue_sync.sfind, false, 0); + if (use_raw_sync_mode) { + srsran_sync_set_threshold(&ue_sync.sfind, 1.5f); + } ZERO_OBJECT(dl_sf); ZERO_OBJECT(pdsch_cfg); @@ -353,6 +461,11 @@ bool LTESniffer_Core::run(){ uint64_t sf_cnt = 0; //uint32_t sfn = 0; uint32_t last_decoded_tm = 0; + bool raw_stream_started = false; + uint32_t raw_expected_sf_idx = 0; + uint64_t raw_search_subframes = 0; + uint64_t raw_lock_subframes = 0; + uint32_t raw_lock_sf0_attempts = 0; /* Length in complex samples */ uint32_t max_num_samples = 3 * SRSRAN_SF_LEN_PRB(cell.nof_prb); @@ -364,10 +477,16 @@ bool LTESniffer_Core::run(){ set_srsran_verbose_level(args.verbose); ret = srsran_ue_sync_zerocopy(&ue_sync, cur_worker->getBuffers(), max_num_samples); if (ret < 0) { - if (args.input_file_name != ""){ - std::cout << "Finish reading from file" << std::endl; + if (file_input_name != "") { + if (use_raw_sync_mode && raw_input_file.eof) { + std::cout << "Finish reading raw input file" << std::endl; + } else { + std::cout << "Finish reading from file" << std::endl; + } + break; } ERROR("Error calling srsran_ue_sync_work()"); + break; } // std:: cout << "CFO = " << srsran_ue_sync_get_cfo(&ue_sync) << std::endl; #ifdef CORRECT_SAMPLE_OFFSET @@ -378,20 +497,81 @@ bool LTESniffer_Core::run(){ if (ret == 1){ uint32_t sf_idx = srsran_ue_sync_get_sfidx(&ue_sync); + uint32_t logical_sf_idx = sf_idx; + if (use_raw_sync_mode && state == DECODE_MIB) { + if (!raw_stream_started) { + raw_search_subframes++; + if (raw_search_subframes > 4000) { + ERROR("Failed to lock to a stable subframe-0 boundary while streaming raw input file"); + break; + } + if (sf_idx != 0) { + continue; + } + raw_stream_started = true; + raw_expected_sf_idx = 0; + raw_lock_subframes = 0; + raw_lock_sf0_attempts = 0; + srsran_ue_mib_reset(&ue_mib); + cout << "Raw input stream locked at raw_samples=" << raw_input_file.samples_read << endl; + } + if (sf_idx != raw_expected_sf_idx) { + cout << "Raw input stream discarded short lock after sf_idx jump from " + << raw_expected_sf_idx << " to " << sf_idx << endl; + raw_stream_started = false; + raw_expected_sf_idx = 0; + raw_lock_subframes = 0; + raw_lock_sf0_attempts = 0; + srsran_ue_mib_reset(&ue_mib); + continue; + } + raw_lock_subframes++; + raw_expected_sf_idx = (raw_expected_sf_idx + 1) % 10; + } switch (state) { case DECODE_MIB: if (sf_idx == 0) { uint8_t bch_payload[SRSRAN_BCH_PAYLOAD_LEN]; int sfn_offset; + if (use_raw_sync_mode) { + raw_lock_sf0_attempts++; + } n = srsran_ue_mib_decode(&ue_mib, bch_payload, NULL, &sfn_offset); if (n < 0) { ERROR("Error decoding UE MIB"); exit(-1); } else if (n == SRSRAN_UE_MIB_FOUND) { - srsran_pbch_mib_unpack(bch_payload, &cell, &sfn); - srsran_cell_fprint(stdout, &cell, sfn); - printf("Decoded MIB. SFN: %d, offset: %d\n", sfn, sfn_offset); - sfn = (sfn + sfn_offset) % 1024; + srsran_cell_t decoded_mib_cell = cell; + uint32_t decoded_sfn = 0; + srsran_pbch_mib_unpack(bch_payload, &decoded_mib_cell, &decoded_sfn); + if (file_input_name != "") { + if (!srsran_cell_isvalid(&decoded_mib_cell) || decoded_mib_cell.nof_prb != cell.nof_prb) { + cout << "Rejected file-input MIB candidate at sf_idx=" << sf_idx + << ": decoded PRB=" << decoded_mib_cell.nof_prb + << ", expected PRB=" << cell.nof_prb << endl; + srsran_ue_mib_reset(&ue_mib); + break; + } + } + if (use_raw_sync_mode) { + cell.nof_ports = decoded_mib_cell.nof_ports; + cell.cp = decoded_mib_cell.cp; + cell.phich_length = decoded_mib_cell.phich_length; + cell.phich_resources = decoded_mib_cell.phich_resources; + cell.nof_prb = decoded_mib_cell.nof_prb; + raw_search_subframes = 0; + raw_lock_subframes = 0; + raw_lock_sf0_attempts = 0; + } else if (file_input_name == "") { + cell = decoded_mib_cell; + } + if (!phy->setCell(cell)) { + cout << "Error updating UE downlink processing module after MIB decode" << endl; + exit(-1); + } + srsran_cell_fprint(stdout, &decoded_mib_cell, decoded_sfn); + printf("Decoded MIB. SFN: %d, offset: %d\n", decoded_sfn, sfn_offset); + sfn = (decoded_sfn + sfn_offset) % 1024; state = DECODE_PDSCH; //config RNTI Manager from Falcon Lib @@ -415,6 +595,16 @@ bool LTESniffer_Core::run(){ //disallow RNTI=0 for all formats rntiManager.addForbidden(0x0, 0x0, f); } + } else if (use_raw_sync_mode && raw_lock_sf0_attempts >= 12) { + cout << "Raw input stream discarded lock without a valid MIB after " + << raw_lock_subframes << " synchronized subframes and " + << raw_lock_sf0_attempts << " sf0 attempts" << endl; + raw_stream_started = false; + raw_expected_sf_idx = 0; + raw_search_subframes = 0; + raw_lock_subframes = 0; + raw_lock_sf0_attempts = 0; + srsran_ue_mib_reset(&ue_mib); } } break; @@ -426,15 +616,15 @@ bool LTESniffer_Core::run(){ mcs_tracking.reset_nof_api_msg(); } } - uint32_t tti = sfn * 10 + sf_idx; + uint32_t tti = sfn * 10 + logical_sf_idx; /* Prepare sf_idx and sfn for worker , SF_NRM only*/ dl_sf.tti = tti; dl_sf.sf_type = SRSRAN_SF_NORM; - cur_worker->prepare(sf_idx, sfn, sf_cnt % (args.dci_format_split_update_interval_ms) == 0, dl_sf); + cur_worker->prepare(logical_sf_idx, sfn, sf_cnt % (args.dci_format_split_update_interval_ms) == 0, dl_sf); /*Get next worker from avail list*/ - std:shared_ptr next_worker; + std::shared_ptr next_worker; if(args.input_file_name == "") { next_worker = phy->getAvailImmediate(); //here non-blocking if reading from radio } else { @@ -453,7 +643,7 @@ bool LTESniffer_Core::run(){ } /*increase system frame number*/ - if (sf_idx == 9) { + if (logical_sf_idx == 9) { sfn++; } if (sfn == 1024){ @@ -503,15 +693,25 @@ bool LTESniffer_Core::run(){ break; } } + sf_cnt++; } else if(ret == 0){ //get buffer wrong or out of sync /*Change state to Decode MIB to find system frame number again*/ if (state == DECODE_PDSCH && nof_lost_sync > 5){ state = DECODE_MIB; + raw_stream_started = false; + raw_expected_sf_idx = 0; + raw_search_subframes = 0; + raw_lock_subframes = 0; + raw_lock_sf0_attempts = 0; + srsran_cell_t mib_reset_cell = cell; + if (use_raw_sync_mode) { + mib_reset_cell.nof_ports = 0; + } if (srsran_ue_mib_init(&ue_mib, cur_worker->getBuffers()[0], cell.nof_prb)) { ERROR("Error initaiting UE MIB decoder"); exit(-1); } - if (srsran_ue_mib_set_cell(&ue_mib, cell)) { + if (srsran_ue_mib_set_cell(&ue_mib, mib_reset_cell)) { ERROR("Error initaiting UE MIB decoder"); exit(-1); } @@ -523,7 +723,6 @@ bool LTESniffer_Core::run(){ ", FrameCnt: " << ue_sync.frame_total_cnt << " State: " << ue_sync.state << endl; } - sf_cnt++; } // main loop @@ -548,11 +747,32 @@ bool LTESniffer_Core::run(){ std::cout << "Destroyed Phy" << std::endl; if (args.input_file_name == ""){ + if (!args.raw_iq_output_file.empty()) { + cout << "Live raw IQ capture summary: overflows=" << rf_live_source.overflow_count.load() + << ", late_rx=" << rf_live_source.late_rx_count.load() + << ", rx_errors=" << rf_live_source.rx_error_count.load() + << ", other_errors=" << rf_live_source.other_error_count.load() + << ", timestamp_gap_events=" << rf_live_source.timestamp_gap_events + << ", timestamp_gap_samples_accum=" << rf_live_source.timestamp_gap_samples_accum + << ", timestamp_gap_samples_max_abs=" << rf_live_source.timestamp_gap_samples_max_abs + << (rf_live_source.write_failed ? ", file_write_failed=1" : ", file_write_failed=0") + << endl; + } srsran_rf_close(&rf); - //srsran_ue_dl_free(falcon_ue_dl.q); - srsran_ue_sync_free(&ue_sync); - srsran_ue_mib_free(&ue_mib); + if (raw_iq_sink.f != nullptr) { + srsran_filesink_free(&raw_iq_sink); + } + } else if (use_raw_sync_mode && raw_input_file.file != nullptr) { + fclose(raw_input_file.file); + if (raw_input_file.sc16_buffer != nullptr) { + free(raw_input_file.sc16_buffer); + } + if (raw_input_file.apply_cfo) { + srsran_cfo_free(&raw_input_file.cfo_correct); + } } + srsran_ue_sync_free(&ue_sync); + srsran_ue_mib_free(&ue_mib); //common->getRNTIManager().printActiveSet(); cout << "Skipped subframe: " << skip_cnt << " / " << sf_cnt << endl; //phy->getCommon().getRNTIManager().printActiveSet(); @@ -593,11 +813,133 @@ int srsran_rf_recv_wrapper( void* h, uint32_t nsamples, srsran_timestamp_t* t){ DEBUG(" ---- Receive %d samples ----", nsamples); + rf_live_source_t* source = static_cast(h); + if (source == nullptr || source->rf == nullptr) { + return SRSRAN_ERROR_INVALID_INPUTS; + } void* ptr[SRSRAN_MAX_PORTS]; for (int i = 0; i < SRSRAN_MAX_PORTS; i++) { ptr[i] = data_[i]; } - return srsran_rf_recv_with_time_multi((srsran_rf_t*)h, ptr, nsamples, true, NULL, NULL); + time_t secs = 0; + double frac_secs = 0.0; + int ret = srsran_rf_recv_with_time_multi(source->rf, ptr, nsamples, true, &secs, &frac_secs); + if (ret > 0 && source->raw_sink != nullptr && !source->write_failed) { + int written = srsran_filesink_write_multi(source->raw_sink, ptr, ret, source->nof_channels); + if (written != ret * static_cast(source->nof_channels)) { + source->write_failed = true; + ERROR("Failed writing live raw IQ capture file, disabling recorder"); + } + } + if (ret > 0 && source->rx_srate > 0.0) { + srsran_timestamp_t rx_ts = {}; + srsran_timestamp_init(&rx_ts, secs, frac_secs); + uint64_t current_ts_samples = srsran_timestamp_uint64(&rx_ts, source->rx_srate); + if (source->have_last_rx_ts) { + uint64_t expected_ts_samples = source->last_rx_ts_samples + source->last_rx_nsamples; + int64_t delta = static_cast(current_ts_samples) - static_cast(expected_ts_samples); + if (delta != 0) { + source->timestamp_gap_events++; + source->timestamp_gap_samples_accum += delta; + int64_t abs_delta = llabs(delta); + if (abs_delta > source->timestamp_gap_samples_max_abs) { + source->timestamp_gap_samples_max_abs = abs_delta; + } + } + } + source->have_last_rx_ts = true; + source->last_rx_ts_samples = current_ts_samples; + source->last_rx_nsamples = static_cast(ret); + } + if (t != nullptr && ret > 0) { + srsran_timestamp_init(t, secs, frac_secs); + } + return ret; +} + +void srsran_rf_error_handler_wrapper(void* arg, srsran_rf_error_t error) +{ + rf_live_source_t* source = static_cast(arg); + if (source == nullptr) { + return; + } + + switch (error.type) { + case srsran_rf_error_t::SRSRAN_RF_ERROR_OVERFLOW: + source->overflow_count++; + break; + case srsran_rf_error_t::SRSRAN_RF_ERROR_LATE: + if (error.opt == 1) { + source->late_rx_count++; + } else { + source->other_error_count++; + } + break; + case srsran_rf_error_t::SRSRAN_RF_ERROR_RX: + source->rx_error_count++; + break; + default: + source->other_error_count++; + break; + } +} + +int raw_iq_file_recv_wrapper(void* h, + cf_t* data_[SRSRAN_MAX_PORTS], + uint32_t nsamples, + srsran_timestamp_t* t) +{ + raw_iq_file_source_t* source = static_cast(h); + (void)t; + + if (source == nullptr || source->file == nullptr || data_[0] == nullptr) { + return SRSRAN_ERROR_INVALID_INPUTS; + } + + uint32_t total_read = 0; + while (total_read < nsamples) { + size_t nread = 0; + if (source->input_is_sc16) { + if (source->sc16_capacity_samples < nsamples) { + int16_t* resized = static_cast(realloc(source->sc16_buffer, sizeof(int16_t) * 2 * nsamples)); + if (resized == nullptr) { + return SRSRAN_ERROR; + } + source->sc16_buffer = resized; + source->sc16_capacity_samples = nsamples; + } + + nread = fread(source->sc16_buffer, sizeof(int16_t) * 2, nsamples - total_read, source->file); + for (size_t i = 0; i < nread; ++i) { + cf_t sample = 0.0f; + __real__ sample = static_cast(source->sc16_buffer[2 * i]) / 32768.0f; + __imag__ sample = static_cast(source->sc16_buffer[2 * i + 1]) / 32768.0f; + data_[0][total_read + i] = sample; + } + } else { + nread = fread(&data_[0][total_read], sizeof(cf_t), nsamples - total_read, source->file); + } + + source->samples_read += nread; + total_read += static_cast(nread); + + if (total_read == nsamples) { + if (source->apply_cfo) { + srsran_cfo_correct(&source->cfo_correct, data_[0], data_[0], source->cfo_freq); + } + return static_cast(total_read); + } + + if (!source->wrap || !feof(source->file)) { + source->eof = true; + return SRSRAN_ERROR; + } + + clearerr(source->file); + rewind(source->file); + } + + return static_cast(total_read); } void LTESniffer_Core::setDCIConsumer(std::shared_ptr consumer) { diff --git a/src/src/Phy.cc b/src/src/Phy.cc index 5c8e675..cc1edf5 100644 --- a/src/src/Phy.cc +++ b/src/src/Phy.cc @@ -14,7 +14,8 @@ Phy::Phy(uint32_t nof_rx_antennas, HARQ *harq, int mcs_tracking_mode, int harq_mode, - ULSchedule *ulsche): + ULSchedule *ulsche, + double dl_dci_min_snr_db): nof_rx_antennas(nof_rx_antennas), nof_workers(nof_workers), workers(), @@ -37,7 +38,8 @@ Phy::Phy(uint32_t nof_rx_antennas, mcs_tracking_mode, harq_mode, ulsche, - mcs_tracking->get_sniffer_mode())); + mcs_tracking->get_sniffer_mode(), + dl_dci_min_snr_db)); workers.push_back(worker); avail.enqueue(worker); } diff --git a/src/src/SubframeWorker.cc b/src/src/SubframeWorker.cc index c533548..9b382eb 100644 --- a/src/src/SubframeWorker.cc +++ b/src/src/SubframeWorker.cc @@ -18,7 +18,8 @@ SubframeWorker::SubframeWorker(uint32_t idx, int mcs_tracking_mode, int harq_mode, ULSchedule *ulsche, - int sniffer_mode) : sfb(common.nof_rx_antennas), + int sniffer_mode, + double dl_dci_min_snr_db) : sfb(common.nof_rx_antennas), idx(idx), max_prb(max_prb), common(common), @@ -29,6 +30,7 @@ SubframeWorker::SubframeWorker(uint32_t idx, stats(), pcapwriter(pcapwriter), mcs_tracking(mcs_tracking), + dl_dci_min_snr_db(dl_dci_min_snr_db), harq(harq), harq_mode(harq_mode), mcs_tracking_mode(mcs_tracking_mode), @@ -161,7 +163,8 @@ void SubframeWorker::work() common.getRNTIManager(), subframeInfo, sf_idx, sfn, - &dl_sf, &ue_dl_cfg); + &dl_sf, &ue_dl_cfg, + dl_dci_min_snr_db); dciSearch.setShortcutDiscovery(common.getShortcutDiscovery()); int snr_ret = SRSRAN_SUCCESS; @@ -416,4 +419,4 @@ void SubframeWorker::print_nof_DCI(SubframeInfo &subframeInfo, uint32_t tti) std::string time_str = ss.str(); std::cout << time_str << "," << tti << "," << nof_dci << std::endl; -} \ No newline at end of file +}